Repository navigation
Re-introduce annotations elided by javac for certain cases
#1473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
b42b6fb
d602c3e
e884047
be220e7
0949a0a
38e6005
6d789a6
bcd15ee
7f4d0cc
dd306e3
a2682e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| import com.sun.tools.javac.code.Types; | ||
| import com.sun.tools.javac.tree.JCTree; | ||
| import com.sun.tools.javac.tree.TreeInfo; | ||
| import com.sun.tools.javac.util.ListBuffer; | ||
| import com.sun.tools.javac.util.Name; | ||
| import com.sun.tools.javac.util.Names; | ||
| import com.uber.nullaway.CodeAnnotationInfo; | ||
|
|
@@ -1949,6 +1950,9 @@ private Type substituteTypeArgsInGenericMethodType( | |
| Type.MethodType methodTypeAtCallSite = | ||
| castToNonNull(ASTHelpers.getType(invocationTree.getMethodSelect())).asMethodType(); | ||
| if (result instanceof InferenceSuccess successResult) { | ||
| methodTypeAtCallSite = | ||
| restoreNestedNullabilityForTypeVarArguments( | ||
| invocationTree, methodType, methodTypeAtCallSite, state); | ||
| return TypeSubstitutionUtils.updateMethodTypeWithInferredNullability( | ||
| methodTypeAtCallSite, methodType, successResult.typeVarNullability, state, config); | ||
| } else { | ||
|
|
@@ -1960,6 +1964,90 @@ private Type substituteTypeArgsInGenericMethodType( | |
| state.getTypes(), methodType, forAllType.tvars, explicitTypeArgs, config); | ||
| } | ||
|
|
||
| /** | ||
| * For some calls, javac drops nested type-use nullability annotations in inferred substitutions | ||
| * for method type variables. Recover these annotations from the corresponding actual argument | ||
| * types, while preserving one consistent top-level substitution per method type variable. | ||
| */ | ||
| @SuppressWarnings("ReferenceEquality") | ||
| private Type.MethodType restoreNestedNullabilityForTypeVarArguments( | ||
| MethodInvocationTree invocationTree, | ||
| Type.MethodType origMethodType, | ||
| Type.MethodType methodTypeAtCallSite, | ||
| VisitorState state) { | ||
| Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(invocationTree); | ||
| if (methodSymbol.isVarArgs()) { | ||
| // TODO handle varargs methods | ||
| return methodTypeAtCallSite; | ||
| } | ||
| com.sun.tools.javac.util.List<Type> origArgTypes = origMethodType.getParameterTypes(); | ||
| com.sun.tools.javac.util.List<Type> callSiteArgTypes = methodTypeAtCallSite.getParameterTypes(); | ||
| List<? extends ExpressionTree> callArgs = invocationTree.getArguments(); | ||
| if (origArgTypes.size() != callSiteArgTypes.size() || callArgs.size() != origArgTypes.size()) { | ||
| return methodTypeAtCallSite; | ||
| } | ||
|
|
||
| // use this map to store repaired substitutions for method type variables, to ensure we use the | ||
| // same repaired | ||
| // substitution for all occurrences of the same method type variable | ||
| Map<Symbol.TypeVariableSymbol, Type> repairedTopLevelSubstitutions = new HashMap<>(); | ||
| ListBuffer<Type> updatedArgTypes = new ListBuffer<>(); | ||
| boolean changed = false; | ||
| for (int i = 0; i < origArgTypes.size(); i++) { | ||
| Type updatedType = callSiteArgTypes.get(i); | ||
| Type origArgType = origArgTypes.get(i); | ||
| if (origArgType instanceof Type.TypeVar typeVar | ||
| && typeVar.tsym.owner == methodSymbol | ||
| && !(updatedType instanceof Type.TypeVar)) { | ||
| Symbol.TypeVariableSymbol typeVarSymbol = (Symbol.TypeVariableSymbol) typeVar.tsym; | ||
| Type repairedSubstitution = repairedTopLevelSubstitutions.get(typeVarSymbol); | ||
| if (repairedSubstitution != null) { | ||
| if (!state | ||
| .getTypes() | ||
| .isSameType( | ||
| state.getTypes().erasure(repairedSubstitution), | ||
| state.getTypes().erasure(updatedType))) { | ||
| // Inconsistent substitution for the same top-level type variable; bail out. | ||
| return methodTypeAtCallSite; | ||
| } | ||
| if (repairedSubstitution != updatedType) { | ||
| changed = true; | ||
| updatedType = repairedSubstitution; | ||
| } | ||
| } else { // need to compute the substitution | ||
| Type actualArgType = getTreeType(callArgs.get(i), state); | ||
| if (actualArgType != null | ||
| && !actualArgType.isRaw() | ||
| && state | ||
| .getTypes() | ||
| .isSameType( | ||
| state.getTypes().erasure(actualArgType), | ||
| state.getTypes().erasure(updatedType))) { | ||
| Type restoredType = | ||
| TypeSubstitutionUtils.restoreExplicitNullabilityAnnotations( | ||
| actualArgType, updatedType, config, Collections.emptyMap()); | ||
| repairedTopLevelSubstitutions.put(typeVarSymbol, restoredType); | ||
| if (restoredType != updatedType) { | ||
| changed = true; | ||
| updatedType = restoredType; | ||
| } | ||
| } else { | ||
| repairedTopLevelSubstitutions.put(typeVarSymbol, updatedType); | ||
| } | ||
| } | ||
| } | ||
| updatedArgTypes.append(updatedType); | ||
| } | ||
| if (!changed) { | ||
| return methodTypeAtCallSite; | ||
| } | ||
| return new Type.MethodType( | ||
| updatedArgTypes.toList(), | ||
| methodTypeAtCallSite.getReturnType(), | ||
| methodTypeAtCallSite.getThrownTypes(), | ||
| methodTypeAtCallSite.tsym); | ||
| } | ||
|
Comment on lines
+1967
to
+2061
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, locate existing usage patterns of Types.subst and TypeSubstitutionUtils.subst
rg -n "Types\.subst\(|TypeSubstitutionUtils\.subst" -S --type java | head -30Repository: uber/NullAway Length of output: 171 🏁 Script executed: #!/bin/bash
# Check the broader context of the GenericsChecks.java file to understand the pattern
rg -n "TypeSubstitutionUtils|Types.*subst" nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.javaRepository: uber/NullAway Length of output: 1390 🏁 Script executed: #!/bin/bash
# Look for usages of getTypeParameters and method type variable handling in the file
rg -n "getTypeParameters|TypeVariableSymbol" nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java -A 2 -B 2Repository: uber/NullAway Length of output: 1302 🏁 Script executed: #!/bin/bash
# Check if there are existing tests for the restoreMissingNullabilityFromSingleTopLevelTypeVarArguments method
fd -t f "Test.*\.java" nullaway | xargs grep -l "restoreMissingNullabilityFromSingleTopLevelTypeVarArguments" 2>/dev/nullRepository: uber/NullAway Length of output: 41 🏁 Script executed: #!/bin/bash
# Check the context around line 1611 to see how TypeSubstitutionUtils.subst is used
sed -n '1605,1620p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.javaRepository: uber/NullAway Length of output: 784 🏁 Script executed: #!/bin/bash
# Look at the TypeSubstitutionUtils class to understand available subst methods
grep -n "\.subst\|public.*subst" nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java | head -20Repository: uber/NullAway Length of output: 210 🏁 Script executed: #!/bin/bash
# Examine the entire method being reviewed to check if return types are mentioned
sed -n '1615,1697p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.javaRepository: uber/NullAway Length of output: 3886 🏁 Script executed: #!/bin/bash
# Find where restoreMissingNullabilityFromSingleTopLevelTypeVarArguments is called
rg -n "restoreMissingNullabilityFromSingleTopLevelTypeVarArguments" nullaway/Repository: uber/NullAway Length of output: 364 🏁 Script executed: #!/bin/bash
# Look for tests related to this method or similar return type scenarios
fd -t f ".*Test.*\.java" nullaway | xargs grep -l "restoreMissing\|return.*type.*var" 2>/dev/null | head -5Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check if there are any TODOs or comments about return type handling near this method
sed -n '1597,1700p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | grep -i "todo\|fixme\|return\|thrown"Repository: uber/NullAway Length of output: 615 Consider return/thrown type repair as a follow-up enhancement. The suggested approach—applying repaired substitutions to return/thrown types using 🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * An invocation of a generic method, and the corresponding information about its assignment | ||
| * context, for the purposes of inference. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Optional: Replace indexed access with iterator-based traversal for
com.sun.tools.javac.util.List.origArgTypes.get(i)andcallSiteArgTypes.get(i)are each O(i) on the linked-listcom.sun.tools.javac.util.List, making the loop O(n²). In practice this is negligible, but the idiomatic javac pattern (used invisitTypeListsinRestoreNullnessAnnotationsVisitor) uses parallell.tail/l.headtraversal:♻️ Proposed refactor (O(n) traversal)
🤖 Prompt for AI Agents