-
Notifications
You must be signed in to change notification settings - Fork 337
Initial inference for wildcards #1549
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 26 commits
6468f79
a69452f
93658b0
acf7a46
145780e
4dc86d5
0762977
ab188c1
d9f11aa
b3cbb66
644df50
00e94a0
1b1216b
1ea5c3b
2f2c832
e6e36a1
56d03b3
271d1d4
9378e23
0a0e73a
25f57aa
f57f949
6e84a58
5acb656
36ffec3
adb858c
272c447
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 |
|---|---|---|
|
|
@@ -5,10 +5,12 @@ | |
| import com.google.common.base.Verify; | ||
| import com.google.errorprone.VisitorState; | ||
| import com.sun.tools.javac.code.Attribute; | ||
| import com.sun.tools.javac.code.BoundKind; | ||
| import com.sun.tools.javac.code.Symbol; | ||
| import com.sun.tools.javac.code.Type; | ||
| import com.sun.tools.javac.code.Type.ClassType; | ||
| import com.sun.tools.javac.code.Type.TypeVar; | ||
| import com.sun.tools.javac.code.Type.WildcardType; | ||
| import com.sun.tools.javac.code.Types; | ||
| import com.uber.nullaway.CodeAnnotationInfo; | ||
| import com.uber.nullaway.Config; | ||
|
|
@@ -90,7 +92,15 @@ class AddSubtypeConstraintsVisitor extends Types.DefaultTypeVisitor<@Nullable Vo | |
|
|
||
| @Override | ||
| public @Nullable Void visitType(Type subtype, Type supertype) { | ||
| // handle flow into a type variable. the check for !(subtype instanceof TypeVar) is a | ||
| if (config.handleWildcardGenerics()) { | ||
| WildcardType supertypeWildcard = GenericsUtils.asWildcard(supertype); | ||
| if (supertypeWildcard != null) { | ||
| Verify.verify(!localVariableType, "A local variable should not have a wildcard type"); | ||
| constrainSubtypeToWildcard(subtype, supertypeWildcard); | ||
| return null; | ||
| } | ||
| } | ||
| // handle flow into a type variable. The check for !(subtype instanceof TypeVar) is a | ||
| // small optimization, as that case should be handled in visitTypeVar. | ||
| if (!localVariableType && (supertype instanceof TypeVar) && !(subtype instanceof TypeVar)) { | ||
| directlyConstrainTypePair(subtype, supertype); | ||
|
|
@@ -116,17 +126,13 @@ class AddSubtypeConstraintsVisitor extends Types.DefaultTypeVisitor<@Nullable Vo | |
| int numTypeArgs = supertypeTypeArguments.size(); | ||
| Verify.verify(numTypeArgs == subtypeTypeArguments.size()); | ||
| for (int i = 0; i < numTypeArgs; i++) { | ||
| Type rhsTypeArg = supertypeTypeArguments.get(i); | ||
| Type lhsTypeArg = subtypeTypeArguments.get(i); | ||
| // constrain in both directions | ||
| // TODO should we have a more optimized way to equate two types? this just makes each | ||
| // type a subtype of the other | ||
| lhsTypeArg.accept(this, rhsTypeArg); | ||
| rhsTypeArg.accept(this, lhsTypeArg); | ||
| Type supertypeTypeArg = supertypeTypeArguments.get(i); | ||
| Type subtypeTypeArg = subtypeTypeArguments.get(i); | ||
| constrainTypeArgumentContainment(subtypeTypeArg, supertypeTypeArg); | ||
| } | ||
| } | ||
| // if supertype is not a ClassType, we still call visitType to handle the case where | ||
| // supertype is a TypeVar | ||
| // supertype is a TypeVar or a wildcard | ||
| return visitType(subtype, supertype); | ||
| } | ||
|
|
||
|
|
@@ -141,7 +147,7 @@ class AddSubtypeConstraintsVisitor extends Types.DefaultTypeVisitor<@Nullable Vo | |
| subtypeComponentType.accept(this, superComponentType); | ||
| } | ||
| // if supertype is not an ArrayType, we still call visitType to handle the case where | ||
| // supertype is a TypeVar | ||
| // supertype is a TypeVar or a wildcard | ||
| return visitType(subtype, supertype); | ||
| } | ||
|
|
||
|
|
@@ -152,6 +158,117 @@ class AddSubtypeConstraintsVisitor extends Types.DefaultTypeVisitor<@Nullable Vo | |
| } | ||
| return visitType(subtype, supertype); | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable Void visitWildcardType(WildcardType subtype, Type supertype) { | ||
| if (config.handleWildcardGenerics()) { | ||
| Verify.verify(!localVariableType, "A wildcard type cannot be assigned to a local variable"); | ||
| constrainWildcardToSupertype(subtype, supertype); | ||
| } | ||
| return null; | ||
| } | ||
|
msridhar marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Adds nullability constraints for containment of one type argument by another during generic | ||
| * class/interface subtyping. For non-wildcard arguments, NullAway requires identical | ||
| * nullability. When either side is a wildcard, containment is reduced to constraints between | ||
| * the wildcard bound and the opposing argument. | ||
| */ | ||
| private void constrainTypeArgumentContainment(Type subtypeTypeArg, Type supertypeTypeArg) { | ||
| if (!config.handleWildcardGenerics()) { | ||
| equateTypeArguments(subtypeTypeArg, supertypeTypeArg); | ||
| return; | ||
| } | ||
| WildcardType supertypeWildcard = GenericsUtils.asWildcard(supertypeTypeArg); | ||
| if (supertypeWildcard != null) { | ||
| constrainContainedByWildcard(subtypeTypeArg, supertypeWildcard); | ||
| return; | ||
| } | ||
| WildcardType subtypeWildcard = GenericsUtils.asWildcard(subtypeTypeArg); | ||
| if (subtypeWildcard != null) { | ||
| constrainWildcardContainedByConcrete(subtypeWildcard, supertypeTypeArg); | ||
|
msridhar marked this conversation as resolved.
Outdated
|
||
| return; | ||
| } | ||
| equateTypeArguments(subtypeTypeArg, supertypeTypeArg); | ||
| } | ||
|
|
||
| private void equateTypeArguments(Type subtypeTypeArg, Type supertypeTypeArg) { | ||
| // constrain in both directions | ||
| // TODO should we have a more optimized way to equate two types? this just makes each | ||
| // type a subtype of the other | ||
| subtypeTypeArg.accept(this, supertypeTypeArg); | ||
| supertypeTypeArg.accept(this, subtypeTypeArg); | ||
| } | ||
|
|
||
| /** | ||
| * Adds constraints for type-argument containment where the formal argument is a wildcard. For | ||
| * {@code ? extends S} and {@code ?}, containment requires the actual argument's effective upper | ||
| * bound to be a subtype of {@code S}. For {@code ? super S}, concrete actual arguments require | ||
| * {@code S <: subtypeTypeArg}; {@code ? super T} actual arguments require {@code S <: T}. Other | ||
| * actual wildcard forms place no useful nullability constraint. | ||
| */ | ||
| private void constrainContainedByWildcard(Type subtypeTypeArg, WildcardType supertypeWildcard) { | ||
| switch (supertypeWildcard.kind) { | ||
| case UNBOUND, EXTENDS -> { | ||
| Type subtypeUpperBound = GenericsUtils.effectiveWildcardUpperBound(subtypeTypeArg, state); | ||
| subtypeUpperBound.accept( | ||
| this, GenericsUtils.wildcardUpperBound(supertypeWildcard, state)); | ||
| } | ||
| case SUPER -> { | ||
| Type supertypeLowerBound = castToNonNull(supertypeWildcard.getSuperBound()); | ||
| WildcardType subtypeWildcard = GenericsUtils.asWildcard(subtypeTypeArg); | ||
| if (subtypeWildcard != null) { | ||
| if (subtypeWildcard.kind == BoundKind.SUPER) { | ||
| supertypeLowerBound.accept(this, castToNonNull(subtypeWildcard.getSuperBound())); | ||
| } | ||
| // the subtype wildcard could have an extends bound, but as far as I know we do not | ||
| // need to generate constraints for this case | ||
| // TODO revisit if needed | ||
| } else { | ||
| supertypeLowerBound.accept(this, subtypeTypeArg); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Adds constraints for type-argument containment where the actual argument is a wildcard and | ||
| * the formal argument is concrete. For {@code ? extends S} and {@code ?}, containment requires | ||
| * {@code S <: supertypeTypeArg}. For {@code ? super S}, use the lower bound and require {@code | ||
| * S <: supertypeTypeArg}. | ||
| */ | ||
| private void constrainWildcardContainedByConcrete( | ||
|
Collaborator
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. This method and
Collaborator
Author
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. Good catch! I kept |
||
| WildcardType subtypeWildcard, Type supertypeTypeArg) { | ||
| if (subtypeWildcard.kind == BoundKind.SUPER) { | ||
| castToNonNull(subtypeWildcard.getSuperBound()).accept(this, supertypeTypeArg); | ||
| } else { | ||
| GenericsUtils.wildcardUpperBound(subtypeWildcard, state).accept(this, supertypeTypeArg); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Adds constraints for a top-level subtype relation {@code subtype <: supertypeWildcard}. For | ||
| * {@code ? extends S} and {@code ?}, this reduces to {@code subtype <: S}. A {@code ? super S} | ||
| * supertype places no useful nullability constraint on {@code subtype}. | ||
| */ | ||
| private void constrainSubtypeToWildcard(Type subtype, WildcardType supertypeWildcard) { | ||
| if (supertypeWildcard.kind != BoundKind.SUPER) { | ||
| subtype.accept(this, GenericsUtils.wildcardUpperBound(supertypeWildcard, state)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Adds constraints for a top-level subtype relation {@code subtypeWildcard <: supertype}. For | ||
| * {@code ? extends S} and {@code ?}, this reduces to {@code S <: supertype}. For {@code ? super | ||
| * S}, use the lower bound and reduce to {@code S <: supertype}. | ||
| */ | ||
| private void constrainWildcardToSupertype(WildcardType subtypeWildcard, Type supertype) { | ||
| if (subtypeWildcard.kind == BoundKind.SUPER) { | ||
| castToNonNull(subtypeWildcard.getSuperBound()).accept(this, supertype); | ||
| } else { | ||
| GenericsUtils.wildcardUpperBound(subtypeWildcard, state).accept(this, supertype); | ||
| } | ||
| } | ||
|
msridhar marked this conversation as resolved.
|
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -272,10 +389,9 @@ private VarState getState(Element typeVarElement) { | |
|
|
||
| private boolean isTypeVariable(Type t) { | ||
| if (t instanceof TypeVar tv) { | ||
| // For now ignore capture variables, like "capture#1 of ? extends X". Also, only treat as a | ||
| // type variable if it _doesn't_ have an explicit @Nullable or @NonNull annotation. | ||
| return !tv.isCaptured() | ||
| && !Nullness.hasNullableAnnotation(tv.getAnnotationMirrors().stream(), config) | ||
| // Only treat as a type variable if it _doesn't_ have an explicit @Nullable or @NonNull | ||
| // annotation. | ||
| return !Nullness.hasNullableAnnotation(tv.getAnnotationMirrors().stream(), config) | ||
| && !Nullness.hasNonNullAnnotation(tv.getAnnotationMirrors().stream(), config); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } else { | ||
| return false; | ||
|
|
@@ -298,19 +414,19 @@ private boolean upperBoundIsNullable(Element typeVarElement) { | |
| } | ||
| // first, check if library model overrides the upper bound nullability | ||
| Element enclosingElement = typeVarElement.getEnclosingElement(); | ||
| if (enclosingElement instanceof Symbol.MethodSymbol methodSymbol) { | ||
| int typeVarIndex = | ||
| methodSymbol.getTypeParameters().indexOf((Symbol.TypeVariableSymbol) typeVarElement); | ||
| if (enclosingElement instanceof Symbol.MethodSymbol methodSymbol | ||
| && typeVarElement instanceof Symbol.TypeVariableSymbol typeVariableSymbol) { | ||
| int typeVarIndex = methodSymbol.getTypeParameters().indexOf(typeVariableSymbol); | ||
| // TODO typeVarIndex is -1 in some cases; see test | ||
| // com.uber.nullaway.jspecify.GenericMethodTests.instanceGenericMethodWithMethodRefArgument. | ||
| // Investigate further. | ||
| if (typeVarIndex >= 0 | ||
| && handler.onOverrideMethodTypeVariableUpperBound(methodSymbol, typeVarIndex, state)) { | ||
| return true; | ||
| } | ||
| } else if (enclosingElement instanceof Symbol.ClassSymbol classSymbol) { | ||
| int typeVarIndex = | ||
| classSymbol.getTypeParameters().indexOf((Symbol.TypeVariableSymbol) typeVarElement); | ||
| } else if (enclosingElement instanceof Symbol.ClassSymbol classSymbol | ||
| && typeVarElement instanceof Symbol.TypeVariableSymbol typeVariableSymbol) { | ||
| int typeVarIndex = classSymbol.getTypeParameters().indexOf(typeVariableSymbol); | ||
| if (typeVarIndex >= 0 | ||
| && handler.onOverrideClassTypeVariableUpperBound(classSymbol.toString(), typeVarIndex)) { | ||
| return true; | ||
|
|
@@ -324,8 +440,11 @@ private boolean upperBoundIsNullable(Element typeVarElement) { | |
| } | ||
|
|
||
| private boolean fromUnannotatedMethod(Element typeVarElement) { | ||
| Symbol enclosingElement = (Symbol) typeVarElement.getEnclosingElement(); | ||
| return enclosingElement != null | ||
| && codeAnnotationInfo.isSymbolUnannotated(enclosingElement, config, handler); | ||
| Element enclosingElement = typeVarElement.getEnclosingElement(); | ||
| if (!(enclosingElement instanceof Symbol.MethodSymbol) | ||
| && !(enclosingElement instanceof Symbol.ClassSymbol)) { | ||
| return false; | ||
| } | ||
| return codeAnnotationInfo.isSymbolUnannotated((Symbol) enclosingElement, config, handler); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,17 @@ | |
| import com.google.errorprone.VisitorState; | ||
| import com.google.errorprone.util.ASTHelpers; | ||
| import com.sun.source.tree.MemberReferenceTree; | ||
| import com.sun.tools.javac.code.BoundKind; | ||
| import com.sun.tools.javac.code.Symbol; | ||
| import com.sun.tools.javac.code.Symtab; | ||
| import com.sun.tools.javac.code.Type; | ||
| import com.sun.tools.javac.code.Type.CapturedType; | ||
| import com.sun.tools.javac.code.Type.WildcardType; | ||
| import com.sun.tools.javac.code.Types; | ||
| import com.sun.tools.javac.tree.JCTree; | ||
| import com.uber.nullaway.NullabilityUtil; | ||
| import javax.lang.model.type.TypeKind; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| /** Utility methods for doing generics-related checking */ | ||
| public class GenericsUtils { | ||
|
|
@@ -22,6 +27,49 @@ enum MethodRefTypeRelationKind { | |
| PARAMETER | ||
| } | ||
|
|
||
| /** | ||
| * Returns the effective upper bound of {@code typeArg}. For concrete type arguments, returns the | ||
| * type itself. For wildcards and captured wildcards, returns the wildcard's upper bound, | ||
| * recursing through nested wildcards and captures produced by javac. | ||
| */ | ||
| static Type effectiveWildcardUpperBound(Type typeArg, VisitorState state) { | ||
| WildcardType wildcardType = asWildcard(typeArg); | ||
| return wildcardType == null ? typeArg : wildcardUpperBound(wildcardType, state); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the effective upper bound of a wildcard, using the corresponding type variable's upper | ||
| * bound for unbounded wildcards and {@code super} wildcards. | ||
| */ | ||
| static Type wildcardUpperBound(WildcardType wildcardType, VisitorState state) { | ||
| Type upperBound; | ||
| if (wildcardType.kind == BoundKind.EXTENDS) { | ||
| upperBound = wildcardType.getExtendsBound(); | ||
| } else { | ||
| upperBound = | ||
| wildcardType.bound == null | ||
| ? Symtab.instance(state.context).objectType | ||
| : wildcardType.bound.getUpperBound(); | ||
|
Collaborator
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. Does this API not work with
Collaborator
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. There are some docs from the previous version in
Collaborator
Author
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. I've added some comments to explain here. The confusing thing is that |
||
| } | ||
| if (upperBound instanceof WildcardType nestedWildcard) { | ||
| return wildcardUpperBound(nestedWildcard, state); | ||
| } | ||
| if (upperBound instanceof CapturedType capturedType && capturedType.wildcard != null) { | ||
| return wildcardUpperBound(capturedType.wildcard, state); | ||
| } | ||
| return upperBound; | ||
| } | ||
|
|
||
| static @Nullable WildcardType asWildcard(Type typeArg) { | ||
| if (typeArg instanceof WildcardType wildcardType) { | ||
| return wildcardType; | ||
| } | ||
| if (typeArg instanceof CapturedType capturedType) { | ||
| return capturedType.wildcard; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Handler for method reference type relations, used by {{@link | ||
| * #processMethodRefTypeRelations(GenericsChecks, Type, MemberReferenceTree, VisitorState, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -293,13 +293,15 @@ public Type visitMethodType(Type.MethodType t, Type other) { | |
|
|
||
| @Override | ||
| public Type visitClassType(Type.ClassType t, Type other) { | ||
| if (other instanceof Type.WildcardType wt && wt.kind == BoundKind.EXTENDS) { | ||
| // As a temporary measure, we restore nullability annotations from the upper bound of the | ||
| // wildcard. | ||
| // TODO revisit this decision when we add fuller support for inference and wildcards. | ||
| return visit(t, wt.getExtendsBound()); | ||
| if (other instanceof Type.WildcardType wt) { | ||
| // When the other type is a wildcard, restore nullability annotations from the bound that | ||
| // determines the functional interface type. | ||
| if (wt.kind == BoundKind.EXTENDS) { | ||
| return visit(t, wt.getExtendsBound()); | ||
| } else if (wt.kind == BoundKind.SUPER) { | ||
| return visit(t, wt.getSuperBound()); | ||
| } | ||
| } | ||
|
Comment on lines
+296
to
304
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
# Check if there are test cases covering UNBOUND wildcards flowing through this visitor
rg -n --type=java 'visitClassType.*Wildcard|UNBOUND' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
rg -n --type=java '\<\?\>' nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java | head -20Repository: uber/NullAway Length of output: 130 🏁 Script executed: #!/bin/bash
# Read the TypeSubstitutionUtils.java file to see the full context around lines 296-304
wc -l nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 138 🏁 Script executed: #!/bin/bash
# Read the relevant section of TypeSubstitutionUtils.java
sed -n '290,315p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 1129 🏁 Script executed: #!/bin/bash
# Find GenericsUtils.java and check the wildcardUpperBound method
fd -n GenericsUtils.java nullaway/src/main/javaRepository: uber/NullAway Length of output: 288 🏁 Script executed: #!/bin/bash
# Search for UNBOUND keyword in the codebase
rg -n "BoundKind.UNBOUND" nullaway/src/main/java/Repository: uber/NullAway Length of output: 187 🏁 Script executed: #!/bin/bash
# Search for test cases related to wildcards
fd -n "WildcardTests.java" nullaway/src/test/javaRepository: uber/NullAway Length of output: 288 🏁 Script executed: #!/bin/bash
# Find GenericsUtils.java
find nullaway/src/main/java -name "GenericsUtils.java"Repository: uber/NullAway Length of output: 126 🏁 Script executed: #!/bin/bash
# Find WildcardTests.java
find nullaway/src/test/java -name "WildcardTests.java"Repository: uber/NullAway Length of output: 126 🏁 Script executed: #!/bin/bash
# Search for wildcardUpperBound method
rg -n "wildcardUpperBound" nullaway/src/main/java/Repository: uber/NullAway Length of output: 1483 🏁 Script executed: #!/bin/bash
# Search for how UNBOUND wildcards are handled in generics code
rg -n "BoundKind\|UNBOUND" nullaway/src/main/java/com/uber/nullaway/generics/ -A 2 -B 2Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Read the GenericsUtils.java file to understand wildcardUpperBound
cat -n nullaway/src/main/java/com/uber/nullaway/generics/GenericsUtils.java | head -80Repository: uber/NullAway Length of output: 3711 🏁 Script executed: #!/bin/bash
# Search for UNBOUND handling patterns
rg -n "UNBOUND" nullaway/src/main/java/com/uber/nullaway/generics/Repository: uber/NullAway Length of output: 425 🏁 Script executed: #!/bin/bash
# Check WildcardTests.java for test coverage of UNBOUND wildcards
cat -n nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java | head -100Repository: uber/NullAway Length of output: 4631 🏁 Script executed: #!/bin/bash
# Search for similar wildcard handling patterns in other visitors
rg -n "instanceof.*WildcardType" nullaway/src/main/java/com/uber/nullaway/generics/ -A 5 -B 1Repository: uber/NullAway Length of output: 5547 🏁 Script executed: #!/bin/bash
# Check if visitClassType is called with UNBOUND wildcards in practice
rg -n "visitClassType" nullaway/src/main/java/com/uber/nullaway/generics/ -B 2 -A 2Repository: uber/NullAway Length of output: 3135 🏁 Script executed: #!/bin/bash
# Read more of TypeSubstitutionUtils.java to understand the context and purpose
sed -n '260,320p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 2698 🏁 Script executed: #!/bin/bash
# Search for test cases with unbounded wildcards (just "?")
rg -n '\?\>' nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.javaRepository: uber/NullAway Length of output: 130 🏁 Script executed: #!/bin/bash
# Check if there are any tests with super wildcards
rg -n '\? super' nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.javaRepository: uber/NullAway Length of output: 2881 🏁 Script executed: #!/bin/bash
# Search for any existing UNBOUND handling or comments about it
rg -n "UNBOUND" nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check if bound field can be null for WildcardType
rg -n "wildcardType.bound|wt.bound" nullaway/src/main/java/com/uber/nullaway/generics/ -B 2 -A 2Repository: uber/NullAway Length of output: 674 🏁 Script executed: #!/bin/bash
# Look at the visitWildcardType method in TypeSubstitutionUtils to see how it handles wildcards
sed -n '321,345p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 830 🏁 Script executed: #!/bin/bash
# Check if there are any comments or documentation about why UNBOUND is not handled
sed -n '290,310p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java | cat -nRepository: uber/NullAway Length of output: 1046 🏁 Script executed: #!/bin/bash
# Verify if wt.bound can be null and check the null handling in GenericsUtils pattern
rg -n "bound == null|bound != null" nullaway/src/main/java/com/uber/nullaway/generics/GenericsUtils.javaRepository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check if there are any other visitors that handle UNBOUND explicitly
rg -n "BoundKind.UNBOUND" nullaway/src/main/java/com/uber/nullaway/generics/ -B 2 -A 2Repository: uber/NullAway Length of output: 668 🏁 Script executed: #!/bin/bash
# Check if there are tests for unbounded wildcards by looking for patterns like Foo<?>
rg -n 'Foo<.*>' nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java | grep -v extends | grep -v super | head -20Repository: uber/NullAway Length of output: 1458 🏁 Script executed: #!/bin/bash
# Check if the code actually processes UNBOUND wildcards and what happens
rg -n "RestoreNullnessAnnotationsVisitor\|visitClassType" nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java -B 5 -A 15 | head -60Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Verify what updateDirectNullabilityAnnotationsForType does with WildcardType
rg -n "updateDirectNullabilityAnnotationsForType" nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java -A 5 | head -40Repository: uber/NullAway Length of output: 1206 🏁 Script executed: #!/bin/bash
# Read the updateDirectNullabilityAnnotationsForType method fully
sed -n '362,390p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 1261 🏁 Script executed: #!/bin/bash
# Check the git history or comments around this code
git log --oneline -20 -- nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 89 🏁 Script executed: #!/bin/bash
# Look for any test that specifically checks UNBOUND wildcard behavior
rg -n "unboundedFoo\|Foo<\?>" nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java -B 3 -A 8Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check what the actual test expectations are for unbounded wildcards
sed -n '270,320p' nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.javaRepository: uber/NullAway Length of output: 2626 🏁 Script executed: #!/bin/bash
# Look at the context around line 275 test with unbounded foo
rg -n "unboundedFoo" nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java -B 5 -A 15Repository: uber/NullAway Length of output: 1671 🏁 Script executed: #!/bin/bash
# Check if bound field is populated for UNBOUND wildcards
rg -n "\.bound\s*=" nullaway/src/main/java/com/uber/nullaway/generics/ -B 2 -A 2Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check the commit context for the RestoreNullnessAnnotationsVisitor code
git show 272c447 --statRepository: uber/NullAway Length of output: 27203 🏁 Script executed: #!/bin/bash
# Search for usages of RestoreNullnessAnnotationsVisitor to understand when it's invoked
rg -n "RestoreNullnessAnnotationsVisitor\|new RestoreNullnessAnnotationsVisitor" nullaway/src/main/java/ -B 2 -A 2Repository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Check if there's a way for UNBOUND to be processed through the formal type parameter bound
rg -n "wt.bound\|wildcardType.bound" nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 39 🏁 Script executed: #!/bin/bash
# Look at the broader context of how RestoreNullnessAnnotationsVisitor is used
rg -n "new RestoreNullnessAnnotationsVisitor\|RestoreNullnessAnnotationsVisitor(" nullaway/src/main/java/com/uber/nullaway/generics/ -B 3 -A 3Repository: uber/NullAway Length of output: 254 🏁 Script executed: #!/bin/bash
# Look at the broader context of how RestoreNullnessAnnotationsVisitor is used
rg -n "RestoreNullnessAnnotationsVisitor" nullaway/src/main/java/com/uber/nullaway/generics/ -B 3 -A 3Repository: uber/NullAway Length of output: 2508 🏁 Script executed: #!/bin/bash
# Check what happens in the UNBOUND case - does it fall through intentionally or by omission?
# Look for any comments or documentation about wildcard handling
sed -n '290,340p' nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaRepository: uber/NullAway Length of output: 1782 Handle UNBOUND wildcards by extracting the formal type parameter's upper bound. The code explicitly extracts bounds for For UNBOUND wildcards, consider handling the case explicitly: Suggested approach if (other instanceof Type.WildcardType wt) {
// When the other type is a wildcard, restore nullability annotations from the bound that
// determines the functional interface type.
if (wt.kind == BoundKind.EXTENDS) {
return visit(t, wt.getExtendsBound());
} else if (wt.kind == BoundKind.SUPER) {
return visit(t, wt.getSuperBound());
+ } else if (wt.kind == BoundKind.UNBOUND && wt.bound != null) {
+ return visit(t, wt.bound.getUpperBound());
+ }
}🤖 Prompt for AI Agents
Collaborator
Author
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. There cannot be explicit nullability annotations for the unbound case
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.
✏️ Learnings added
🧠 Learnings used |
||
|
|
||
| Type updated = updateDirectNullabilityAnnotationsForType(t, other); | ||
| if (!(other instanceof Type.ClassType)) { | ||
| return updated; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.