eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
156 stars 123 forks source link

Null secondary annotations not recognized anymore #2734

Open blablubbabc opened 1 month ago

blablubbabc commented 1 month ago

I don't have a minimal reproduction project available currently, but a description of my situation is as follows:

  public interface TabCompleter {
    @Nullable
    public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args);
}

Issue: The @Nullable in my signature is flagged in Eclipse with error The return type is incompatible with '@NonNull List<@NonNull String>' returned from TabCompleter.onTabComplete(CommandSender, Command, String, String[]) (mismatching null constraints)

Eclipse allows me to mark the method as NonNull, but then nobviously no longer allows the supposedly valid null return value.

Interestingly:

It behaves as if the secondary Nullable annotations are not recognized.

jdt prefs:

eclipse.preferences.version=1
org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=error
org.eclipse.jdt.core.compiler.annotation.nonnull=org.checkerframework.checker.nullness.qual.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=org.jetbrains.annotations.NotNull,org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=org.checkerframework.framework.qual.DefaultQualifier
org.eclipse.jdt.core.compiler.annotation.notowning=org.eclipse.jdt.annotation.NotOwning
org.eclipse.jdt.core.compiler.annotation.nullable=org.checkerframework.checker.nullness.qual.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=org.jetbrains.annotations.Nullable,org.eclipse.jdt.annotation.Nullable,org.checkerframework.checker.nullness.qual.PolyNull
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
org.eclipse.jdt.core.compiler.annotation.owning=org.eclipse.jdt.annotation.Owning
org.eclipse.jdt.core.compiler.annotation.resourceanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=error
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompatibleOwningContract=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.insufficientResourceAnalysis=warning
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=info
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=enabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=ignore
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=error
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=error
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=error
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=error
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=error
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=info
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=21
stephan-herrmann commented 1 month ago

Quick question: do all sets of null annotations share the same @Target definition? Seeing checkerframework in the picture, you probably need all annotations to specify a target of TYPE_USE. Is that given?

blablubbabc commented 1 month ago

The external project in question seems to use an older version of the jetbrains annotations (version org.jetbrains:annotations-java5:24.1.0), which don't seem to have the target for TYPE_USE: @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}). The current version seems to include that target: https://github.com/JetBrains/java-annotations/blob/master/java-annotations/src/main/java/org/jetbrains/annotations/Nullable.java#L39

Could this explain the issue? Is there any workaround, since I don't have control over the external library.

stephan-herrmann commented 1 month ago

Is there any workaround, since I don't have control over the external library.

This could be difficult to work around, since both kinds of annotations define quite different semantics. It would be like switching compilers half-way through the build :). And indeed JDT derives the choice of semantics from the @Target of the primary set of annotations.

blablubbabc commented 1 month ago

I just tried updating the jetbrains annotations dependency of the external project, building it, and depending on that modified version, and the issue is gone.

I don't know if there is any technical reason to sticking to the older version, but I will propose to update the dependency in the external project. Thank you!

Edit: Seems like they are using the java5 version to avoid some issue with the annotations showing up twice on methods in the javadocs (https://github.com/JetBrains/java-annotations/issues/37). I guess I will have to suppress the null checks for now until this issue has been resolved.

stephan-herrmann commented 1 month ago

Edit: Seems like they are using the java5 version to avoid some issue with the annotations showing up twice on methods in the javadocs (JetBrains/java-annotations#37).

Thanks for sharing. IMHO they should have gone straight from declaration annotations to only TYPE_USE target.

I guess I will have to suppress the null checks for now until this issue has been resolved.

Hopefully this can be done at a fairly narrow scope (using @SuppressWarnings)? You might have to enable "Suppress optional errors with @SuppressWarnings" for this to work.

When thinking about how JDT could help more: should we raise a warning (error?), when secondary null annotations are incompatible with the primary annotations in this way?

blablubbabc commented 1 month ago

When thinking about how JDT could help more: should we raise a warning (error?), when secondary null annotations are incompatible with the primary annotations in this way?

I mean, JDT is able to interpret those annotations in some way if I use them as primary ones. Ideal would be if JDT would be able to detect these secondary Java5 annotations as well and merge the nullability information/hints derived from them into its model about what it expects can be null or nullable. E.g. if the method is annotated with this Java5 Nullable annotation, to interpret this as if the return type was annotated as nullable. If the parameter is annotated, to interpret this as the parameter type is annotated, etc. If there is also a type_use annotation found, this would override these other hints. If there is no nullability information at all, e.g. on type parameters, this could be interpreted as "unknown"/"potentially nullable" as usual, unless there is also a "NonNullByDefault" annotation on the package to define the default interpretation of un-annotated types.

But I am probably missing something that makes this more complicated than it seems to me.

stephan-herrmann commented 1 month ago

But I am probably missing something that makes this more complicated than it seems to me.

One of the simpler questions that would follow: how should it interpret void m(@org.jetbrains.annotations.Nullable String[] a) when primary annotations are TYPE_USE?

And that's just the start. So, frankly I don't see anybody having the leisure to implement this without causing lots of bad surprises.

Back to the warning regarding inconsistent configuration: do you think this is useful?

blablubbabc commented 1 month ago

One of the simpler questions that would follow: how should it interpret void m(@org.jetbrains.annotations.Nullable String[] a) when primary annotations are TYPE_USE?

Wouldn't it be possible to check the actually used annotation type in each individual case, see if it defines TYPE_USE, and then interpret it accordingly, instead of only considering this aspect for the primary annotation? I.e. if I depend on a mix of external projects, and some are using annotations with TYPE_USE and some are using legacy annotations without TYPE_USE, Eclipse would interpret each annotation case according to the kind of annotation used in this case.

In the example above, if the Java5 jetbrains annotations without TYPE_USE are used, this would mean "the array is not null".

Back to the warning regarding inconsistent configuration: do you think this is useful?

If there is really no solution to make this mix work, then a warning, maybe inside the Eclipse UI where the secondary annotations are defined, could make sense. Similar to the warning that is already used there when the specified annotation is not found.

stephan-herrmann commented 1 month ago

Wouldn't it be possible [...]

possible: yes. affordable: maybe not.