Fix abstract getter widening and nested reactive flatMap resolution.

Ensure abstract-type getter resolution merges subclass field values and trace indexed accessors on the requesting type, and extract terminal payloads from chained flatMap sendEvent calls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 06:02:48 +02:00
parent 613dfffd53
commit c2eff6d7bb
8 changed files with 221 additions and 24 deletions

View File

@@ -43,6 +43,8 @@ class AccessorPipelineBugFixTest {
variableTracer = new VariableTracer(context, constantResolver);
constantExtractor.setVariableTracer(variableTracer);
constantExtractor.setConstructorAnalyzer(constructorAnalyzer);
constructorAnalyzer.setVariableTracer(variableTracer);
constructorAnalyzer.setConstantExtractor(constantExtractor);
accessorResolver = new AccessorResolver(context, constantExtractor, constructorAnalyzer, constantResolver);
constantExtractor.setAccessorResolver(accessorResolver);
}
@@ -118,6 +120,37 @@ class AccessorPipelineBugFixTest {
assertThat(resolved).containsExactlyInAnyOrder("Code.A", "Code.B");
}
@Test
void shouldWidenToImplsEvenWhenAbstractFieldHasInlineInitializer() throws IOException {
writeJava("com/example/Code.java", """
package com.example;
public enum Code { PAY, SHIP }
""");
writeJava("com/example/BaseEvent.java", """
package com.example;
public abstract class BaseEvent {
protected Code code = Code.PAY;
public Code getCode() { return code; }
}
""");
writeJava("com/example/DefaultEvent.java", """
package com.example;
public class DefaultEvent extends BaseEvent {}
""");
writeJava("com/example/ShipEvent.java", """
package com.example;
public class ShipEvent extends BaseEvent {
public ShipEvent() { this.code = Code.SHIP; }
}
""");
scanWorkspace();
List<String> resolved = constantExtractor.resolveMethodReturnConstant(
"com.example.BaseEvent", "getCode", 0, new HashSet<>(), null);
assertThat(resolved).containsExactlyInAnyOrder("Code.PAY", "Code.SHIP");
}
}
@Nested

View File

@@ -1037,6 +1037,42 @@ class HeuristicCallGraphEngineCoreTest {
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("REACTIVE_EVENT");
}
@Test
void shouldResolveEventFromNestedReactiveFlatMap(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderController {
private StateMachine stateMachine;
public void handleReactive() {
Mono.just("IGNORED")
.flatMap(x -> Mono.just("NESTED_REACTIVE_EVENT"))
.flatMap(stateMachine::sendEvent);
}
}
class StateMachine {
public void sendEvent(String event) {}
}
class Mono {
public static Mono just(Object obj) { return new Mono(); }
public Mono flatMap(Object mapper) { return this; }
}
""";
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleReactive").build();
TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("NESTED_REACTIVE_EVENT");
}
@Test
void shouldExtractEventFromSwitchExpressionAndParentheses(@TempDir Path tempDir) throws IOException {
String source = """