-
Notifications
You must be signed in to change notification settings - Fork 337
Handle nested restrictive annotations on type variables in @NullUnmarked code #1510
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
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 |
|---|---|---|
|
|
@@ -1438,19 +1438,25 @@ public void checkTypeParameterNullnessForFunctionReturnType( | |
| * @param state the visitor state | ||
| */ | ||
| private boolean identicalTypeParameterNullability( | ||
| Type lhsType, Type rhsType, VisitorState state) { | ||
| return lhsType.accept(new CheckIdenticalNullabilityVisitor(state, this, config), rhsType); | ||
| Type lhsType, Type rhsType, VisitorState state, boolean isMethodUnannotated) { | ||
| return lhsType.accept( | ||
| new CheckIdenticalNullabilityVisitor(state, this, config, isMethodUnannotated), rhsType); | ||
| } | ||
|
|
||
| /** | ||
| * Like {@link #identicalTypeParameterNullability(Type, Type, VisitorState)}, but allows for | ||
| * covariant array subtyping at the top level. | ||
| * Like {@link #identicalTypeParameterNullability(Type, Type, VisitorState, boolean)}, but allows | ||
| * for covariant array subtyping at the top level. | ||
| * | ||
| * @param lhsType type for the lhs of the assignment | ||
| * @param rhsType type for the rhs of the assignment | ||
| * @param state the visitor state | ||
| */ | ||
| private boolean subtypeParameterNullability(Type lhsType, Type rhsType, VisitorState state) { | ||
| return subtypeParameterNullability(lhsType, rhsType, state, false); | ||
| } | ||
|
|
||
| private boolean subtypeParameterNullability( | ||
| Type lhsType, Type rhsType, VisitorState state, boolean isMethodUnannotated) { | ||
| if (lhsType.isRaw()) { | ||
| return true; | ||
| } | ||
|
|
@@ -1466,11 +1472,19 @@ private boolean subtypeParameterNullability(Type lhsType, Type rhsType, VisitorS | |
| boolean isRHSNullableAnnotated = isNullableAnnotated(rhsComponentType); | ||
| // an array of @Nullable references is _not_ a subtype of an array of @NonNull references | ||
| if (isRHSNullableAnnotated && !isLHSNullableAnnotated) { | ||
| return false; | ||
| // In @NullUnmarked code, skip if the LHS component has no explicit nullness annotation | ||
| if (isMethodUnannotated | ||
| && !Nullness.hasNonNullAnnotation( | ||
| lhsComponentType.getAnnotationMirrors().stream(), config)) { | ||
| // unannotated component in @NullUnmarked context; fall through to nested check | ||
| } else { | ||
| return false; | ||
| } | ||
| } | ||
| return identicalTypeParameterNullability(lhsComponentType, rhsComponentType, state); | ||
| return identicalTypeParameterNullability( | ||
| lhsComponentType, rhsComponentType, state, isMethodUnannotated); | ||
| } else { | ||
| return identicalTypeParameterNullability(lhsType, rhsType, state); | ||
| return identicalTypeParameterNullability(lhsType, rhsType, state, isMethodUnannotated); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1553,9 +1567,12 @@ public void checkTypeParameterNullnessForConditionalExpression( | |
| * @param methodSymbol the symbol for the method being called | ||
| * @param tree the tree representing the method call | ||
| * @param state the visitor state | ||
| * @param isMethodAnnotated whether the called method is in annotated (non-{@code @NullUnmarked}) | ||
| * code. When {@code false}, only restrictive (explicit) nullness annotations on type | ||
| * parameters are enforced. | ||
| */ | ||
| public void compareGenericTypeParameterNullabilityForCall( | ||
| Symbol.MethodSymbol methodSymbol, Tree tree, VisitorState state) { | ||
| Symbol.MethodSymbol methodSymbol, Tree tree, VisitorState state, boolean isMethodAnnotated) { | ||
| Config config = analysis.getConfig(); | ||
| if (!config.isJSpecifyMode()) { | ||
| return; | ||
|
|
@@ -1602,13 +1619,15 @@ public void compareGenericTypeParameterNullabilityForCall( | |
| // the type of the method reference tree provided by javac may not capture | ||
| // nullability of nested types. So, do explicit type checks based on the return and | ||
| // parameter types of the referenced method | ||
| boolean isUnannotated = !isMethodAnnotated; | ||
| GenericsUtils.processMethodRefTypeRelations( | ||
| this, | ||
| formalParameter, | ||
| memberReferenceTree, | ||
| state, | ||
| (subtype, supertype, relationKind) -> { | ||
| if (!subtypeParameterNullability(supertype, subtype, state)) { | ||
| if (!subtypeParameterNullability( | ||
| supertype, subtype, state, isUnannotated)) { | ||
| if (relationKind == MethodRefTypeRelationKind.RETURN) { | ||
| reportInvalidMethodReferenceReturnTypeError( | ||
| memberReferenceTree, supertype, subtype, state); | ||
|
|
@@ -1646,7 +1665,8 @@ public void compareGenericTypeParameterNullabilityForCall( | |
| false, | ||
| false); | ||
| } | ||
| if (!subtypeParameterNullability(formalParameter, actualParameterType, state)) { | ||
| if (!subtypeParameterNullability( | ||
| formalParameter, actualParameterType, state, !isMethodAnnotated)) { | ||
| reportInvalidParametersNullabilityError( | ||
| formalParameter, actualParameterType, currentActualParam, state); | ||
| } | ||
|
|
@@ -1929,8 +1949,12 @@ private Type substituteTypeArgsInGenericMethodType( | |
| return TypeSubstitutionUtils.updateMethodTypeWithInferredNullability( | ||
| methodTypeAtCallSite, methodType, successResult.typeVarNullability, state, config); | ||
| } else { | ||
| // inference failed; just return the method type at the call site with no substitutions | ||
| return methodTypeAtCallSite; | ||
| // inference failed; restore explicit nullability annotations from the original method | ||
| // type to the call-site type, so restrictive annotations (e.g. @NonNull on type | ||
| // arguments) are still enforced | ||
| return TypeSubstitutionUtils.restoreExplicitNullabilityAnnotations( | ||
| methodType, methodTypeAtCallSite, config, Collections.emptyMap()) | ||
| .asMethodType(); | ||
|
Comment on lines
+1952
to
+1957
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 Add a regression for the inference-failure fallback. This branch now preserves explicit restrictive annotations after solver failure, but the PR’s listed tests do not appear to exercise an 🤖 Prompt for AI Agents
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. Agreed that a dedicated inference-failure test would be ideal here. However, constructing a minimal unsatisfiable-inference scenario that reliably triggers InferenceFailure (rather than the solver just picking a fallback) is non-trivial and somewhat fragile since it depends on internal solver behavior. The existing test I will look into adding a targeted inference-failure test as a follow-up if I can find a reliable way to trigger that path. For now, the change is safe because
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.
One way to reliably hit the If you'd like, I can open a follow-up issue to track adding that regression test. Just let me know! 🧠 Learnings used
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. @officialasishkumar can you say more on why you can't write a test for this? It should be easy to reliably trigger an inference failure.
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.
|
||
| } | ||
| } | ||
| return TypeSubstitutionUtils.subst( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.