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