diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java index 5127cf2acd..8b4d06b202 100644 --- a/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java @@ -1756,28 +1756,35 @@ public Nullness getGenericMethodReturnTypeNullness( private @Nullable Type getTypeForSymbol(Symbol symbol, VisitorState state) { if (symbol.isAnonymous()) { // For anonymous classes, symbol.type does not contain annotations on generic type parameters. - // So, we get a correct type from the enclosing NewClassTree representing the anonymous class. + // So, we get a correct type from the NewClassTree representing the anonymous class. + // The nearest enclosing NewClassTree on the current path may be some other constructor call, + // such as when `this` from an anonymous class is passed as an argument to a constructor. TreePath path = state.getPath(); - path = - castToNonNull(ASTHelpers.findPathFromEnclosingNodeToTopLevel(path, NewClassTree.class)); - NewClassTree newClassTree = (NewClassTree) path.getLeaf(); - if (newClassTree.getClassBody() == null) { - throw new RuntimeException( - "method should be directly inside an anonymous NewClassTree " - + state.getSourceForNode(path.getLeaf())); - } - Type typeFromTree = getTreeType(newClassTree, state); - if (typeFromTree != null) { - verify( - state.getTypes().isAssignable(symbol.type, typeFromTree), - "%s is not assignable to %s", - symbol.type, - typeFromTree); + while (path != null) { + if (path.getLeaf() instanceof NewClassTree newClassTree + && newClassTree.getClassBody() != null) { + Type newClassType = ASTHelpers.getType(newClassTree); + if (newClassType != null && newClassType.tsym.equals(symbol)) { + Type typeFromTree = getTreeType(newClassTree, state); + if (typeFromTree != null) { + verify( + state.getTypes().isAssignable(symbol.type, typeFromTree), + "%s is not assignable to %s", + symbol.type, + typeFromTree); + } + return typeFromTree; + } + } + path = path.getParentPath(); } - return typeFromTree; - } else { - return symbol.type; + throw new RuntimeException( + "could not find anonymous NewClassTree for symbol " + + symbol + + " from path " + + state.getSourceForNode(state.getPath().getLeaf())); } + return symbol.type; } public Nullness getGenericMethodReturnTypeNullness( diff --git a/nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java index a669d657a9..595ea0ed50 100644 --- a/nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java @@ -1653,6 +1653,32 @@ interface TestInterface { .doTest(); } + @Test + public void constructorCallWithThisFromAnonymousClass() { + makeHelper() + .addSourceLines( + "Test.java", + """ + package com.uber; + import org.jspecify.annotations.NullMarked; + @NullMarked + class Test { + static class TakesRunnable { + TakesRunnable(Runnable runnable) {} + } + void repro() { + new Runnable() { + @Override + public void run() { + new TakesRunnable(this); + } + }; + } + } + """) + .doTest(); + } + @Test public void explicitlyTypedAnonymousClassAsReceiver() { makeHelper()