Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit cf602ea

Browse files
author
Hendrik van Antwerpen
committed
Only add to the precondition if this is actually possible.
1 parent 2cf1bfb commit cf602ea

File tree

3 files changed

+262
-14
lines changed

3 files changed

+262
-14
lines changed

stack-graphs/src/partial.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2311,9 +2311,9 @@ impl Node {
23112311
None => return Err(PathResolutionError::MissingAttachedScopeList),
23122312
};
23132313
*scope_stack_postcondition = new_scope_stack;
2314-
} else {
2315-
// If the symbol stack postcondition is empty, then we need to update the
2316-
// _precondition_ to indicate that the symbol stack needs to contain this scoped
2314+
} else if symbol_stack_postcondition.has_variable() {
2315+
// If the symbol stack postcondition is empty but has a variable, then we can update
2316+
// the _precondition_ to indicate that the symbol stack needs to contain this scoped
23172317
// symbol in order to successfully use this partial path.
23182318
let scope_stack_variable =
23192319
PartialPath::fresh_scope_stack_variable_for_partial_stack(
@@ -2327,9 +2327,18 @@ impl Node {
23272327
scope_stack_variable,
23282328
)),
23292329
};
2330+
// We simply push to the precondition. The official procedure here
2331+
// is to bind the postcondition symbol stack variable to the symbol
2332+
// and a fresh variable, and apply that. However, because the variable
2333+
// can only be bound in the precondition symbol stack, this amounts to
2334+
// pushing the symbol there directly.
23302335
symbol_stack_precondition.push_back(partials, precondition_symbol);
23312336
*scope_stack_postcondition =
23322337
PartialScopeStack::from_variable(scope_stack_variable);
2338+
} else {
2339+
// The symbol stack postcondition is empty and has no variable, so we cannot
2340+
// perform the operation.
2341+
return Err(PathResolutionError::SymbolStackUnsatisfied);
23332342
}
23342343
}
23352344
Self::PopSymbol(sink) => {
@@ -2341,15 +2350,24 @@ impl Node {
23412350
if top.scopes.is_some() {
23422351
return Err(PathResolutionError::UnexpectedAttachedScopeList);
23432352
}
2344-
} else {
2345-
// If the symbol stack postcondition is empty, then we need to update the
2346-
// _precondition_ to indicate that the symbol stack needs to contain this symbol in
2347-
// order to successfully use this partial path.
2353+
} else if symbol_stack_postcondition.has_variable() {
2354+
// If the symbol stack postcondition is empty but has a variable, then we can update
2355+
// the _precondition_ to indicate that the symbol stack needs to contain this symbol
2356+
// in order to successfully use this partial path.
23482357
let precondition_symbol = PartialScopedSymbol {
23492358
symbol: sink.symbol,
23502359
scopes: ControlledOption::none(),
23512360
};
2361+
// We simply push to the precondition. The official procedure here
2362+
// is to bind the postcondition symbol stack variable to the symbol
2363+
// and a fresh variable, and apply that. However, because the variable
2364+
// can only be bound in the precondition symbol stack, this amounts to
2365+
// pushing the symbol there directly.
23522366
symbol_stack_precondition.push_back(partials, precondition_symbol);
2367+
} else {
2368+
// The symbol stack postcondition is empty and has no variable, so we cannot
2369+
// perform the operation.
2370+
return Err(PathResolutionError::SymbolStackUnsatisfied);
23532371
}
23542372
}
23552373
Self::PushScopedSymbol(sink) => {

stack-graphs/tests/it/partial.rs

Lines changed: 230 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,192 @@ fn can_create_partial_path_from_node() {
519519
);
520520
}
521521

522+
#[test]
523+
fn can_append_edges() -> Result<(), PathResolutionError> {
524+
let mut graph = StackGraph::new();
525+
let file = graph.add_file("test").expect("");
526+
let jump_to_scope_node = StackGraph::jump_to_node();
527+
let scope0 = create_scope_node(&mut graph, file, false);
528+
let scope1 = create_scope_node(&mut graph, file, false);
529+
let foo_ref = create_push_symbol_node(&mut graph, file, "foo", false);
530+
let foo_def = create_pop_symbol_node(&mut graph, file, "foo", false);
531+
let bar_ref = create_push_symbol_node(&mut graph, file, "bar", false);
532+
let bar_def = create_pop_symbol_node(&mut graph, file, "bar", false);
533+
let exported_scope = create_scope_node(&mut graph, file, true);
534+
let exported_scope_id = graph[exported_scope].id();
535+
let baz_ref = create_push_scoped_symbol_node(&mut graph, file, "baz", exported_scope_id, false);
536+
let baz_def = create_pop_scoped_symbol_node(&mut graph, file, "baz", false);
537+
let drop_scopes = create_drop_scopes_node(&mut graph, file);
538+
539+
fn run(
540+
graph: &StackGraph,
541+
left: NicePartialPath,
542+
right: NiceEdge,
543+
expected: &str,
544+
) -> Result<(), PathResolutionError> {
545+
let mut g = StackGraph::new();
546+
g.add_from_graph(graph).expect("");
547+
548+
let mut ps = PartialPaths::new();
549+
550+
let mut l = create_partial_path_and_edges(&mut g, &mut ps, left).expect("");
551+
let r = create_edge(&mut g, right);
552+
553+
l.append(graph, &mut ps, r)?;
554+
let actual = l.display(&g, &mut ps).to_string();
555+
assert_eq!(expected, actual);
556+
557+
Ok(())
558+
}
559+
560+
fn verify(graph: &StackGraph, left: NicePartialPath, right: NiceEdge, expected: &str) {
561+
run(graph, left, right, expected).expect("");
562+
}
563+
564+
fn verify_not(graph: &StackGraph, left: NicePartialPath, right: NiceEdge) {
565+
run(graph, left, right, "").expect_err("");
566+
}
567+
568+
verify(
569+
&graph,
570+
&[foo_ref, scope0],
571+
(scope0, foo_def),
572+
"<%1> ($1) [test(2) push foo] -> [test(3) pop foo] <%1> ($1)",
573+
);
574+
575+
verify(
576+
&graph,
577+
&[foo_ref, scope0],
578+
(scope0, scope1),
579+
"<%1> ($1) [test(2) push foo] -> [test(1) scope] <foo,%1> ($1)",
580+
);
581+
582+
verify(
583+
&graph,
584+
&[scope0, scope1],
585+
(scope1, foo_ref),
586+
"<%1> ($1) [test(0) scope] -> [test(2) push foo] <foo,%1> ($1)",
587+
);
588+
589+
verify(
590+
&graph,
591+
&[foo_def, scope0],
592+
(scope0, bar_ref),
593+
"<foo,%1> ($1) [test(3) pop foo] -> [test(4) push bar] <bar,%1> ($1)",
594+
);
595+
596+
verify(
597+
&graph,
598+
&[foo_ref, scope0, foo_def],
599+
(foo_def, bar_ref),
600+
"<%1> ($1) [test(2) push foo] -> [test(4) push bar] <bar,%1> ($1)",
601+
);
602+
603+
verify(
604+
&graph,
605+
&[foo_def, scope0, bar_ref],
606+
(bar_ref, bar_def),
607+
"<foo,%1> ($1) [test(3) pop foo] -> [test(5) pop bar] <%1> ($1)",
608+
);
609+
610+
verify(
611+
&graph,
612+
&[baz_ref, scope0, baz_def],
613+
(baz_def, bar_ref),
614+
"<%1> ($1) [test(7) push scoped baz test(6)] -> [test(4) push bar] <bar,%1> ([test(6)],$1)",
615+
);
616+
617+
verify(
618+
&graph,
619+
&[foo_def, scope0, baz_ref],
620+
(baz_ref, baz_def),
621+
"<foo,%1> ($1) [test(3) pop foo] -> [test(8) pop scoped baz] <%1> ([test(6)],$1)",
622+
);
623+
624+
verify(
625+
&graph,
626+
&[scope0, drop_scopes],
627+
(drop_scopes, scope1),
628+
"<%1> ($1) [test(0) scope] -> [test(1) scope] <%1> ()",
629+
);
630+
631+
verify_not(
632+
&graph,
633+
&[scope0, drop_scopes, scope1],
634+
(scope1, jump_to_scope_node),
635+
);
636+
637+
verify(
638+
&graph,
639+
&[baz_def, scope0],
640+
(scope0, jump_to_scope_node),
641+
"<baz/($2),%1> ($1) [test(8) pop scoped baz] -> [jump to scope] <%1> ($2)",
642+
);
643+
644+
verify(
645+
&graph,
646+
&[baz_ref, scope0],
647+
(scope0, jump_to_scope_node),
648+
"<%1> ($1) [test(7) push scoped baz test(6)] -> [jump to scope] <baz/([test(6)],$1),%1> ($1)",
649+
);
650+
651+
Ok(())
652+
}
653+
654+
#[test]
655+
fn can_append_edges_without_precondition_variables() -> Result<(), PathResolutionError> {
656+
let mut graph = StackGraph::new();
657+
let file = graph.add_file("test").expect("");
658+
let scope0 = create_scope_node(&mut graph, file, false);
659+
let foo_ref = create_push_symbol_node(&mut graph, file, "foo", false);
660+
let foo_def = create_pop_symbol_node(&mut graph, file, "foo", false);
661+
let bar_ref = create_push_symbol_node(&mut graph, file, "bar", false);
662+
let bar_def = create_pop_symbol_node(&mut graph, file, "bar", false);
663+
664+
fn run(
665+
graph: &StackGraph,
666+
left: NicePartialPath,
667+
right: NiceEdge,
668+
expected: &str,
669+
) -> Result<(), PathResolutionError> {
670+
let mut g = StackGraph::new();
671+
g.add_from_graph(graph).expect("");
672+
673+
let mut ps = PartialPaths::new();
674+
675+
let mut l = create_partial_path_and_edges(&mut g, &mut ps, left).expect("");
676+
l.eliminate_precondition_stack_variables(&mut ps);
677+
let r = create_edge(&mut g, right);
678+
679+
l.append(graph, &mut ps, r)?;
680+
let actual = l.display(&g, &mut ps).to_string();
681+
assert_eq!(expected, actual);
682+
683+
Ok(())
684+
}
685+
686+
fn verify(graph: &StackGraph, left: NicePartialPath, right: NiceEdge, expected: &str) {
687+
run(graph, left, right, expected).expect("");
688+
}
689+
690+
fn verify_not(graph: &StackGraph, left: NicePartialPath, right: NiceEdge) {
691+
run(graph, left, right, "").expect_err("");
692+
}
693+
694+
verify(
695+
&graph,
696+
&[foo_ref, scope0, foo_def],
697+
(foo_def, bar_ref),
698+
"<> () [test(1) push foo] -> [test(3) push bar] <bar> ()",
699+
);
700+
701+
verify_not(&graph, &[scope0], (scope0, bar_def));
702+
703+
verify_not(&graph, &[foo_ref, scope0, foo_def], (foo_def, bar_def));
704+
705+
Ok(())
706+
}
707+
522708
#[test]
523709
fn can_append_partial_paths() -> Result<(), PathResolutionError> {
524710
let mut graph = StackGraph::new();
@@ -658,24 +844,61 @@ fn can_append_partial_paths() -> Result<(), PathResolutionError> {
658844
"<%1> ($1) [test(7) push scoped baz test(6)] -> [jump to scope] <baz/([test(6)],$1),%1> ($1)",
659845
);
660846

661-
// verify that without stack variables in the precondition, the precondition cannot grow because of concatenation
662-
{
663-
let left = &[scope0];
664-
let right = &[scope0, bar_def];
847+
Ok(())
848+
}
665849

850+
#[test]
851+
fn can_append_partial_paths_without_precondition_variables() -> Result<(), PathResolutionError> {
852+
let mut graph = StackGraph::new();
853+
let file = graph.add_file("test").expect("");
854+
let scope0 = create_scope_node(&mut graph, file, false);
855+
let foo_ref = create_push_symbol_node(&mut graph, file, "foo", false);
856+
let foo_def = create_pop_symbol_node(&mut graph, file, "foo", false);
857+
let bar_ref = create_push_symbol_node(&mut graph, file, "bar", false);
858+
let bar_def = create_pop_symbol_node(&mut graph, file, "bar", false);
859+
860+
fn run(
861+
graph: &StackGraph,
862+
left: NicePartialPath,
863+
right: NicePartialPath,
864+
expected: &str,
865+
) -> Result<(), PathResolutionError> {
666866
let mut g = StackGraph::new();
667-
g.add_from_graph(&graph).expect("");
867+
g.add_from_graph(graph).expect("");
668868

669869
let mut ps = PartialPaths::new();
870+
670871
let mut l = create_partial_path_and_edges(&mut g, &mut ps, left).expect("");
671872
l.eliminate_precondition_stack_variables(&mut ps);
672873
let mut r = create_partial_path_and_edges(&mut g, &mut ps, right).expect("");
673874

674875
r.ensure_no_overlapping_variables(&mut ps, &l);
675-
let result = l.concatenate(&g, &mut ps, &r);
676-
result.expect_err("");
876+
l.concatenate(&g, &mut ps, &r)?;
877+
let actual = l.display(&g, &mut ps).to_string();
878+
assert_eq!(expected, actual);
879+
880+
Ok(())
881+
}
882+
883+
fn verify(graph: &StackGraph, left: NicePartialPath, right: NicePartialPath, expected: &str) {
884+
run(graph, left, right, expected).expect("");
885+
}
886+
887+
fn verify_not(graph: &StackGraph, left: NicePartialPath, right: NicePartialPath) {
888+
run(graph, left, right, "").expect_err("");
677889
}
678890

891+
verify(
892+
&graph,
893+
&[foo_ref, scope0],
894+
&[scope0, foo_def, bar_ref],
895+
"<> () [test(1) push foo] -> [test(3) push bar] <bar> ()",
896+
);
897+
898+
verify_not(&graph, &[scope0], &[scope0, bar_def]);
899+
900+
verify_not(&graph, &[foo_ref, scope0, foo_def], &[foo_def, bar_def]);
901+
679902
Ok(())
680903
}
681904

stack-graphs/tests/it/util.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub(crate) type NiceSymbolStack<'a> = (&'a [NiceScopedSymbol<'a>], Option<Symbol
2525
pub(crate) type NiceScopedSymbol<'a> = (&'a str, Option<NiceScopeStack<'a>>);
2626
pub(crate) type NiceScopeStack<'a> = (&'a [u32], Option<ScopeStackVariable>);
2727
pub(crate) type NicePartialPath<'a> = &'a [Handle<Node>];
28+
pub(crate) type NiceEdge = (Handle<Node>, Handle<Node>);
2829

2930
pub(crate) fn create_drop_scopes_node(graph: &mut StackGraph, file: Handle<File>) -> Handle<Node> {
3031
let id = graph.new_node_id(file);
@@ -164,6 +165,12 @@ pub(crate) fn create_partial_path_and_edges(
164165
Ok(path)
165166
}
166167

168+
pub(crate) fn create_edge(graph: &mut StackGraph, contents: NiceEdge) -> Edge {
169+
let edge = edge(contents.0, contents.1, 0);
170+
graph.add_edge(edge.source, edge.sink, edge.precedence);
171+
edge
172+
}
173+
167174
pub(crate) fn edge(source: Handle<Node>, sink: Handle<Node>, precedence: i32) -> Edge {
168175
Edge {
169176
source,

0 commit comments

Comments
 (0)