mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
014a1ac581
commit
6092fd6604
+78
-1
@@ -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;
|
||||
|
||||
+21
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
+18
@@ -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>
|
||||
}
|
||||
+4
@@ -43,5 +43,9 @@ public class OverrideImplementNullabilityTest extends OverrideImplementBaseTest
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNestedTypeParameter() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
protected void doTest() { doTest(false, true); }
|
||||
}
|
||||
Reference in New Issue
Block a user