Prune call-graph paths using switch constraints during search.

Propagate path bindings while exploring CallEdge constraints in CallGraphPathFinder so impossible dispatch arms are skipped before chain assembly, with inheritance fallback preserved for polymorphic resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 05:53:17 +02:00
parent 23a14d8391
commit db158200dd
5 changed files with 206 additions and 3 deletions

View File

@@ -78,4 +78,40 @@ class CallGraphPathFinderTest {
"com.acme.End.finish"
);
}
@Test
void shouldPruneBranchesDuringSearchWhenConstraintsPresent() {
Map<String, List<CallEdge>> graph = new HashMap<>();
graph.put("com.acme.Start.run", List.of(
new CallEdge("com.acme.BranchA.step", List.of(), null, "command == \"PAY\""),
new CallEdge("com.acme.BranchB.step", List.of(), null, "command == \"SHIP\"")
));
graph.put("com.acme.BranchA.step", List.of(
new CallEdge("com.acme.End.finish", List.of())
));
graph.put("com.acme.BranchB.step", List.of(
new CallEdge("com.acme.End.finish", List.of())
));
CallGraphPathFinder pathFinder = new CallGraphPathFinder(new CodebaseContext());
PathBindingEvaluator evaluator = new PathBindingEvaluator(
new CodebaseContext(), null, null, null);
List<List<String>> unconstrained = pathFinder.findAllPaths(
"com.acme.Start.run", "com.acme.End.finish", graph, new HashSet<>());
assertThat(unconstrained).hasSize(2);
List<List<String>> payOnly = pathFinder.findAllPaths(
"com.acme.Start.run",
"com.acme.End.finish",
graph,
new HashSet<>(),
evaluator,
Map.of("command", "PAY"));
assertThat(payOnly).hasSize(1);
assertThat(payOnly.get(0)).containsExactly(
"com.acme.Start.run",
"com.acme.BranchA.step",
"com.acme.End.finish");
}
}

View File

@@ -65,6 +65,9 @@ class PathBindingEvaluatorTest {
Map<String, List<CallEdge>> graph = engine.buildCallGraph();
CallGraphPathFinder pathFinder = new CallGraphPathFinder(context);
PathBindingEvaluator evaluator = new PathBindingEvaluator(
context, engine.variableTracer, context.getConstantResolver(), engine.typeResolver);
List<List<String>> rawPaths = pathFinder.findAllPaths(
"com.example.ApiController.pay",
"com.example.CentralDispatcher.fire",
@@ -72,8 +75,15 @@ class PathBindingEvaluatorTest {
new java.util.HashSet<>());
assertThat(rawPaths).hasSize(2);
PathBindingEvaluator evaluator = new PathBindingEvaluator(
context, engine.variableTracer, context.getConstantResolver(), engine.typeResolver);
List<List<String>> constrainedPaths = pathFinder.findAllPaths(
"com.example.ApiController.pay",
"com.example.CentralDispatcher.fire",
graph,
new java.util.HashSet<>(),
evaluator,
Map.of());
assertThat(constrainedPaths).hasSize(1);
assertThat(constrainedPaths.get(0)).contains("com.example.CentralDispatcher.orderPay");
List<CallEdge> dispatchEdges = graph.get("com.example.CentralDispatcher.dispatch");
assertThat(dispatchEdges.stream().map(CallEdge::getConstraint).filter(c -> c != null && !c.isBlank()))