|
1 | 1 | #![cfg(feature = "inventory")]
|
2 | 2 |
|
3 |
| -//! Tests for cycles where the cycle head is stored on a tracked struct |
4 |
| -//! and that tracked struct is freed in a later revision. |
5 |
| -
|
6 | 3 | mod common;
|
7 | 4 |
|
8 | 5 | use crate::common::{EventLoggerDatabase, LogDatabase};
|
@@ -45,6 +42,7 @@ struct Node<'db> {
|
45 | 42 | #[salsa::input(debug)]
|
46 | 43 | struct GraphInput {
|
47 | 44 | simple: bool,
|
| 45 | + fixpoint_variant: usize, |
48 | 46 | }
|
49 | 47 |
|
50 | 48 | #[salsa::tracked(returns(ref))]
|
@@ -125,11 +123,13 @@ fn cycle_recover(
|
125 | 123 | CycleRecoveryAction::Iterate
|
126 | 124 | }
|
127 | 125 |
|
| 126 | +/// Tests for cycles where the cycle head is stored on a tracked struct |
| 127 | +/// and that tracked struct is freed in a later revision. |
128 | 128 | #[test]
|
129 | 129 | fn main() {
|
130 | 130 | let mut db = EventLoggerDatabase::default();
|
131 | 131 |
|
132 |
| - let input = GraphInput::new(&db, false); |
| 132 | + let input = GraphInput::new(&db, false, 0); |
133 | 133 | let graph = create_graph(&db, input);
|
134 | 134 | let c = graph.find_node(&db, "c").unwrap();
|
135 | 135 |
|
@@ -192,3 +192,126 @@ fn main() {
|
192 | 192 | "WillCheckCancellation",
|
193 | 193 | ]"#]]);
|
194 | 194 | }
|
| 195 | + |
| 196 | +#[salsa::tracked] |
| 197 | +struct IterationNode<'db> { |
| 198 | + #[returns(ref)] |
| 199 | + name: String, |
| 200 | + iteration: usize, |
| 201 | +} |
| 202 | + |
| 203 | +/// A cyclic query that creates more tracked structs in later fixpoint iterations. |
| 204 | +/// |
| 205 | +/// The output depends on the input's fixpoint_variant: |
| 206 | +/// - variant=0: Returns `[base]` (1 struct, no cycle) |
| 207 | +/// - variant=1: Through fixpoint iteration, returns `[iter_0, iter_1, iter_2]` (3 structs) |
| 208 | +/// - variant=2: Through fixpoint iteration, returns `[iter_0, iter_1]` (2 structs) |
| 209 | +/// - variant>2: Through fixpoint iteration, returns `[iter_0, iter_1]` (2 structs, same as variant=2) |
| 210 | +/// |
| 211 | +/// When variant > 0, the query creates a cycle by calling itself. The fixpoint iteration |
| 212 | +/// proceeds as follows: |
| 213 | +/// 1. Initial: returns empty vector |
| 214 | +/// 2. First iteration: returns `[iter_0]` |
| 215 | +/// 3. Second iteration: returns `[iter_0, iter_1]` |
| 216 | +/// 4. Third iteration (only for variant=1): returns `[iter_0, iter_1, iter_2]` |
| 217 | +/// 5. Further iterations: no change, fixpoint reached |
| 218 | +#[salsa::tracked(cycle_fn=cycle_recover_with_structs, cycle_initial=initial_with_structs)] |
| 219 | +fn create_tracked_in_cycle<'db>( |
| 220 | + db: &'db dyn Database, |
| 221 | + input: GraphInput, |
| 222 | +) -> Vec<IterationNode<'db>> { |
| 223 | + // Check if we should create more nodes based on the input. |
| 224 | + let variant = input.fixpoint_variant(db); |
| 225 | + |
| 226 | + if variant == 0 { |
| 227 | + // Base case - no cycle, just return a single node. |
| 228 | + vec![IterationNode::new(db, "base".to_string(), 0)] |
| 229 | + } else { |
| 230 | + // Create a cycle by calling ourselves. |
| 231 | + let previous = create_tracked_in_cycle(db, input); |
| 232 | + |
| 233 | + // In later iterations, create additional tracked structs. |
| 234 | + if previous.is_empty() { |
| 235 | + // First iteration - initial returns empty. |
| 236 | + vec![IterationNode::new(db, "iter_0".to_string(), 0)] |
| 237 | + } else { |
| 238 | + // Limit based on variant: variant=1 allows 3 nodes, variant=2 allows 2 nodes. |
| 239 | + let limit = if variant == 1 { 3 } else { 2 }; |
| 240 | + |
| 241 | + if previous.len() < limit { |
| 242 | + // Subsequent iterations - add more nodes. |
| 243 | + let mut nodes = previous; |
| 244 | + nodes.push(IterationNode::new( |
| 245 | + db, |
| 246 | + format!("iter_{}", nodes.len()), |
| 247 | + nodes.len(), |
| 248 | + )); |
| 249 | + nodes |
| 250 | + } else { |
| 251 | + // Reached the limit. |
| 252 | + previous |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | +} |
| 257 | + |
| 258 | +fn initial_with_structs(_db: &dyn Database, _input: GraphInput) -> Vec<IterationNode<'_>> { |
| 259 | + vec![] |
| 260 | +} |
| 261 | + |
| 262 | +#[allow(clippy::ptr_arg)] |
| 263 | +fn cycle_recover_with_structs<'db>( |
| 264 | + _db: &'db dyn Database, |
| 265 | + _value: &Vec<IterationNode<'db>>, |
| 266 | + _iteration: u32, |
| 267 | + _input: GraphInput, |
| 268 | +) -> CycleRecoveryAction<Vec<IterationNode<'db>>> { |
| 269 | + CycleRecoveryAction::Iterate |
| 270 | +} |
| 271 | + |
| 272 | +#[test] |
| 273 | +fn test_cycle_with_fixpoint_structs() { |
| 274 | + let mut db = EventLoggerDatabase::default(); |
| 275 | + |
| 276 | + // Create an input that will trigger the cyclic behavior. |
| 277 | + let input = GraphInput::new(&db, false, 1); |
| 278 | + |
| 279 | + // Initial query - this will create structs across multiple iterations. |
| 280 | + let nodes = create_tracked_in_cycle(&db, input); |
| 281 | + assert_eq!(nodes.len(), 3); |
| 282 | + // First iteration: previous is empty [], so we get [iter_0] |
| 283 | + // Second iteration: previous is [iter_0], so we get [iter_0, iter_1] |
| 284 | + // Third iteration: previous is [iter_0, iter_1], so we get [iter_0, iter_1, iter_2] |
| 285 | + assert_eq!(nodes[0].name(&db), "iter_0"); |
| 286 | + assert_eq!(nodes[1].name(&db), "iter_1"); |
| 287 | + assert_eq!(nodes[2].name(&db), "iter_2"); |
| 288 | + |
| 289 | + // Clear logs to focus on the change. |
| 290 | + db.clear_logs(); |
| 291 | + |
| 292 | + // Change the input to force re-execution with a different variant. |
| 293 | + // This will create 2 tracked structs instead of 3 (one fewer than before). |
| 294 | + input.set_fixpoint_variant(&mut db).to(2); |
| 295 | + |
| 296 | + // Re-query - this should handle the tracked struct changes properly. |
| 297 | + let nodes = create_tracked_in_cycle(&db, input); |
| 298 | + assert_eq!(nodes.len(), 2); |
| 299 | + assert_eq!(nodes[0].name(&db), "iter_0"); |
| 300 | + assert_eq!(nodes[1].name(&db), "iter_1"); |
| 301 | + |
| 302 | + // Check the logs to ensure proper execution and struct management. |
| 303 | + // We should see the third struct (iter_2) being discarded. |
| 304 | + db.assert_logs(expect![[r#" |
| 305 | + [ |
| 306 | + "DidSetCancellationFlag", |
| 307 | + "WillCheckCancellation", |
| 308 | + "WillExecute { database_key: create_tracked_in_cycle(Id(0)) }", |
| 309 | + "WillCheckCancellation", |
| 310 | + "WillIterateCycle { database_key: create_tracked_in_cycle(Id(0)), iteration_count: IterationCount(1), fell_back: false }", |
| 311 | + "WillCheckCancellation", |
| 312 | + "WillIterateCycle { database_key: create_tracked_in_cycle(Id(0)), iteration_count: IterationCount(2), fell_back: false }", |
| 313 | + "WillCheckCancellation", |
| 314 | + "WillDiscardStaleOutput { execute_key: create_tracked_in_cycle(Id(0)), output_key: IterationNode(Id(402)) }", |
| 315 | + "DidDiscard { key: IterationNode(Id(402)) }", |
| 316 | + ]"#]]); |
| 317 | +} |
0 commit comments