[java-psi] Simplify building of the annotated type from TypeInfo and avoid annotation resolution

Before, we used to build the simple type first, and then tried to annotate it. This was quite complex and fragile due to weird structure of PsiType (it's not always easily decomposable). Also, some operations like replacing parts of PSI tree require type resolution (via PsiType#getCanonicalText). It looks like, it's pretty possible to generate complete annotated type as text, and then parse it into PsiTypeElement generating final PsiType. This should not be slower, as the replacements are mostly doing similar things (e.g., see JavaTreeGenerator.generateTreeFor: it gets type's canonical text, then parses the type back from it).
Fixes IDEA-370755 Avoid resolve when building method/variable return type from stub

GitOrigin-RevId: 949d79a2044f78110ffd6f845ff9e2712b55a36f
This commit is contained in:
Tagir Valeev
2025-04-15 07:15:41 +00:00
committed by intellij-monorepo-bot
parent 946ccd79ec
commit d73351f5e4
10 changed files with 203 additions and 148 deletions
@@ -9,13 +9,11 @@ import com.intellij.psi.impl.compiled.ClsAnnotationParameterListImpl;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.impl.compiled.ClsJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.java.stubs.impl.PsiAnnotationStubImpl;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
@@ -26,7 +24,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* An immutable container that holds all the type annotations for some type (including internal type components).
@@ -105,99 +102,13 @@ public final class ExplicitTypeAnnotationContainer implements TypeAnnotationCont
}
}
/**
* @param type type
* @param context context PsiElement
* @return type annotated with annotations from this container
*/
@Override
public @NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context) {
if (type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
PsiType modifiedComponentType = forArrayElement().applyTo(componentType, context);
if (componentType != modifiedComponentType) {
type = type instanceof PsiEllipsisType ? new PsiEllipsisType(modifiedComponentType) : modifiedComponentType.createArrayType();
public void appendImmediateText(@NotNull StringBuilder sb) {
for (TypeAnnotationEntry entry : myList) {
if (entry.myPath.length == 0) {
sb.append(entry.myText).append(' ');
}
}
else if (type instanceof PsiClassReferenceType) {
PsiJavaCodeReferenceElement reference = ((PsiClassReferenceType)type).getReference();
PsiJavaCodeReferenceElement modifiedReference = annotateReference(reference, context);
if (modifiedReference != reference) {
type = new PsiClassReferenceType(modifiedReference, PsiUtil.getLanguageLevel(context), type.getAnnotationProvider());
}
if (modifiedReference.isQualified()) {
return type;
}
}
else if (type instanceof PsiWildcardType) {
PsiWildcardType wildcardType = (PsiWildcardType)type;
PsiType bound = wildcardType.getBound();
if (bound != null) {
PsiType modifiedBound = forBound().applyTo(bound, context);
if (modifiedBound != bound) {
if (wildcardType.isExtends()) {
type = PsiWildcardType.createExtends(context.getManager(), modifiedBound);
}
else if (wildcardType.isSuper()) {
type = PsiWildcardType.createSuper(context.getManager(), modifiedBound);
}
}
}
}
return type.annotate(getProvider(context));
}
@Override
public @NotNull PsiJavaCodeReferenceElement annotateReference(@NotNull PsiJavaCodeReferenceElement reference,
@NotNull PsiElement context) {
PsiReferenceParameterList list = reference.getParameterList();
PsiJavaCodeReferenceElement copy = reference;
PsiElement qualifier = reference.getQualifier();
if (qualifier != null) {
PsiJavaCodeReferenceElement modifiedQualifier =
forEnclosingClass().annotateReference((PsiJavaCodeReferenceElement)qualifier, context);
if (modifiedQualifier != qualifier) {
copy = (PsiJavaCodeReferenceElement)reference.copy();
Objects.requireNonNull(copy.getQualifier()).replace(modifiedQualifier);
}
StringBuilder refText = null;
for (TypeAnnotationEntry entry : myList) {
if (entry.myPath.length == 0) {
if (refText == null) {
refText = new StringBuilder(modifiedQualifier.getText());
refText.append(".");
}
refText.append(entry.myText).append(' ');
}
}
if (refText != null) {
boolean startCopy = false;
for (PsiElement child = reference.getFirstChild(); child != null; child = child.getNextSibling()) {
if (startCopy) {
refText.append(child.getText());
}
if (PsiUtil.isJavaToken(child, JavaTokenType.DOT)) {
startCopy = true;
}
}
copy = JavaPsiFacade.getElementFactory(context.getProject()).createReferenceFromText(refText.toString(), context);
}
}
if (list != null) {
PsiTypeElement[] elements = list.getTypeParameterElements();
for (int i = 0; i < elements.length; i++) {
PsiType parameter = elements[i].getType();
PsiType modifiedParameter = forTypeArgument(i).applyTo(parameter, context);
if (parameter != modifiedParameter) {
if (copy == reference) {
copy = (PsiJavaCodeReferenceElement)reference.copy();
}
Objects.requireNonNull(copy.getParameterList()).getTypeParameterElements()[i]
.replace(JavaPsiFacade.getElementFactory(context.getProject()).createTypeElement(modifiedParameter));
}
}
}
return copy;
}
/**
@@ -2,7 +2,10 @@
package com.intellij.psi.impl.cache;
import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.psi.*;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.TypeAnnotationProvider;
import org.jetbrains.annotations.NotNull;
/**
@@ -60,16 +63,10 @@ public final class ExternalTypeAnnotationContainer implements TypeAnnotationCont
}
@Override
public @NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context) {
public void appendImmediateText(@NotNull StringBuilder sb) {
throw new UnsupportedOperationException();
}
@Override
public @NotNull PsiJavaCodeReferenceElement annotateReference(@NotNull PsiJavaCodeReferenceElement reference,
@NotNull PsiElement context) {
throw new UnsupportedOperationException();
}
public static @NotNull TypeAnnotationContainer create(@NotNull PsiModifierListOwner owner) {
return new ExternalTypeAnnotationContainer("", owner);
}
@@ -2,8 +2,6 @@
package com.intellij.psi.impl.cache;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiType;
import com.intellij.psi.TypeAnnotationProvider;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -44,14 +42,7 @@ public interface TypeAnnotationContainer {
}
@Override
public @NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context) {
return type;
}
@Override
public @NotNull PsiJavaCodeReferenceElement annotateReference(@NotNull PsiJavaCodeReferenceElement reference,
@NotNull PsiElement context) {
return reference;
public void appendImmediateText(@NotNull StringBuilder sb) {
}
};
@@ -90,8 +81,8 @@ public interface TypeAnnotationContainer {
*/
@NotNull TypeAnnotationProvider getProvider(PsiElement parent);
@NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context);
@NotNull PsiJavaCodeReferenceElement annotateReference(@NotNull PsiJavaCodeReferenceElement reference,
@NotNull PsiElement context);
/**
* Appends to StringBuilder annotation text that applicable to this element immediately (not to sub-elements)
*/
void appendImmediateText(@NotNull StringBuilder sb);
}
@@ -28,6 +28,7 @@ import java.util.*;
import static com.intellij.util.BitUtil.clear;
import static com.intellij.util.BitUtil.isSet;
import static java.util.Objects.requireNonNull;
/**
* Represents a type encoded inside a stub tree
@@ -189,6 +190,46 @@ public /*sealed*/ abstract class TypeInfo {
throw new IllegalStateException();
}
}
@Override
void appendAnnotatedText(@NotNull TypeAnnotationContainer container, @NotNull StringBuilder sb) {
switch (getKind()) {
case EXTENDS:
container.appendImmediateText(sb);
sb.append("? extends ");
myChild.appendAnnotatedText(container.forBound(), sb);
break;
case SUPER:
container.appendImmediateText(sb);
sb.append("? super ");
myChild.appendAnnotatedText(container.forBound(), sb);
break;
case ARRAY:
int arrayCount = 1;
TypeAnnotationContainer curContainer = container.forArrayElement();
TypeInfo child = myChild;
while (child instanceof DerivedTypeInfo && child.getKind() == TypeKind.ARRAY) {
child = ((DerivedTypeInfo)child).child();
arrayCount++;
curContainer = curContainer.forArrayElement();
}
child.appendAnnotatedText(curContainer, sb);
curContainer = container;
for (int i = 0; i < arrayCount; i++) {
curContainer.appendImmediateText(sb);
sb.append("[]");
curContainer = curContainer.forArrayElement();
}
break;
case ELLIPSIS:
myChild.appendAnnotatedText(container.forArrayElement(), sb);
container.appendImmediateText(sb);
sb.append("...");
break;
default:
throw new IllegalStateException();
}
}
}
/**
@@ -242,7 +283,25 @@ public /*sealed*/ abstract class TypeInfo {
sb.append(">");
return sb.toString();
}
@Override
void appendAnnotatedText(@NotNull TypeAnnotationContainer container, @NotNull StringBuilder sb) {
if (myOuter != null) {
myOuter.appendAnnotatedText(container.forEnclosingClass(), sb);
sb.append(".");
}
container.appendImmediateText(sb);
sb.append(myName);
if (myComponents.length > 0) {
sb.append("<");
for (int i = 0; i < myComponents.length; i++) {
if (i > 0) sb.append(",");
myComponents[i].appendAnnotatedText(container.forTypeArgument(i), sb);
}
sb.append(">");
}
}
String jvmName() {
return myOuter == null ? myName.replace('.', '/') : myOuter.jvmName() + "$" + myName;
}
@@ -307,6 +366,17 @@ public /*sealed*/ abstract class TypeInfo {
public final String text() {
return text(false);
}
public @NotNull String annotatedText() {
StringBuilder sb = new StringBuilder();
appendAnnotatedText(getTypeAnnotations(), sb);
return sb.toString();
}
void appendAnnotatedText(@NotNull TypeAnnotationContainer container, @NotNull StringBuilder sb) {
container.appendImmediateText(sb);
sb.append(kind.text);
}
/**
* @return type kind
@@ -371,7 +441,7 @@ public /*sealed*/ abstract class TypeInfo {
/* factories and serialization */
/**
* @return return type of the constructor (null-type)
* @return return the type of the constructor (null-type)
*/
public static @NotNull TypeInfo createConstructorType() {
return TypeInfo.SimpleTypeInfo.NULL;
@@ -539,10 +609,10 @@ public /*sealed*/ abstract class TypeInfo {
info = new SimpleTypeInfo(TypeKind.WILDCARD); // may be overwritten
}
if (tokenType == JavaTokenType.LBRACKET) {
info = Objects.requireNonNull(info).arrayOf();
info = requireNonNull(info).arrayOf();
}
else if (tokenType == JavaTokenType.ELLIPSIS) {
info = Objects.requireNonNull(info).arrayOf().withEllipsis();
info = requireNonNull(info).arrayOf().withEllipsis();
}
}
if (info == null) {
@@ -670,20 +740,20 @@ public /*sealed*/ abstract class TypeInfo {
RefTypeInfo outer = null;
switch (kind) {
case REF:
info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()));
info = new RefTypeInfo(requireNonNull(record.readNameString()));
break;
case INNER_SIMPLE:
outer = new RefTypeInfo(Objects.requireNonNull(record.readNameString()));
info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer);
outer = new RefTypeInfo(requireNonNull(record.readNameString()));
info = new RefTypeInfo(requireNonNull(record.readNameString()), outer);
break;
case INNER:
outer = (RefTypeInfo)readTYPE(record);
info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer);
info = new RefTypeInfo(requireNonNull(record.readNameString()), outer);
break;
case INNER_GENERIC:
outer = (RefTypeInfo)readTYPE(record);
case GENERIC:
String name = Objects.requireNonNull(record.readNameString());
String name = requireNonNull(record.readNameString());
byte count = record.readByte();
TypeInfo[] components = new TypeInfo[count];
for (int i = 0; i < count; i++) {
@@ -698,7 +768,7 @@ public /*sealed*/ abstract class TypeInfo {
info = new DerivedTypeInfo(kind, readTYPE(record));
break;
default:
info = kind.isReference() ? new RefTypeInfo(Objects.requireNonNull(kind.text)) : new SimpleTypeInfo(kind);
info = kind.isReference() ? new RefTypeInfo(requireNonNull(kind.text)) : new SimpleTypeInfo(kind);
}
info.setTypeAnnotations(hasTypeAnnotations ? ExplicitTypeAnnotationContainer.readTypeAnnotations(record) : TypeAnnotationContainer.EMPTY);
return info;
@@ -713,10 +783,10 @@ public /*sealed*/ abstract class TypeInfo {
}
else if (typeInfo instanceof RefTypeInfo && typeInfo.kind.text == null) {
if (typeInfo.kind == TypeKind.INNER_SIMPLE) {
dataStream.writeName(Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter).myName);
dataStream.writeName(requireNonNull(((RefTypeInfo)typeInfo).myOuter).myName);
}
if (typeInfo.kind == TypeKind.INNER || typeInfo.kind == TypeKind.INNER_GENERIC) {
writeTYPE(dataStream, Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter));
writeTYPE(dataStream, requireNonNull(((RefTypeInfo)typeInfo).myOuter));
}
dataStream.writeName(((RefTypeInfo)typeInfo).myName);
if (typeInfo.kind == TypeKind.INNER_GENERIC || typeInfo.kind == TypeKind.GENERIC) {
@@ -20,13 +20,10 @@ import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
public final class JavaSharedImplUtil {
private static final Logger LOG = Logger.getInstance(JavaSharedImplUtil.class);
@@ -88,11 +85,9 @@ public final class JavaSharedImplUtil {
}
public static @NotNull PsiType createTypeFromStub(@NotNull PsiModifierListOwner owner, @NotNull TypeInfo typeInfo) {
String typeText = typeInfo.text();
assert typeText != null : owner;
String typeText = typeInfo.annotatedText();
PsiType type = JavaPsiFacade.getInstance(owner.getProject()).getParserFacade().createTypeFromText(typeText, owner);
type = applyAnnotations(type, owner.getModifierList());
return typeInfo.getTypeAnnotations().applyTo(type, owner);
return applyAnnotations(type, owner.getModifierList());
}
public static @NotNull PsiType applyAnnotations(@NotNull PsiType type, @Nullable PsiModifierList modifierList) {
@@ -100,7 +95,7 @@ public final class JavaSharedImplUtil {
PsiAnnotation[] annotations = modifierList.getAnnotations();
if (annotations.length > 0) {
if (type instanceof PsiArrayType) {
Stack<PsiArrayType> types = new Stack<>();
Deque<PsiArrayType> types = new ArrayDeque<>();
do {
types.push((PsiArrayType)type);
type = ((PsiArrayType)type).getComponentType();
@@ -85,8 +85,7 @@ public class TypeInfoTest extends LightJavaCodeInsightFixtureTestCase {
LighterASTNode classNode = LightTreeUtil.firstChildOfType(ast, root, JavaElementType.CLASS);
LighterASTNode fieldNode = LightTreeUtil.firstChildOfType(ast, classNode, JavaElementType.FIELD);
TypeInfo info = TypeInfo.create(ast, fieldNode, null);
PsiType type = getElementFactory().createTypeFromText(info.text(), getFile());
type = info.getTypeAnnotations().applyTo(type, getFile());
PsiType type = getElementFactory().createTypeFromText(info.annotatedText(), getFile());
assertEquals(typeText, type.getCanonicalText(true));
}
}
@@ -94,4 +94,9 @@ public class K2BundledCompilerPluginsHighlightingMetaInfoTestGenerated extends A
public void testSerialize_non_existing_jar_from_kotlinDistForIde() throws Exception {
runTest("../../idea/tests/testData/highlighterMetaInfoWithBundledCompilerPlugins/serialize_non_existing_jar_from_kotlinDistForIde.kt");
}
@TestMetadata("violationViaPrimaryConstructorPointer.kt")
public void testViolationViaPrimaryConstructorPointer() throws Exception {
runTest("../../idea/tests/testData/highlighterMetaInfoWithBundledCompilerPlugins/violationViaPrimaryConstructorPointer.kt");
}
}
@@ -0,0 +1,78 @@
// COMPILER_ARGUMENTS: -Xplugin=$TEST_DIR$/serialize_fake_plugin.jar
// CHECK_SYMBOL_NAMES
// HIGHLIGHTER_ATTRIBUTES_KEY
// FILE: main.kt
package foo
fun foo(): JavaClass.NestedClass? {
return null
}
// FILE: JavaClass.java
import static NlsContexts.*;
import static NlsContexts.foo;
import static NlsContexts.NlsContexts;
import java.util.List;
import NlsContexts;
public class JavaClass {
@NlsContexts.DialogTitle
public kotlin.@NlsContexts.DialogTitle2 List<NlsContexts.@NlsContexts.DialogTitle DialogTitle3> bar() {
}
public static class NestedClass {
}
}
// FILE: NlsContext.kt
import org.jetbrains.annotations.NonNls
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class NlsContext(
/**
* Provide a neat property key prefix that unambiguously defines literal usage context.
* E.g. "button", "button.tooltip" for button text and tooltip correspondingly, "action.text" for action text
*/
val prefix: @NonNls String = "",
/**
* Provide a neat property key suffix that unambiguously defines literal usage context.
* E.g. "description" for action/intention description
*/
val suffix: @NonNls String = "",
)
// FILE: NlsContexts.kt
import org.jetbrains.annotations.Nls
import kotlin.annotation.AnnotationTarget.*
class NlsContexts {
/**
* Dialogs
*/
@NlsContext(prefix = "dialog.title")
@Target(TYPE, TYPE_PARAMETER, VALUE_PARAMETER, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, FIELD)
@Nls(capitalization = Nls.Capitalization.Title)
annotation class DialogTitle
/**
* Dialogs
*/
@NlsContext(prefix = "dialog.title")
@Target(TYPE, TYPE_PARAMETER, VALUE_PARAMETER, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, FIELD)
@Nls(capitalization = Nls.Capitalization.Title)
annotation class DialogTitle2
/**
* Dialogs
*/
@NlsContext(prefix = "dialog.title")
@Target(TYPE, TYPE_PARAMETER, VALUE_PARAMETER, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, FIELD)
@Nls(capitalization = Nls.Capitalization.Title)
annotation class DialogTitle3
fun DialogTitle3() {}
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xplugin=$TEST_DIR$/serialize_fake_plugin.jar
// CHECK_SYMBOL_NAMES
// HIGHLIGHTER_ATTRIBUTES_KEY
// FILE: main.kt
package foo
fun <!HIGHLIGHTING("severity='SYMBOL_TYPE_SEVERITY'; highlightingTextAttributesKey='KOTLIN_FUNCTION_DECLARATION'")!>foo<!>(): <!HIGHLIGHTING("severity='SYMBOL_TYPE_SEVERITY'; highlightingTextAttributesKey='KOTLIN_CLASS'")!>JavaClass<!>.<!HIGHLIGHTING("severity='SYMBOL_TYPE_SEVERITY'; highlightingTextAttributesKey='KOTLIN_CLASS'")!>NestedClass<!>? {
return null
}
@@ -1,13 +1,13 @@
// ERROR: Return type mismatch: expected 'ArrayList<String?>', actual 'ArrayList<@NotNull() String>'.
// ERROR: Return type mismatch: expected 'ArrayList<String?>?', actual 'ArrayList<@NotNull() String>?'.
// ERROR: Argument type mismatch: actual type is 'ArrayList<String?>?', but 'ArrayList<@NotNull() String>?' was expected.
// ERROR: Argument type mismatch: actual type is 'ArrayList<String?>', but 'ArrayList<@NotNull() String>' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>?', actual 'ArrayList<@NotNull() String>?'.
// ERROR: Type mismatch: inferred type is 'ArrayList<@NotNull() String>?', but 'ArrayList<String?>?' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>', actual 'ArrayList<@NotNull() String>'.
// ERROR: Type mismatch: inferred type is 'ArrayList<@NotNull() String>', but 'ArrayList<String?>' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>?', actual 'ArrayList<@NotNull() String>?'.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>', actual 'ArrayList<@NotNull() String>'.
// ERROR: Return type mismatch: expected 'ArrayList<String?>', actual '@NotNull() ArrayList<@NotNull() String>'.
// ERROR: Return type mismatch: expected 'ArrayList<String?>?', actual '@Nullable() ArrayList<@NotNull() String>?'.
// ERROR: Argument type mismatch: actual type is 'ArrayList<String?>?', but '@Nullable() ArrayList<@NotNull() String>?' was expected.
// ERROR: Argument type mismatch: actual type is 'ArrayList<String?>', but '@NotNull() ArrayList<@NotNull() String>' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>?', actual '@Nullable() ArrayList<@NotNull() String>?'.
// ERROR: Type mismatch: inferred type is '@Nullable() ArrayList<@NotNull() String>?', but 'ArrayList<String?>?' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>', actual '@NotNull() ArrayList<@NotNull() String>'.
// ERROR: Type mismatch: inferred type is '@NotNull() ArrayList<@NotNull() String>', but 'ArrayList<String?>' was expected.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>?', actual '@Nullable() ArrayList<@NotNull() String>?'.
// ERROR: Initializer type mismatch: expected 'ArrayList<String?>', actual '@NotNull() ArrayList<@NotNull() String>'.
class Foo {
fun testAssignment(j: J) {
val l1 = j.return1()