IDEA-389407 [java]: Fix Duplicate @NotNull generated by implement-method intention

Bug root cause explanation:
When implement-method intention generates parameter copy for generated method then internally `com.intellij.psi.impl.PsiElementFactoryImpl#createParameter(...)` is used which generates all symbols with fully qualified names, so the copy is slightly different from original. Below two examples:
`@NotNull Integer` -> `java.lang.@org.jetbrains.annotations.NotNull Integer`
`@NotNull Map.Entry` -> `java.util.@org.jetbrains.annotations.NotNull Map.Entry`
when `com.intellij.codeInsight.generation.OverrideImplementsAnnotationsHandler#repeatAnnotationsFromSource` detect cases when some annotation is present on source but not on target by using `AnnotationUtil.isAnnotated`. When run for above 4 types it returns true for all of them except the fourth (`java.util.@org.jetbrains.annotations.NotNull Map.Entry`) as `@NotNull` is neither before the whole expression nor before the last part (`Entry`).

Merge-request: IJ-MR-204762
Merged-by: Marcin Mikosik <marcin.mikosik@jetbrains.com>

GitOrigin-RevId: 89804ff61806bbb3e312320e86966913c3e2d1b9
This commit is contained in:
Marcin Mikosik
2026-05-21 16:22:45 +00:00
committed by intellij-monorepo-bot
parent 014a1ac581
commit 6092fd6604
4 changed files with 121 additions and 1 deletions
@@ -11,9 +11,14 @@ import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ArrayUtil;
@@ -59,7 +64,7 @@ public interface OverrideImplementsAnnotationsHandler extends OverrideImplements
if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) continue;
int flags = CHECK_EXTERNAL | CHECK_TYPE;
if (AnnotationUtil.isAnnotated(source, annotation, flags) && !AnnotationUtil.isAnnotated(target, annotation, flags)) {
if (AnnotationUtil.isAnnotated(source, annotation, flags) && !hasAnnotation(target, annotation)) {
each.transferToTarget(annotation, source, target);
}
}
@@ -70,6 +75,78 @@ public interface OverrideImplementsAnnotationsHandler extends OverrideImplements
}
}
private static boolean hasAnnotation(@NotNull PsiModifierListOwner target, @NotNull String annotation) {
if (AnnotationUtil.isAnnotated(target, annotation, CHECK_EXTERNAL | CHECK_TYPE)) return true;
// Handle case when the annotation present in the source modifier list
// has been 'hidden' after the package qualifier when generating target.
// For example for:
// source = @NotNull Map.Entry param
// target = java.util.@org.jetbrains.annotations.NotNull Map.Entry
// AnnotationUtil.hasAnnotation(.., ..@NotNull) returns true for source but false for the target
PsiTypeElement typeElement = getTypeElement(target);
if (typeElement == null) return false;
return hasAnnotationOnFirstNonPackageSubElement(typeElement, annotation);
}
private static @Nullable PsiTypeElement getTypeElement(@NotNull PsiModifierListOwner target) {
if (target instanceof PsiMethod) return ((PsiMethod)target).getReturnTypeElement();
if (target instanceof PsiVariable) return ((PsiVariable)target).getTypeElement();
return null;
}
/**
* Checks if a specified annotation is present at the innermost component reference element of the given type element
* after skipping the initial package qualifier.
*
* <p>Examples:
* <pre>{@code
* Returns true for annotation "org.jetbrains.annotations.NotNull"
* when typeElement represents: java.lang.@NotNull String
*
* Returns true for annotation "org.jetbrains.annotations.NotNull"
* when typeElement represents: @NotNull String
*
* Returns false for annotation "org.jetbrains.annotations.NotNull"
* when typeElement represents: @NotNull java.lang.String
*
* Returns true for annotation "org.jetbrains.annotations.Nullable"
* when typeElement represents: java.util.@Nullable List<String>
* }</pre>
*
* @param typeElement The {@link PsiTypeElement} to inspect. This represents a type in Java source code,
* such as a method return type or a parameter type.
* @param annotation The fully qualified name of the annotation to search for.
* @return true if the annotation is found after the package qualifier in the type reference;
* false otherwise, or if the type element has no innermost component reference.
*/
private static boolean hasAnnotationOnFirstNonPackageSubElement(PsiTypeElement typeElement, @NotNull String annotation) {
PsiJavaCodeReferenceElement reference = typeElement.getInnermostComponentReferenceElement();
if (reference == null) return false;
var leftMostResolvedQualifier = getLeftmostNonPackageElement(reference);
if (leftMostResolvedQualifier == null) return false;
return hasDirectAnnotation(leftMostResolvedQualifier, annotation);
}
private static @Nullable PsiElement getLeftmostNonPackageElement(@NotNull PsiJavaCodeReferenceElement reference) {
PsiJavaCodeReferenceElement qualifier =
reference.getQualifier() instanceof PsiJavaCodeReferenceElement referenceElement ? referenceElement : null;
if (qualifier != null) {
PsiElement result = getLeftmostNonPackageElement(qualifier);
if (result != null) return result;
}
PsiElement resolved = reference.resolve();
return resolved == null || resolved instanceof PsiPackage ? null : reference;
}
private static boolean hasDirectAnnotation(@NotNull PsiElement element, @NotNull String annotation) {
for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof PsiAnnotation psiAnnotation && psiAnnotation.hasQualifiedName(annotation)) {
return true;
}
}
return false;
}
default void transferToTarget(String annotation, PsiModifierListOwner source, PsiModifierListOwner target) {
PsiModifierList modifierList = target.getModifierList();
assert modifierList != null : target;
@@ -0,0 +1,21 @@
package mypackage;
import org.jetbrains.annotations.NotNull;
class Outer {
static class Middle {
static class Nested {
}
}
}
interface Parent {
void test(@NotNull Outer.Middle.Nested nested);
}
class Child implements Parent {
@Override
public void test(@NotNull Outer.Middle.Nested nested) {
}
}
@@ -0,0 +1,18 @@
package mypackage;
import org.jetbrains.annotations.NotNull;
class Outer {
static class Middle {
static class Nested {
}
}
}
interface Parent {
void test(@NotNull Outer.Middle.Nested nested);
}
class Child implements Parent {
<caret>
}
@@ -43,5 +43,9 @@ public class OverrideImplementNullabilityTest extends OverrideImplementBaseTest
doTest();
}
public void testNestedTypeParameter() {
doTest();
}
protected void doTest() { doTest(false, true); }
}