1use std::sync::Arc;
4
5use itertools::{EitherOrBoth, Itertools};
6use tokio::sync::RwLock;
7
8use crate::{
9 execution::{
10 annotations,
11 memory::Stack,
12 state::{self as exec_state, ModuleInfoMap},
13 EnvironmentRef, ExecutorSettings,
14 },
15 parsing::ast::types::{Annotation, Node, Program},
16 walk::Node as WalkNode,
17 ExecOutcome, ExecutorContext,
18};
19
20lazy_static::lazy_static! {
21 static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
23 static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
25}
26
27pub(super) async fn read_old_ast() -> Option<GlobalState> {
29 let old_ast = OLD_AST.read().await;
30 old_ast.clone()
31}
32
33pub(super) async fn write_old_ast(old_state: GlobalState) {
34 let mut old_ast = OLD_AST.write().await;
35 *old_ast = Some(old_state);
36}
37
38pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
39 let old_mem = PREV_MEMORY.read().await;
40 old_mem.clone()
41}
42
43pub(crate) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
44 let mut old_mem = PREV_MEMORY.write().await;
45 *old_mem = Some(mem);
46}
47
48pub async fn bust_cache() {
49 let mut old_ast = OLD_AST.write().await;
50 *old_ast = None;
51}
52
53pub async fn clear_mem_cache() {
54 let mut old_mem = PREV_MEMORY.write().await;
55 *old_mem = None;
56}
57
58#[derive(Debug, Clone)]
60pub struct CacheInformation<'a> {
61 pub ast: &'a Node<Program>,
62 pub settings: &'a ExecutorSettings,
63}
64
65#[derive(Debug, Clone)]
67pub(super) struct GlobalState {
68 pub(super) main: ModuleState,
69 pub(super) exec_state: exec_state::GlobalState,
71 pub(super) settings: ExecutorSettings,
73}
74
75impl GlobalState {
76 pub fn new(
77 state: exec_state::ExecState,
78 settings: ExecutorSettings,
79 ast: Node<Program>,
80 result_env: EnvironmentRef,
81 ) -> Self {
82 Self {
83 main: ModuleState {
84 ast,
85 exec_state: state.mod_local,
86 result_env,
87 },
88 exec_state: state.global,
89 settings,
90 }
91 }
92
93 pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
94 self.settings = settings;
95 self
96 }
97
98 pub fn reconstitute_exec_state(&self) -> exec_state::ExecState {
99 exec_state::ExecState {
100 global: self.exec_state.clone(),
101 mod_local: self.main.exec_state.clone(),
102 }
103 }
104
105 pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> ExecOutcome {
106 ExecOutcome {
109 variables: self.main.exec_state.variables(self.main.result_env),
110 filenames: self.exec_state.filenames(),
111 #[cfg(feature = "artifact-graph")]
112 operations: self.exec_state.artifacts.operations,
113 #[cfg(feature = "artifact-graph")]
114 artifact_commands: self.exec_state.artifacts.commands,
115 #[cfg(feature = "artifact-graph")]
116 artifact_graph: self.exec_state.artifacts.graph,
117 errors: self.exec_state.errors,
118 default_planes: ctx.engine.get_default_planes().read().await.clone(),
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub(super) struct ModuleState {
126 pub(super) ast: Node<Program>,
128 pub(super) exec_state: exec_state::ModuleState,
130 pub(super) result_env: EnvironmentRef,
132}
133
134#[derive(Debug, Clone, PartialEq)]
136#[allow(clippy::large_enum_variant)]
137pub(super) enum CacheResult {
138 ReExecute {
139 clear_scene: bool,
141 reapply_settings: bool,
143 program: Node<Program>,
145 },
146 CheckImportsOnly {
150 reapply_settings: bool,
152 ast: Node<Program>,
154 },
155 NoAction(bool),
157}
158
159pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult {
167 let mut reapply_settings = false;
168
169 if old.settings != new.settings {
172 reapply_settings = true;
175 }
176
177 if old.ast == new.ast {
180 if !old.ast.has_import_statements() {
184 return CacheResult::NoAction(reapply_settings);
185 }
186
187 return CacheResult::CheckImportsOnly {
189 reapply_settings,
190 ast: old.ast.clone(),
191 };
192 }
193
194 let mut old_ast = old.ast.clone();
196 let mut new_ast = new.ast.clone();
197
198 old_ast.compute_digest();
201 new_ast.compute_digest();
202
203 if old_ast.digest == new_ast.digest {
205 if !old.ast.has_import_statements() {
209 return CacheResult::NoAction(reapply_settings);
210 }
211
212 return CacheResult::CheckImportsOnly {
214 reapply_settings,
215 ast: old.ast.clone(),
216 };
217 }
218
219 if !old_ast
221 .inner_attrs
222 .iter()
223 .filter(annotations::is_significant)
224 .zip_longest(new_ast.inner_attrs.iter().filter(annotations::is_significant))
225 .all(|pair| {
226 match pair {
227 EitherOrBoth::Both(old, new) => {
228 let Annotation { name, properties, .. } = &old.inner;
231 let Annotation {
232 name: new_name,
233 properties: new_properties,
234 ..
235 } = &new.inner;
236
237 name.as_ref().map(|n| n.digest) == new_name.as_ref().map(|n| n.digest)
238 && properties
239 .as_ref()
240 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
241 == new_properties
242 .as_ref()
243 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
244 }
245 _ => false,
246 }
247 })
248 {
249 return CacheResult::ReExecute {
253 clear_scene: true,
254 reapply_settings: true,
255 program: new.ast.clone(),
256 };
257 }
258
259 generate_changed_program(old_ast, new_ast, reapply_settings)
261}
262
263fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>, reapply_settings: bool) -> CacheResult {
270 if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
271 let old_node: WalkNode = old.into();
272 let new_node: WalkNode = new.into();
273 old_node.digest() == new_node.digest()
274 }) {
275 return CacheResult::ReExecute {
281 clear_scene: true,
282 reapply_settings,
283 program: new_ast,
284 };
285 }
286
287 match new_ast.body.len().cmp(&old_ast.body.len()) {
291 std::cmp::Ordering::Less => {
292 CacheResult::ReExecute {
301 clear_scene: true,
302 reapply_settings,
303 program: new_ast,
304 }
305 }
306 std::cmp::Ordering::Greater => {
307 new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
315
316 CacheResult::ReExecute {
317 clear_scene: false,
318 reapply_settings,
319 program: new_ast,
320 }
321 }
322 std::cmp::Ordering::Equal => {
323 CacheResult::NoAction(reapply_settings)
333 }
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use pretty_assertions::assert_eq;
340
341 use super::*;
342 use crate::execution::{parse_execute, parse_execute_with_project_dir, ExecTestResults};
343
344 #[tokio::test(flavor = "multi_thread")]
345 async fn test_get_changed_program_same_code() {
346 let new = r#"// Remove the end face for the extrusion.
347firstSketch = startSketchOn(XY)
348 |> startProfile(at = [-12, 12])
349 |> line(end = [24, 0])
350 |> line(end = [0, -24])
351 |> line(end = [-24, 0])
352 |> close()
353 |> extrude(length = 6)
354
355// Remove the end face for the extrusion.
356shell(firstSketch, faces = [END], thickness = 0.25)"#;
357
358 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
359
360 let result = get_changed_program(
361 CacheInformation {
362 ast: &program.ast,
363 settings: &exec_ctxt.settings,
364 },
365 CacheInformation {
366 ast: &program.ast,
367 settings: &exec_ctxt.settings,
368 },
369 )
370 .await;
371
372 assert_eq!(result, CacheResult::NoAction(false));
373 }
374
375 #[tokio::test(flavor = "multi_thread")]
376 async fn test_get_changed_program_same_code_changed_whitespace() {
377 let old = r#" // Remove the end face for the extrusion.
378firstSketch = startSketchOn(XY)
379 |> startProfile(at = [-12, 12])
380 |> line(end = [24, 0])
381 |> line(end = [0, -24])
382 |> line(end = [-24, 0])
383 |> close()
384 |> extrude(length = 6)
385
386// Remove the end face for the extrusion.
387shell(firstSketch, faces = [END], thickness = 0.25) "#;
388
389 let new = r#"// Remove the end face for the extrusion.
390firstSketch = startSketchOn(XY)
391 |> startProfile(at = [-12, 12])
392 |> line(end = [24, 0])
393 |> line(end = [0, -24])
394 |> line(end = [-24, 0])
395 |> close()
396 |> extrude(length = 6)
397
398// Remove the end face for the extrusion.
399shell(firstSketch, faces = [END], thickness = 0.25)"#;
400
401 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
402
403 let program_new = crate::Program::parse_no_errs(new).unwrap();
404
405 let result = get_changed_program(
406 CacheInformation {
407 ast: &program.ast,
408 settings: &exec_ctxt.settings,
409 },
410 CacheInformation {
411 ast: &program_new.ast,
412 settings: &exec_ctxt.settings,
413 },
414 )
415 .await;
416
417 assert_eq!(result, CacheResult::NoAction(false));
418 }
419
420 #[tokio::test(flavor = "multi_thread")]
421 async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() {
422 let old = r#" // Removed the end face for the extrusion.
423firstSketch = startSketchOn(XY)
424 |> startProfile(at = [-12, 12])
425 |> line(end = [24, 0])
426 |> line(end = [0, -24])
427 |> line(end = [-24, 0])
428 |> close()
429 |> extrude(length = 6)
430
431// Remove the end face for the extrusion.
432shell(firstSketch, faces = [END], thickness = 0.25) "#;
433
434 let new = r#"// Remove the end face for the extrusion.
435firstSketch = startSketchOn(XY)
436 |> startProfile(at = [-12, 12])
437 |> line(end = [24, 0])
438 |> line(end = [0, -24])
439 |> line(end = [-24, 0])
440 |> close()
441 |> extrude(length = 6)
442
443// Remove the end face for the extrusion.
444shell(firstSketch, faces = [END], thickness = 0.25)"#;
445
446 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
447
448 let program_new = crate::Program::parse_no_errs(new).unwrap();
449
450 let result = get_changed_program(
451 CacheInformation {
452 ast: &program.ast,
453 settings: &exec_ctxt.settings,
454 },
455 CacheInformation {
456 ast: &program_new.ast,
457 settings: &exec_ctxt.settings,
458 },
459 )
460 .await;
461
462 assert_eq!(result, CacheResult::NoAction(false));
463 }
464
465 #[tokio::test(flavor = "multi_thread")]
466 async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
467 let old = r#"@foo(whatever = whatever)
468@bar
469// Removed the end face for the extrusion.
470firstSketch = startSketchOn(XY)
471 |> startProfile(at = [-12, 12])
472 |> line(end = [24, 0])
473 |> line(end = [0, -24])
474 |> line(end = [-24, 0]) // my thing
475 |> close()
476 |> extrude(length = 6)
477
478// Remove the end face for the extrusion.
479shell(firstSketch, faces = [END], thickness = 0.25) "#;
480
481 let new = r#"@foo(whatever = 42)
482@baz
483// Remove the end face for the extrusion.
484firstSketch = startSketchOn(XY)
485 |> startProfile(at = [-12, 12])
486 |> line(end = [24, 0])
487 |> line(end = [0, -24])
488 |> line(end = [-24, 0])
489 |> close()
490 |> extrude(length = 6)
491
492// Remove the end face for the extrusion.
493shell(firstSketch, faces = [END], thickness = 0.25)"#;
494
495 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
496
497 let program_new = crate::Program::parse_no_errs(new).unwrap();
498
499 let result = get_changed_program(
500 CacheInformation {
501 ast: &program.ast,
502 settings: &exec_ctxt.settings,
503 },
504 CacheInformation {
505 ast: &program_new.ast,
506 settings: &exec_ctxt.settings,
507 },
508 )
509 .await;
510
511 assert_eq!(result, CacheResult::NoAction(false));
512 }
513
514 #[tokio::test(flavor = "multi_thread")]
516 async fn test_get_changed_program_same_code_but_different_grid_setting() {
517 let new = r#"// Remove the end face for the extrusion.
518firstSketch = startSketchOn(XY)
519 |> startProfile(at = [-12, 12])
520 |> line(end = [24, 0])
521 |> line(end = [0, -24])
522 |> line(end = [-24, 0])
523 |> close()
524 |> extrude(length = 6)
525
526// Remove the end face for the extrusion.
527shell(firstSketch, faces = [END], thickness = 0.25)"#;
528
529 let ExecTestResults {
530 program, mut exec_ctxt, ..
531 } = parse_execute(new).await.unwrap();
532
533 exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
535
536 let result = get_changed_program(
537 CacheInformation {
538 ast: &program.ast,
539 settings: &Default::default(),
540 },
541 CacheInformation {
542 ast: &program.ast,
543 settings: &exec_ctxt.settings,
544 },
545 )
546 .await;
547
548 assert_eq!(result, CacheResult::NoAction(true));
549 }
550
551 #[tokio::test(flavor = "multi_thread")]
553 async fn test_get_changed_program_same_code_but_different_edge_visibility_setting() {
554 let new = r#"// Remove the end face for the extrusion.
555firstSketch = startSketchOn(XY)
556 |> startProfile(at = [-12, 12])
557 |> line(end = [24, 0])
558 |> line(end = [0, -24])
559 |> line(end = [-24, 0])
560 |> close()
561 |> extrude(length = 6)
562
563// Remove the end face for the extrusion.
564shell(firstSketch, faces = [END], thickness = 0.25)"#;
565
566 let ExecTestResults {
567 program, mut exec_ctxt, ..
568 } = parse_execute(new).await.unwrap();
569
570 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
572
573 let result = get_changed_program(
574 CacheInformation {
575 ast: &program.ast,
576 settings: &Default::default(),
577 },
578 CacheInformation {
579 ast: &program.ast,
580 settings: &exec_ctxt.settings,
581 },
582 )
583 .await;
584
585 assert_eq!(result, CacheResult::NoAction(true));
586
587 let old_settings = exec_ctxt.settings.clone();
589 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
590
591 let result = get_changed_program(
592 CacheInformation {
593 ast: &program.ast,
594 settings: &old_settings,
595 },
596 CacheInformation {
597 ast: &program.ast,
598 settings: &exec_ctxt.settings,
599 },
600 )
601 .await;
602
603 assert_eq!(result, CacheResult::NoAction(true));
604
605 let old_settings = exec_ctxt.settings.clone();
607 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
608
609 let result = get_changed_program(
610 CacheInformation {
611 ast: &program.ast,
612 settings: &old_settings,
613 },
614 CacheInformation {
615 ast: &program.ast,
616 settings: &exec_ctxt.settings,
617 },
618 )
619 .await;
620
621 assert_eq!(result, CacheResult::NoAction(true));
622 }
623
624 #[tokio::test(flavor = "multi_thread")]
627 async fn test_get_changed_program_same_code_but_different_unit_setting_using_annotation() {
628 let old_code = r#"@settings(defaultLengthUnit = in)
629startSketchOn(XY)
630"#;
631 let new_code = r#"@settings(defaultLengthUnit = mm)
632startSketchOn(XY)
633"#;
634
635 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
636
637 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
638 new_program.compute_digest();
639
640 let result = get_changed_program(
641 CacheInformation {
642 ast: &program.ast,
643 settings: &exec_ctxt.settings,
644 },
645 CacheInformation {
646 ast: &new_program.ast,
647 settings: &exec_ctxt.settings,
648 },
649 )
650 .await;
651
652 assert_eq!(
653 result,
654 CacheResult::ReExecute {
655 clear_scene: true,
656 reapply_settings: true,
657 program: new_program.ast,
658 }
659 );
660 }
661
662 #[tokio::test(flavor = "multi_thread")]
665 async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
666 let old_code = r#"@settings(defaultLengthUnit = in)
667startSketchOn(XY)
668"#;
669 let new_code = r#"
670startSketchOn(XY)
671"#;
672
673 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
674
675 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
676 new_program.compute_digest();
677
678 let result = get_changed_program(
679 CacheInformation {
680 ast: &program.ast,
681 settings: &exec_ctxt.settings,
682 },
683 CacheInformation {
684 ast: &new_program.ast,
685 settings: &exec_ctxt.settings,
686 },
687 )
688 .await;
689
690 assert_eq!(
691 result,
692 CacheResult::ReExecute {
693 clear_scene: true,
694 reapply_settings: true,
695 program: new_program.ast,
696 }
697 );
698 }
699
700 #[tokio::test(flavor = "multi_thread")]
701 async fn test_multi_file_no_changes_does_not_reexecute() {
702 let code = r#"import "toBeImported.kcl" as importedCube
703
704importedCube
705
706sketch001 = startSketchOn(XZ)
707profile001 = startProfile(sketch001, at = [-134.53, -56.17])
708 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
709 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
710 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
711 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
712 |> close()
713extrude001 = extrude(profile001, length = 100)
714sketch003 = startSketchOn(extrude001, face = seg02)
715sketch002 = startSketchOn(extrude001, face = seg01)
716"#;
717
718 let other_file = (
719 std::path::PathBuf::from("toBeImported.kcl"),
720 r#"sketch001 = startSketchOn(XZ)
721profile001 = startProfile(sketch001, at = [281.54, 305.81])
722 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
723 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
724 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
725 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
726 |> close()
727extrude(profile001, length = 100)"#
728 .to_string(),
729 );
730
731 let tmp_dir = std::env::temp_dir();
732 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
733
734 let tmp_file = tmp_dir.join(other_file.0);
736 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
737 std::fs::write(tmp_file, other_file.1).unwrap();
738
739 let ExecTestResults { program, exec_ctxt, .. } =
740 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
741 .await
742 .unwrap();
743
744 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
745 new_program.compute_digest();
746
747 let result = get_changed_program(
748 CacheInformation {
749 ast: &program.ast,
750 settings: &exec_ctxt.settings,
751 },
752 CacheInformation {
753 ast: &new_program.ast,
754 settings: &exec_ctxt.settings,
755 },
756 )
757 .await;
758
759 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
760 panic!("Expected CheckImportsOnly, got {:?}", result);
761 };
762
763 assert_eq!(reapply_settings, false);
764 }
765
766 #[tokio::test(flavor = "multi_thread")]
767 async fn test_cache_multi_file_only_other_file_changes_should_reexecute() {
768 let code = r#"import "toBeImported.kcl" as importedCube
769
770importedCube
771
772sketch001 = startSketchOn(XZ)
773profile001 = startProfile(sketch001, at = [-134.53, -56.17])
774 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
775 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
776 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
777 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
778 |> close()
779extrude001 = extrude(profile001, length = 100)
780sketch003 = startSketchOn(extrude001, face = seg02)
781sketch002 = startSketchOn(extrude001, face = seg01)
782"#;
783
784 let other_file = (
785 std::path::PathBuf::from("toBeImported.kcl"),
786 r#"sketch001 = startSketchOn(XZ)
787profile001 = startProfile(sketch001, at = [281.54, 305.81])
788 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
789 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
790 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
791 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
792 |> close()
793extrude(profile001, length = 100)"#
794 .to_string(),
795 );
796
797 let other_file2 = (
798 std::path::PathBuf::from("toBeImported.kcl"),
799 r#"sketch001 = startSketchOn(XZ)
800profile001 = startProfile(sketch001, at = [281.54, 305.81])
801 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
802 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
803 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
804 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
805 |> close()
806extrude(profile001, length = 100)
807|> translate(z=100)
808"#
809 .to_string(),
810 );
811
812 let tmp_dir = std::env::temp_dir();
813 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
814
815 let tmp_file = tmp_dir.join(other_file.0);
817 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
818 std::fs::write(&tmp_file, other_file.1).unwrap();
819
820 let ExecTestResults { program, exec_ctxt, .. } =
821 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
822 .await
823 .unwrap();
824
825 std::fs::write(tmp_file, other_file2.1).unwrap();
827
828 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
829 new_program.compute_digest();
830
831 let result = get_changed_program(
832 CacheInformation {
833 ast: &program.ast,
834 settings: &exec_ctxt.settings,
835 },
836 CacheInformation {
837 ast: &new_program.ast,
838 settings: &exec_ctxt.settings,
839 },
840 )
841 .await;
842
843 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
844 panic!("Expected CheckImportsOnly, got {:?}", result);
845 };
846
847 assert_eq!(reapply_settings, false);
848 }
849}