mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[java] Basic support for external type annotations
Only in XML directly; no editing UI for now; no inlays for now; only for libraries (Cls), currently no intent to extend to sources (Psi) Part of IDEA-231901 Support TYPE_USE in external annotations GitOrigin-RevId: 672ed09f57ffc40b61e8fe4dd33d0f9acdac92dc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1c8240ecd6
commit
b50767f679
@@ -11,10 +11,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiFileEx;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiFormatUtilBase;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -86,7 +83,7 @@ public final class JavaHighlightUtil {
|
||||
|
||||
@NotNull
|
||||
public static String formatType(@Nullable PsiType type) {
|
||||
return type == null ? PsiKeyword.NULL : type.getInternalCanonicalText();
|
||||
return type == null ? PsiKeyword.NULL : PsiTypesUtil.removeExternalAnnotations(type).getInternalCanonicalText();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.NullableFunction;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
@@ -280,16 +279,12 @@ public final class DfaPsiUtil {
|
||||
*/
|
||||
@NotNull
|
||||
public static Nullability getFunctionalParameterNullability(PsiFunctionalExpression function, int index) {
|
||||
Nullability nullability = inferLambdaParameterNullability(function, index);
|
||||
if(nullability != Nullability.UNKNOWN) {
|
||||
return nullability;
|
||||
}
|
||||
PsiClassType type = ObjectUtils.tryCast(LambdaUtil.getFunctionalInterfaceType(function, true), PsiClassType.class);
|
||||
PsiClassType type = tryCast(LambdaUtil.getFunctionalInterfaceType(function, true), PsiClassType.class);
|
||||
PsiMethod sam = LambdaUtil.getFunctionalInterfaceMethod(type);
|
||||
if (sam != null) {
|
||||
PsiParameter parameter = sam.getParameterList().getParameter(index);
|
||||
if (parameter != null) {
|
||||
nullability = getElementNullability(null, parameter);
|
||||
Nullability nullability = getElementNullability(null, parameter);
|
||||
if (nullability != Nullability.UNKNOWN) {
|
||||
return nullability;
|
||||
}
|
||||
@@ -300,44 +295,6 @@ public final class DfaPsiUtil {
|
||||
return Nullability.UNKNOWN;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Nullability inferLambdaParameterNullability(PsiFunctionalExpression lambda, int parameterIndex) {
|
||||
PsiElement expression = lambda;
|
||||
PsiElement expressionParent = lambda.getParent();
|
||||
while(expressionParent instanceof PsiConditionalExpression || expressionParent instanceof PsiParenthesizedExpression) {
|
||||
expression = expressionParent;
|
||||
expressionParent = expressionParent.getParent();
|
||||
}
|
||||
if (expressionParent instanceof PsiExpressionList list && list.getParent() instanceof PsiMethodCallExpression call) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null) {
|
||||
int expressionIndex = ArrayUtil.find(list.getExpressions(), expression);
|
||||
return getLambdaParameterNullability(method, expressionIndex, parameterIndex);
|
||||
}
|
||||
}
|
||||
return Nullability.UNKNOWN;
|
||||
}
|
||||
|
||||
private static final CallMatcher OPTIONAL_FUNCTIONS =
|
||||
CallMatcher.instanceCall(JAVA_UTIL_OPTIONAL, "map", "filter", "ifPresent", "flatMap", "ifPresentOrElse");
|
||||
private static final CallMatcher MAP_COMPUTE =
|
||||
CallMatcher.instanceCall(JAVA_UTIL_MAP, "compute").parameterTypes("K", JAVA_UTIL_FUNCTION_BI_FUNCTION);
|
||||
|
||||
@NotNull
|
||||
private static Nullability getLambdaParameterNullability(@NotNull PsiMethod method, int parameterIndex, int lambdaParameterIndex) {
|
||||
if (OPTIONAL_FUNCTIONS.methodMatches(method)) {
|
||||
if (parameterIndex == 0 && lambdaParameterIndex == 0) {
|
||||
return Nullability.NOT_NULL;
|
||||
}
|
||||
}
|
||||
else if (MAP_COMPUTE.methodMatches(method)) {
|
||||
if (parameterIndex == 1 && lambdaParameterIndex == 1) {
|
||||
return Nullability.NULLABLE;
|
||||
}
|
||||
}
|
||||
return Nullability.UNKNOWN;
|
||||
}
|
||||
|
||||
private static boolean isEnumPredefinedMethod(PsiMethod method) {
|
||||
return CallMatcher.enumValueOf().methodMatches(method) || CallMatcher.enumValues().methodMatches(method);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,15 @@ public abstract class ExternalAnnotationsManager {
|
||||
|
||||
public abstract @NotNull PsiAnnotation @NotNull [] findExternalAnnotations(@NotNull PsiModifierListOwner listOwner);
|
||||
|
||||
/**
|
||||
* @param parent a type owner (field, method, or parameter)
|
||||
* @param typePath a type path. See {@code ExternalTypeAnnotationContainer} for syntax
|
||||
* @return external type annotations for a given type path
|
||||
*/
|
||||
public @NotNull PsiAnnotation @NotNull [] findExternalTypeAnnotations(@NotNull PsiModifierListOwner parent, @NotNull String typePath) {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns external annotations associated with default
|
||||
* constructor of the {@code aClass}, if the constructor exists.
|
||||
@@ -115,7 +124,7 @@ public abstract class ExternalAnnotationsManager {
|
||||
/**
|
||||
* Returns external annotations with fully qualified name of {@code annotationFQN}
|
||||
* associated with default constructor of the {@code aClass}, if the constructor exists.
|
||||
*
|
||||
* <p>
|
||||
* Multiple annotations may be returned since there may be repeatable annotations
|
||||
* or annotations from several external annotations roots.
|
||||
*
|
||||
|
||||
@@ -1262,6 +1262,7 @@ public final class LambdaUtil {
|
||||
psiType = substitutor.substitute(parameters[i].getType());
|
||||
if (!PsiTypesUtil.isDenotableType(psiType, lambdaExpression)) return null;
|
||||
}
|
||||
psiType = PsiTypesUtil.removeExternalAnnotations(psiType);
|
||||
|
||||
PsiAnnotation[] annotations = lambdaParameter.getAnnotations();
|
||||
for (PsiAnnotation annotation : annotations) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.openapi.diagnostic.Attachment;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -713,6 +714,63 @@ public final class PsiTypesUtil {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type input type
|
||||
* @return type with removed external annotations (if any); on any level of depth
|
||||
*/
|
||||
public static @NotNull PsiType removeExternalAnnotations(@NotNull PsiType type) {
|
||||
PsiAnnotation[] annotations = type.getAnnotations();
|
||||
if (annotations.length > 0) {
|
||||
List<PsiAnnotation> newAnnotations = ContainerUtil.filter(
|
||||
annotations, annotation -> !ExternalAnnotationsManager.getInstance(annotation.getProject()).isExternalAnnotation(annotation));
|
||||
if (newAnnotations.size() < annotations.length) {
|
||||
type = type.annotate(TypeAnnotationProvider.Static.create(newAnnotations.toArray(PsiAnnotation.EMPTY_ARRAY)));
|
||||
}
|
||||
}
|
||||
if (type instanceof PsiClassType) {
|
||||
PsiClassType classType = (PsiClassType)type;
|
||||
PsiClass psiClass = classType.resolve();
|
||||
if (psiClass != null) {
|
||||
PsiType[] parameters = classType.getParameters();
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiType parameter = parameters[i];
|
||||
PsiType updatedParameter = removeExternalAnnotations(parameter);
|
||||
parameters[i] = updatedParameter;
|
||||
changed |= updatedParameter != parameter;
|
||||
}
|
||||
if (changed) {
|
||||
return JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass, parameters);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
else if (type instanceof PsiArrayType) {
|
||||
PsiArrayType arrayType = (PsiArrayType)type;
|
||||
PsiType origComponentType = arrayType.getComponentType();
|
||||
PsiType componentType = removeExternalAnnotations(origComponentType);
|
||||
return componentType == origComponentType ? type : componentType.createArrayType();
|
||||
}
|
||||
else if (type instanceof PsiWildcardType) {
|
||||
PsiWildcardType wildcardType = (PsiWildcardType)type;
|
||||
PsiType bound = wildcardType.getBound();
|
||||
return bound == null ? wildcardType :
|
||||
wildcardType.isExtends() ? PsiWildcardType.createExtends(wildcardType.getManager(), removeExternalAnnotations(bound)) :
|
||||
wildcardType.isSuper() ? PsiWildcardType.createSuper(wildcardType.getManager(), removeExternalAnnotations(bound)) :
|
||||
wildcardType;
|
||||
}
|
||||
else if (type instanceof PsiIntersectionType) {
|
||||
PsiIntersectionType intersectionType = (PsiIntersectionType)type;
|
||||
PsiType[] conjuncts = intersectionType.getConjuncts();
|
||||
PsiType[] newConjuncts = new PsiType[conjuncts.length];
|
||||
for (int i = 0; i < conjuncts.length; i++) {
|
||||
newConjuncts[i] = removeExternalAnnotations(conjuncts[i]);
|
||||
}
|
||||
return PsiIntersectionType.createIntersection(newConjuncts);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public static class TypeParameterSearcher extends PsiTypeVisitor<Boolean> {
|
||||
private final Set<PsiTypeParameter> myTypeParams = new HashSet<>();
|
||||
|
||||
|
||||
@@ -99,13 +99,22 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
if (result.isEmpty()) return Collections.emptyList();
|
||||
SmartList<PsiAnnotation> annotations = new SmartList<>();
|
||||
for (AnnotationData data : result) {
|
||||
if (annotationFQNs.contains(data.annotationClassFqName)) {
|
||||
if (data.hasNoTypePath() && annotationFQNs.contains(data.annotationClassFqName)) {
|
||||
annotations.add(data.getAnnotation(this));
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiAnnotation @NotNull [] findExternalTypeAnnotations(@NotNull PsiModifierListOwner listOwner,
|
||||
@NotNull String typePath) {
|
||||
List<AnnotationData> result = collectExternalAnnotations(listOwner);
|
||||
if (result.isEmpty()) return PsiAnnotation.EMPTY_ARRAY;
|
||||
return StreamEx.of(result).filter(data -> typePath.equals(data.typePath))
|
||||
.map(data -> data.getAnnotation(this)).toArray(PsiAnnotation.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<PsiAnnotation> findDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass, @NotNull String annotationFQN) {
|
||||
if (aClass.getConstructors().length > 0) {
|
||||
@@ -118,7 +127,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
private @NotNull List<PsiAnnotation> filterAnnotations(@NotNull List<AnnotationData> result, @NotNull String annotationFQN) {
|
||||
SmartList<PsiAnnotation> annotations = new SmartList<>();
|
||||
for (AnnotationData data : result) {
|
||||
if (data.annotationClassFqName.equals(annotationFQN)) {
|
||||
if (data.hasNoTypePath() && data.annotationClassFqName.equals(annotationFQN)) {
|
||||
PsiAnnotation annotation = data.getAnnotation(this);
|
||||
annotations.add(annotation);
|
||||
}
|
||||
@@ -132,12 +141,15 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
return null;
|
||||
}
|
||||
List<AnnotationData> result = collectDefaultConstructorExternalAnnotations(aClass);
|
||||
return ContainerUtil.map(result, data -> data.getAnnotation(this));
|
||||
return StreamEx.of(result)
|
||||
.filter(AnnotationData::hasNoTypePath)
|
||||
.map(data -> data.getAnnotation(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExternalAnnotationWritable(@NotNull PsiModifierListOwner listOwner, final @NotNull String annotationFQN) {
|
||||
// note that this method doesn't cache it's result
|
||||
// note that this method doesn't cache its result
|
||||
List<AnnotationData> map = doCollect(listOwner, true);
|
||||
return findByFQN(map, annotationFQN) != null;
|
||||
}
|
||||
@@ -150,6 +162,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
public @NotNull PsiAnnotation @NotNull [] findExternalAnnotations(final @NotNull PsiModifierListOwner listOwner) {
|
||||
final List<AnnotationData> result = collectExternalAnnotations(listOwner);
|
||||
return result.isEmpty() ? PsiAnnotation.EMPTY_ARRAY : StreamEx.of(result)
|
||||
.filter(data -> data.typePath == null)
|
||||
.map(data -> data.getAnnotation(this))
|
||||
.toArray(PsiAnnotation.EMPTY_ARRAY);
|
||||
}
|
||||
@@ -359,7 +372,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
if (!hasInvalidAttribute(invalidXml)) {
|
||||
return invalidXml;
|
||||
}
|
||||
// We assume that XML has single- and double-quote characters only for attribute values, therefore we don't any complex parsing,
|
||||
// We assume that XML has single- and double-quote characters only for attribute values, therefore, we don't do any complex parsing,
|
||||
// just have binary inAttribute state
|
||||
StringBuilder buf = new StringBuilder(invalidXml.length());
|
||||
boolean inAttribute = false;
|
||||
@@ -441,14 +454,20 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
}
|
||||
|
||||
public static final class AnnotationData {
|
||||
private final String annotationClassFqName;
|
||||
private final String annotationParameters;
|
||||
private final @NotNull String annotationClassFqName;
|
||||
private final @NotNull String annotationParameters;
|
||||
private final @Nullable String typePath;
|
||||
|
||||
private volatile PsiAnnotation myAnnotation;
|
||||
|
||||
private AnnotationData(@NotNull String fqn, @NotNull String parameters) {
|
||||
private AnnotationData(@NotNull String fqn, @NotNull String parameters, @Nullable String typePath) {
|
||||
annotationClassFqName = fqn;
|
||||
annotationParameters = parameters;
|
||||
this.typePath = typePath;
|
||||
}
|
||||
|
||||
public boolean hasNoTypePath() {
|
||||
return typePath == null;
|
||||
}
|
||||
|
||||
public @NotNull PsiAnnotation getAnnotation(@NotNull BaseExternalAnnotationsManager context) {
|
||||
@@ -466,17 +485,28 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AnnotationData data = (AnnotationData)o;
|
||||
|
||||
return annotationClassFqName.equals(data.annotationClassFqName) && annotationParameters.equals(data.annotationParameters);
|
||||
return annotationClassFqName.equals(data.annotationClassFqName) &&
|
||||
annotationParameters.equals(data.annotationParameters) &&
|
||||
Objects.equals(typePath, data.typePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = annotationClassFqName.hashCode();
|
||||
result = 31 * result + annotationParameters.hashCode();
|
||||
result = 31 * result + Objects.hashCode(typePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns annotation typePath, as specified in XML. See {@link com.intellij.psi.impl.cache.ExternalTypeAnnotationContainer}
|
||||
* for syntax description;
|
||||
*/
|
||||
@Nullable
|
||||
public String getTypePath() {
|
||||
return typePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return annotationClassFqName + "(" + annotationParameters + ")";
|
||||
@@ -509,6 +539,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
|
||||
private String myExternalName;
|
||||
private String myAnnotationFqn;
|
||||
private String myTypePath;
|
||||
private StringBuilder myArguments;
|
||||
|
||||
private DataParsingSaxHandler(@NotNull VirtualFile file, @Nullable BaseExternalAnnotationsManager manager) {
|
||||
@@ -520,9 +551,11 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
if ("item".equals(qName)) {
|
||||
myExternalName = attributes.getValue("name");
|
||||
myTypePath = null;
|
||||
}
|
||||
else if ("annotation".equals(qName)) {
|
||||
myAnnotationFqn = attributes.getValue("name");
|
||||
myTypePath = attributes.getValue("typePath");
|
||||
myArguments = new StringBuilder();
|
||||
}
|
||||
else if ("val".equals(qName)) {
|
||||
@@ -542,16 +575,25 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if ("item".equals(qName)) {
|
||||
myExternalName = null;
|
||||
myTypePath = null;
|
||||
}
|
||||
else if ("annotation".equals(qName) && myExternalName != null && myAnnotationFqn != null) {
|
||||
for (AnnotationData existingData : myData.get(myExternalName)) {
|
||||
if (existingData.annotationClassFqName.equals(myAnnotationFqn) && myExternalAnnotationsManager != null) {
|
||||
myExternalAnnotationsManager.duplicateError(myFile, myExternalName, "Duplicate annotation '" + myAnnotationFqn + "'");
|
||||
if (existingData.annotationClassFqName.equals(myAnnotationFqn)
|
||||
&& Objects.equals(myTypePath, existingData.typePath)
|
||||
&& myExternalAnnotationsManager != null) {
|
||||
myExternalAnnotationsManager.duplicateError(myFile, myExternalName, "Duplicate annotation '" +
|
||||
myAnnotationFqn +
|
||||
"'"
|
||||
+
|
||||
(myTypePath == null
|
||||
? ""
|
||||
: " for type path '" + myTypePath + "'"));
|
||||
}
|
||||
}
|
||||
|
||||
String argumentsString = myArguments.length() == 0 ? "" : myExternalAnnotationsManager == null ? myArguments.toString() : myExternalAnnotationsManager.intern(myArguments.toString());
|
||||
AnnotationData data = new AnnotationData(myAnnotationFqn, argumentsString);
|
||||
AnnotationData data = new AnnotationData(myAnnotationFqn, argumentsString, myTypePath);
|
||||
myData.add(myExternalName, myExternalAnnotationsManager == null ? data : myExternalAnnotationsManager.internAnnotationData(data));
|
||||
|
||||
myAnnotationFqn = null;
|
||||
|
||||
456
java/java-psi-impl/src/com/intellij/psi/impl/cache/ExplicitTypeAnnotationContainer.java
vendored
Normal file
456
java/java-psi-impl/src/com/intellij/psi/impl/cache/ExplicitTypeAnnotationContainer.java
vendored
Normal file
@@ -0,0 +1,456 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache;
|
||||
|
||||
import com.intellij.openapi.util.NotNullLazyValue;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
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;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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).
|
||||
*/
|
||||
public final class ExplicitTypeAnnotationContainer implements TypeAnnotationContainer {
|
||||
|
||||
private final List<TypeAnnotationEntry> myList;
|
||||
|
||||
private ExplicitTypeAnnotationContainer(List<TypeAnnotationEntry> entries) {
|
||||
if (entries.isEmpty()) {
|
||||
throw new IllegalArgumentException("Empty container: use TypeAnnotationContainer.EMPTY instead");
|
||||
}
|
||||
myList = entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type annotation container for array element
|
||||
* (assuming that this type annotation container is used for the array type)
|
||||
*/
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forArrayElement() {
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.ARRAY_ELEMENT));
|
||||
return list.isEmpty() ? EMPTY : new ExplicitTypeAnnotationContainer(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type annotation container for enclosing class
|
||||
* (assuming that this type annotation container is used for the inner class)
|
||||
*/
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forEnclosingClass() {
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.ENCLOSING_CLASS));
|
||||
return list.isEmpty() ? EMPTY : new ExplicitTypeAnnotationContainer(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type annotation container for wildcard bound
|
||||
* (assuming that this type annotation container is used for the bounded wildcard type)
|
||||
*/
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forBound() {
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.WILDCARD_BOUND));
|
||||
return list.isEmpty() ? EMPTY : new ExplicitTypeAnnotationContainer(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index type argument index
|
||||
* @return type annotation container for given type argument
|
||||
* (assuming that this type annotation container is used for class type with type arguments)
|
||||
*/
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forTypeArgument(int index) {
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, e -> e.forTypeArgument(index));
|
||||
return list.isEmpty() ? EMPTY : new ExplicitTypeAnnotationContainer(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parent parent element for annotations
|
||||
* @return TypeAnnotationProvider that provides all the top-level annotations
|
||||
*/
|
||||
@Override
|
||||
public @NotNull TypeAnnotationProvider getProvider(PsiElement parent) {
|
||||
return new TypeAnnotationContainerProvider(parent, ObjectUtils.tryCast(parent, PsiAnnotationOwner.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates PsiAnnotationStub elements for top-level annotations in this container
|
||||
*
|
||||
* @param parent parent stub
|
||||
*/
|
||||
public void createAnnotationStubs(StubElement<?> parent) {
|
||||
for (TypeAnnotationEntry entry : myList) {
|
||||
if (entry.myPath.length == 0) {
|
||||
new PsiAnnotationStubImpl(parent, entry.myText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes TypeAnnotationContainer into the supplied stream.
|
||||
*
|
||||
* @param dataStream stream to write to
|
||||
* @param container a container to serialize
|
||||
* @throws IOException if the stream throws
|
||||
*/
|
||||
public static void writeTypeAnnotations(@NotNull StubOutputStream dataStream, @NotNull ExplicitTypeAnnotationContainer container)
|
||||
throws IOException {
|
||||
dataStream.writeShort(container.myList.size());
|
||||
for (TypeAnnotationEntry entry : container.myList) {
|
||||
dataStream.writeShort(entry.myPath.length);
|
||||
dataStream.write(entry.myPath);
|
||||
dataStream.writeUTFFast(entry.myText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads TypeAnnotationContainer from the supplied stream.
|
||||
*
|
||||
* @param dataStream stream to read from
|
||||
* @return deserialized TypeAnnotationContainer
|
||||
* @throws IOException if the stream throws
|
||||
*/
|
||||
public static @NotNull TypeAnnotationContainer readTypeAnnotations(@NotNull StubInputStream dataStream) throws IOException {
|
||||
short count = dataStream.readShort();
|
||||
if (count == 0) return EMPTY;
|
||||
TypeAnnotationEntry[] entries = new TypeAnnotationEntry[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
short pathLength = dataStream.readShort();
|
||||
byte[] path = new byte[pathLength];
|
||||
dataStream.readFully(path);
|
||||
String text = dataStream.readUTFFast();
|
||||
entries[i] = new TypeAnnotationEntry(path, text);
|
||||
}
|
||||
return new ExplicitTypeAnnotationContainer(Arrays.asList(entries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.join(myList, "\n");
|
||||
}
|
||||
|
||||
private static @NotNull String encodePath(byte @NotNull [] path) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int pos = 0;
|
||||
while (pos < path.length) {
|
||||
switch (path[pos]) {
|
||||
case Collector.ARRAY_ELEMENT:
|
||||
result.append('[');
|
||||
break;
|
||||
case Collector.ENCLOSING_CLASS:
|
||||
result.append('.');
|
||||
break;
|
||||
case Collector.WILDCARD_BOUND:
|
||||
result.append('*');
|
||||
break;
|
||||
case Collector.TYPE_ARGUMENT:
|
||||
result.append(path[++pos]).append(';');
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static class Collector {
|
||||
public static final byte ARRAY_ELEMENT = 0;
|
||||
public static final byte ENCLOSING_CLASS = 1;
|
||||
public static final byte WILDCARD_BOUND = 2;
|
||||
public static final byte TYPE_ARGUMENT = 3;
|
||||
|
||||
private final @NotNull ArrayList<TypeAnnotationEntry> myList = new ArrayList<>();
|
||||
protected final @NotNull TypeInfo myTypeInfo;
|
||||
|
||||
public Collector(@NotNull TypeInfo info) {
|
||||
myTypeInfo = info;
|
||||
}
|
||||
|
||||
public void add(byte @NotNull [] path, @NotNull String text) {
|
||||
myList.add(new TypeAnnotationEntry(path, text));
|
||||
}
|
||||
|
||||
public void install() {
|
||||
if (myList.isEmpty()) {
|
||||
myTypeInfo.setTypeAnnotations(EMPTY);
|
||||
}
|
||||
else {
|
||||
myList.trimToSize();
|
||||
myTypeInfo.setTypeAnnotations(new ExplicitTypeAnnotationContainer(myList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TypeAnnotationEntry {
|
||||
/**
|
||||
* path is stored as the sequence of ARRAY_ELEMENT, ENCLOSING_CLASS, WILDCARD_BOUND and TYPE_ARGUMENT bytes.
|
||||
* The TYPE_ARGUMENT byte is followed by the type argument index byte.
|
||||
*/
|
||||
final byte @NotNull [] myPath;
|
||||
final @NotNull String myText;
|
||||
|
||||
private TypeAnnotationEntry(byte @NotNull [] path, @NotNull String text) {
|
||||
myPath = path.length == 0 ? ArrayUtil.EMPTY_BYTE_ARRAY : path;
|
||||
myText = text;
|
||||
}
|
||||
|
||||
private TypeAnnotationEntry forPathElement(int wanted) {
|
||||
if (myPath.length > 0 && myPath[0] == wanted) {
|
||||
return new TypeAnnotationEntry(Arrays.copyOfRange(myPath, 1, myPath.length), myText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TypeAnnotationEntry forTypeArgument(int index) {
|
||||
if (myPath.length > 1 && myPath[0] == Collector.TYPE_ARGUMENT && myPath[1] == index) {
|
||||
return new TypeAnnotationEntry(Arrays.copyOfRange(myPath, 2, myPath.length), myText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return encodePath(myPath) + "->" + myText;
|
||||
}
|
||||
}
|
||||
|
||||
static class ClsTypeAnnotationImpl extends ClsElementImpl implements PsiAnnotation {
|
||||
private final NotNullLazyValue<ClsJavaCodeReferenceElementImpl> myReferenceElement;
|
||||
private final NotNullLazyValue<ClsAnnotationParameterListImpl> myParameterList;
|
||||
private final PsiElement myParent;
|
||||
private final @Nullable PsiAnnotationOwner myOwner;
|
||||
private final String myText;
|
||||
|
||||
ClsTypeAnnotationImpl(PsiElement parent, @Nullable PsiAnnotationOwner owner, String text) {
|
||||
myParent = parent;
|
||||
myOwner = owner;
|
||||
myText = text;
|
||||
myReferenceElement = NotNullLazyValue.atomicLazy(() -> {
|
||||
int index = myText.indexOf('(');
|
||||
String refText = index > 0 ? myText.substring(1, index) : myText.substring(1);
|
||||
return new ClsJavaCodeReferenceElementImpl(this, refText);
|
||||
});
|
||||
myParameterList = NotNullLazyValue.atomicLazy(() -> {
|
||||
PsiNameValuePair[] attrs = myText.indexOf('(') > 0
|
||||
? JavaPsiFacade.getElementFactory(getProject()).createAnnotationFromText(myText, myParent)
|
||||
.getParameterList().getAttributes()
|
||||
: PsiNameValuePair.EMPTY_ARRAY;
|
||||
return new ClsAnnotationParameterListImpl(this, attrs);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiAnnotationParameterList getParameterList() {
|
||||
return myParameterList.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getQualifiedName() {
|
||||
return getNameReferenceElement().getCanonicalText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiJavaCodeReferenceElement getNameReferenceElement() {
|
||||
return myReferenceElement.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationMemberValue findAttributeValue(@Nullable String attributeName) {
|
||||
return PsiImplUtil.findAttributeValue(this, attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationMemberValue findDeclaredAttributeValue(@Nullable String attributeName) {
|
||||
return PsiImplUtil.findDeclaredAttributeValue(this, attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PsiAnnotationMemberValue> T setDeclaredAttributeValue(@Nullable String attributeName, @Nullable T value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationOwner getOwner() {
|
||||
return myOwner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMirrorText(int indentLevel, @NotNull StringBuilder buffer) {
|
||||
buffer.append(myText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return myText;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setMirror(@NotNull TreeElement element) throws InvalidMirrorException {
|
||||
setMirrorCheckingType(element, null);
|
||||
PsiAnnotation mirror = SourceTreeToPsiMap.treeToPsiNotNull(element);
|
||||
setMirror(getNameReferenceElement(), mirror.getNameReferenceElement());
|
||||
setMirror(getParameterList(), mirror.getParameterList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement @NotNull [] getChildren() {
|
||||
return new PsiElement[]{myReferenceElement.getValue(), getParameterList()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement getParent() {
|
||||
return myParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NotNull PsiElementVisitor visitor) {
|
||||
if (visitor instanceof JavaElementVisitor) {
|
||||
((JavaElementVisitor)visitor).visitAnnotation(this);
|
||||
}
|
||||
else {
|
||||
visitor.visitElement(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeAnnotationContainerProvider implements TypeAnnotationProvider {
|
||||
private final PsiElement myParent;
|
||||
private final @Nullable PsiAnnotationOwner myOwner;
|
||||
|
||||
private TypeAnnotationContainerProvider(PsiElement parent, @Nullable PsiAnnotationOwner owner) {
|
||||
myParent = parent;
|
||||
myOwner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationProvider withOwner(@NotNull PsiAnnotationOwner owner) {
|
||||
return new TypeAnnotationContainerProvider(myParent, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiAnnotation @NotNull [] getAnnotations() {
|
||||
List<PsiAnnotation> result = new ArrayList<>();
|
||||
for (TypeAnnotationEntry entry : myList) {
|
||||
if (entry.myPath.length == 0) {
|
||||
PsiAnnotation anno = myParent instanceof PsiCompiledElement ? new ClsTypeAnnotationImpl(myParent, myOwner, entry.myText) :
|
||||
JavaPsiFacade.getElementFactory(myParent.getProject()).createAnnotationFromText(entry.myText, myParent);
|
||||
result.add(anno);
|
||||
}
|
||||
}
|
||||
return result.toArray(PsiAnnotation.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java
vendored
Normal file
75
java/java-psi-impl/src/com/intellij/psi/impl/cache/ExternalTypeAnnotationContainer.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A container that reports external type annotations. External type annotations are described in annotation.xml files
|
||||
* with additional {@code typePath} attribute. The attribute syntax is the following:
|
||||
* <ul>
|
||||
* <li>{@code digit;} - zero-based type argument
|
||||
* <li>{@code *} (asterisk) - bound of a wildcard type
|
||||
* <li>{@code [} (left bracket) - array element
|
||||
* <li>{@code .} (dot) - enclosing type of inner type
|
||||
* </ul>
|
||||
* E.g., for type {@code Consumer<? super T>} the typePath {@code 0;*} points to {@code T}
|
||||
*/
|
||||
public final class ExternalTypeAnnotationContainer implements TypeAnnotationContainer {
|
||||
@NotNull private final String myTypePath;
|
||||
@NotNull private final PsiModifierListOwner myOwner;
|
||||
|
||||
private ExternalTypeAnnotationContainer(@NotNull String typePath, @NotNull PsiModifierListOwner owner) {
|
||||
myTypePath = typePath;
|
||||
myOwner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forArrayElement() {
|
||||
return new ExternalTypeAnnotationContainer(myTypePath + "[", myOwner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forEnclosingClass() {
|
||||
return new ExternalTypeAnnotationContainer(myTypePath + ".", myOwner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forBound() {
|
||||
return new ExternalTypeAnnotationContainer(myTypePath + "*", myOwner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forTypeArgument(int index) {
|
||||
return new ExternalTypeAnnotationContainer(myTypePath + index + ";", myOwner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationProvider getProvider(PsiElement parent) {
|
||||
// We don't expect any top-level type annotations: they will be stored as element (method/field/parameter) annotations,
|
||||
// so let's spare some memory and avoid creating a provider
|
||||
if (myTypePath.isEmpty()) return TypeAnnotationProvider.EMPTY;
|
||||
return new TypeAnnotationProvider() {
|
||||
@Override
|
||||
public @NotNull PsiAnnotation @NotNull [] getAnnotations() {
|
||||
return ExternalAnnotationsManager.getInstance(myOwner.getProject()).findExternalTypeAnnotations(myOwner, myTypePath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,455 +1,97 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache;
|
||||
|
||||
import com.intellij.openapi.util.NotNullLazyValue;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
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;
|
||||
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;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An immutable container that holds all the type annotations for some type (including internal type components).
|
||||
* A container of all type annotations for given type, including structural children of the type
|
||||
* (type arguments, wildcard bounds, outer types, array element types)
|
||||
*/
|
||||
public class TypeAnnotationContainer {
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TypeAnnotationContainer {
|
||||
/**
|
||||
* A container that contains no type annotations.
|
||||
*/
|
||||
public static final TypeAnnotationContainer EMPTY = new TypeAnnotationContainer(Collections.emptyList());
|
||||
TypeAnnotationContainer EMPTY = new TypeAnnotationContainer() {
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forArrayElement() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private final List<TypeAnnotationEntry> myList;
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forEnclosingClass() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private TypeAnnotationContainer(List<TypeAnnotationEntry> entries) {
|
||||
myList = entries;
|
||||
}
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forBound() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationContainer forTypeArgument(int index) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationProvider getProvider(PsiElement parent) {
|
||||
return TypeAnnotationProvider.EMPTY;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return type annotation container for array element
|
||||
* (assuming that this type annotation container is used for the array type)
|
||||
* @return a derived container that contains annotations for an array element,
|
||||
* assuming that this container is used for the array
|
||||
*/
|
||||
public @NotNull TypeAnnotationContainer forArrayElement() {
|
||||
if (isEmpty()) return this;
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.ARRAY_ELEMENT));
|
||||
return list.isEmpty() ? EMPTY : new TypeAnnotationContainer(list);
|
||||
}
|
||||
@NotNull TypeAnnotationContainer forArrayElement();
|
||||
|
||||
/**
|
||||
* @return type annotation container for enclosing class
|
||||
* (assuming that this type annotation container is used for the inner class)
|
||||
* @return a derived container that contains annotations for enclosing class,
|
||||
* assuming that this container is used for the inner class
|
||||
*/
|
||||
public @NotNull TypeAnnotationContainer forEnclosingClass() {
|
||||
if (isEmpty()) return this;
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.ENCLOSING_CLASS));
|
||||
return list.isEmpty() ? EMPTY : new TypeAnnotationContainer(list);
|
||||
}
|
||||
@NotNull TypeAnnotationContainer forEnclosingClass();
|
||||
|
||||
/**
|
||||
* @return type annotation container for wildcard bound
|
||||
* (assuming that this type annotation container is used for the bounded wildcard type)
|
||||
*/
|
||||
public @NotNull TypeAnnotationContainer forBound() {
|
||||
if (isEmpty()) return this;
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, entry -> entry.forPathElement(Collector.WILDCARD_BOUND));
|
||||
return list.isEmpty() ? EMPTY : new TypeAnnotationContainer(list);
|
||||
}
|
||||
@NotNull TypeAnnotationContainer forBound();
|
||||
|
||||
/**
|
||||
* @param index type argument index
|
||||
* Returns a type annotation container for the given type argument index.
|
||||
* This is used for types that have type arguments, and it provides the
|
||||
* annotations associated with a specific type argument.
|
||||
*
|
||||
* @param index type argument index, zero-based
|
||||
* @return type annotation container for given type argument
|
||||
* (assuming that this type annotation container is used for class type with type arguments)
|
||||
* (assuming that this type annotation container is used for a class type with type arguments)
|
||||
*/
|
||||
public @NotNull TypeAnnotationContainer forTypeArgument(int index) {
|
||||
if (isEmpty()) return this;
|
||||
List<TypeAnnotationEntry> list = ContainerUtil.mapNotNull(myList, e -> e.forTypeArgument(index));
|
||||
return list.isEmpty() ? EMPTY : new TypeAnnotationContainer(list);
|
||||
}
|
||||
@NotNull TypeAnnotationContainer forTypeArgument(int index);
|
||||
|
||||
/**
|
||||
* @return true if this type annotation container contains no type annotations
|
||||
* @param parent parent PSI element for context
|
||||
* @return TypeAnnotationProvider
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return myList.isEmpty();
|
||||
}
|
||||
@NotNull TypeAnnotationProvider getProvider(PsiElement parent);
|
||||
|
||||
/**
|
||||
* @param parent parent element for annotations
|
||||
* @return TypeAnnotationProvider that provides all the top-level annotations
|
||||
*/
|
||||
public TypeAnnotationProvider getProvider(PsiElement parent) {
|
||||
if (isEmpty()) return TypeAnnotationProvider.EMPTY;
|
||||
return new TypeAnnotationContainerProvider(parent, ObjectUtils.tryCast(parent, PsiAnnotationOwner.class));
|
||||
}
|
||||
@NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context);
|
||||
|
||||
/**
|
||||
* Creates PsiAnnotationStub elements for top-level annotations in this container
|
||||
*
|
||||
* @param parent parent stub
|
||||
*/
|
||||
public void createAnnotationStubs(StubElement<?> parent) {
|
||||
for (TypeAnnotationEntry entry : myList) {
|
||||
if (entry.myPath.length == 0) {
|
||||
new PsiAnnotationStubImpl(parent, entry.myText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type type
|
||||
* @param context context PsiElement
|
||||
* @return type annotated with annotations from this container
|
||||
*/
|
||||
public @NotNull PsiType applyTo(@NotNull PsiType type, @NotNull PsiElement context) {
|
||||
if (isEmpty()) return type;
|
||||
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();
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
private @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes TypeAnnotationContainer into the supplied stream.
|
||||
*
|
||||
* @param dataStream stream to write to
|
||||
* @param container a container to serialize
|
||||
* @throws IOException if the stream throws
|
||||
*/
|
||||
public static void writeTypeAnnotations(@NotNull StubOutputStream dataStream, @NotNull TypeAnnotationContainer container)
|
||||
throws IOException {
|
||||
dataStream.writeShort(container.myList.size());
|
||||
for (TypeAnnotationEntry entry : container.myList) {
|
||||
dataStream.writeShort(entry.myPath.length);
|
||||
dataStream.write(entry.myPath);
|
||||
dataStream.writeUTFFast(entry.myText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads TypeAnnotationContainer from the supplied stream.
|
||||
*
|
||||
* @param dataStream stream to read from
|
||||
* @return deserialized TypeAnnotationContainer
|
||||
* @throws IOException if the stream throws
|
||||
*/
|
||||
public static @NotNull TypeAnnotationContainer readTypeAnnotations(@NotNull StubInputStream dataStream) throws IOException {
|
||||
short count = dataStream.readShort();
|
||||
TypeAnnotationEntry[] entries = new TypeAnnotationEntry[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
short pathLength = dataStream.readShort();
|
||||
byte[] path = new byte[pathLength];
|
||||
dataStream.readFully(path);
|
||||
String text = dataStream.readUTFFast();
|
||||
entries[i] = new TypeAnnotationEntry(path, text);
|
||||
}
|
||||
return new TypeAnnotationContainer(Arrays.asList(entries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.join(myList, "\n");
|
||||
}
|
||||
|
||||
public static class Collector {
|
||||
public static final byte ARRAY_ELEMENT = 0;
|
||||
public static final byte ENCLOSING_CLASS = 1;
|
||||
public static final byte WILDCARD_BOUND = 2;
|
||||
public static final byte TYPE_ARGUMENT = 3;
|
||||
|
||||
private final @NotNull ArrayList<TypeAnnotationEntry> myList = new ArrayList<>();
|
||||
protected final @NotNull TypeInfo myTypeInfo;
|
||||
|
||||
public Collector(@NotNull TypeInfo info) {
|
||||
myTypeInfo = info;
|
||||
}
|
||||
|
||||
public void add(byte @NotNull [] path, @NotNull String text) {
|
||||
myList.add(new TypeAnnotationEntry(path, text));
|
||||
}
|
||||
|
||||
public void install() {
|
||||
if (myList.isEmpty()) {
|
||||
myTypeInfo.setTypeAnnotations(EMPTY);
|
||||
}
|
||||
else {
|
||||
myList.trimToSize();
|
||||
myTypeInfo.setTypeAnnotations(new TypeAnnotationContainer(myList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TypeAnnotationEntry {
|
||||
/**
|
||||
* path is stored as the sequence of ARRAY_ELEMENT, ENCLOSING_CLASS, WILDCARD_BOUND and TYPE_ARGUMENT bytes.
|
||||
* The TYPE_ARGUMENT byte is followed by the type argument index byte.
|
||||
*/
|
||||
final byte @NotNull [] myPath;
|
||||
final @NotNull String myText;
|
||||
|
||||
private TypeAnnotationEntry(byte @NotNull [] path, @NotNull String text) {
|
||||
myPath = path.length == 0 ? ArrayUtil.EMPTY_BYTE_ARRAY : path;
|
||||
myText = text;
|
||||
}
|
||||
|
||||
private TypeAnnotationEntry forPathElement(int wanted) {
|
||||
if (myPath.length > 0 && myPath[0] == wanted) {
|
||||
return new TypeAnnotationEntry(Arrays.copyOfRange(myPath, 1, myPath.length), myText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TypeAnnotationEntry forTypeArgument(int index) {
|
||||
if (myPath.length > 1 && myPath[0] == Collector.TYPE_ARGUMENT && myPath[1] == index) {
|
||||
return new TypeAnnotationEntry(Arrays.copyOfRange(myPath, 2, myPath.length), myText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int pos = 0;
|
||||
while (pos < myPath.length) {
|
||||
switch (myPath[pos]) {
|
||||
case Collector.ARRAY_ELEMENT:
|
||||
result.append('[');
|
||||
break;
|
||||
case Collector.ENCLOSING_CLASS:
|
||||
result.append('.');
|
||||
break;
|
||||
case Collector.WILDCARD_BOUND:
|
||||
result.append('*');
|
||||
break;
|
||||
case Collector.TYPE_ARGUMENT:
|
||||
result.append(myPath[++pos]).append(';');
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return result + "->" + myText;
|
||||
}
|
||||
}
|
||||
|
||||
static class ClsTypeAnnotationImpl extends ClsElementImpl implements PsiAnnotation {
|
||||
private final NotNullLazyValue<ClsJavaCodeReferenceElementImpl> myReferenceElement;
|
||||
private final NotNullLazyValue<ClsAnnotationParameterListImpl> myParameterList;
|
||||
private final PsiElement myParent;
|
||||
private final @Nullable PsiAnnotationOwner myOwner;
|
||||
private final String myText;
|
||||
|
||||
ClsTypeAnnotationImpl(PsiElement parent, @Nullable PsiAnnotationOwner owner, String text) {
|
||||
myParent = parent;
|
||||
myOwner = owner;
|
||||
myText = text;
|
||||
myReferenceElement = NotNullLazyValue.atomicLazy(() -> {
|
||||
int index = myText.indexOf('(');
|
||||
String refText = index > 0 ? myText.substring(1, index) : myText.substring(1);
|
||||
return new ClsJavaCodeReferenceElementImpl(this, refText);
|
||||
});
|
||||
myParameterList = NotNullLazyValue.atomicLazy(() -> {
|
||||
PsiNameValuePair[] attrs = myText.indexOf('(') > 0
|
||||
? JavaPsiFacade.getElementFactory(getProject()).createAnnotationFromText(myText, myParent)
|
||||
.getParameterList().getAttributes()
|
||||
: PsiNameValuePair.EMPTY_ARRAY;
|
||||
return new ClsAnnotationParameterListImpl(this, attrs);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiAnnotationParameterList getParameterList() {
|
||||
return myParameterList.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getQualifiedName() {
|
||||
return getNameReferenceElement().getCanonicalText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiJavaCodeReferenceElement getNameReferenceElement() {
|
||||
return myReferenceElement.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationMemberValue findAttributeValue(@Nullable String attributeName) {
|
||||
return PsiImplUtil.findAttributeValue(this, attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationMemberValue findDeclaredAttributeValue(@Nullable String attributeName) {
|
||||
return PsiImplUtil.findDeclaredAttributeValue(this, attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PsiAnnotationMemberValue> T setDeclaredAttributeValue(@Nullable String attributeName, @Nullable T value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiAnnotationOwner getOwner() {
|
||||
return myOwner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMirrorText(int indentLevel, @NotNull StringBuilder buffer) {
|
||||
buffer.append(myText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return myText;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setMirror(@NotNull TreeElement element) throws InvalidMirrorException {
|
||||
setMirrorCheckingType(element, null);
|
||||
PsiAnnotation mirror = SourceTreeToPsiMap.treeToPsiNotNull(element);
|
||||
setMirror(getNameReferenceElement(), mirror.getNameReferenceElement());
|
||||
setMirror(getParameterList(), mirror.getParameterList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement @NotNull [] getChildren() {
|
||||
return new PsiElement[]{myReferenceElement.getValue(), getParameterList()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement getParent() {
|
||||
return myParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NotNull PsiElementVisitor visitor) {
|
||||
if (visitor instanceof JavaElementVisitor) {
|
||||
((JavaElementVisitor)visitor).visitAnnotation(this);
|
||||
}
|
||||
else {
|
||||
visitor.visitElement(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeAnnotationContainerProvider implements TypeAnnotationProvider {
|
||||
private final PsiElement myParent;
|
||||
private final @Nullable PsiAnnotationOwner myOwner;
|
||||
|
||||
private TypeAnnotationContainerProvider(PsiElement parent, @Nullable PsiAnnotationOwner owner) {
|
||||
myParent = parent;
|
||||
myOwner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TypeAnnotationProvider withOwner(@NotNull PsiAnnotationOwner owner) {
|
||||
return new TypeAnnotationContainerProvider(myParent, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiAnnotation @NotNull [] getAnnotations() {
|
||||
List<PsiAnnotation> result = new ArrayList<>();
|
||||
for (TypeAnnotationEntry entry : myList) {
|
||||
if (entry.myPath.length == 0) {
|
||||
PsiAnnotation anno = myParent instanceof PsiCompiledElement ? new ClsTypeAnnotationImpl(myParent, myOwner, entry.myText) :
|
||||
JavaPsiFacade.getElementFactory(myParent.getProject()).createAnnotationFromText(entry.myText, myParent);
|
||||
result.add(anno);
|
||||
}
|
||||
}
|
||||
return result.toArray(PsiAnnotation.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
@NotNull PsiJavaCodeReferenceElement annotateReference(@NotNull PsiJavaCodeReferenceElement reference,
|
||||
@NotNull PsiElement context);
|
||||
}
|
||||
|
||||
@@ -417,15 +417,15 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
typeInfo = typeInfo.arrayOf();
|
||||
}
|
||||
byte[] prefix = new byte[arrayCount];
|
||||
Arrays.fill(prefix, TypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
TypeAnnotationContainer.Collector collector = new TypeAnnotationContainer.Collector(typeInfo);
|
||||
Arrays.fill(prefix, ExplicitTypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
ExplicitTypeAnnotationContainer.Collector collector = new ExplicitTypeAnnotationContainer.Collector(typeInfo);
|
||||
collectAnnotations(typeInfo, collector, tree, typeElement, prefix);
|
||||
collector.install();
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
private static void collectAnnotations(@NotNull TypeInfo info,
|
||||
@NotNull TypeAnnotationContainer.Collector collector,
|
||||
@NotNull ExplicitTypeAnnotationContainer.Collector collector,
|
||||
@NotNull LighterAST tree,
|
||||
@NotNull LighterASTNode element,
|
||||
byte @NotNull [] prefix) {
|
||||
@@ -451,10 +451,10 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
byte[] newPrefix;
|
||||
if (bound) {
|
||||
newPrefix = Arrays.copyOf(prefix, prefix.length + 1);
|
||||
newPrefix[prefix.length] = TypeAnnotationContainer.Collector.WILDCARD_BOUND;
|
||||
newPrefix[prefix.length] = ExplicitTypeAnnotationContainer.Collector.WILDCARD_BOUND;
|
||||
} else {
|
||||
newPrefix = Arrays.copyOf(prefix, prefix.length + arrayCount);
|
||||
Arrays.fill(newPrefix, prefix.length, newPrefix.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
Arrays.fill(newPrefix, prefix.length, newPrefix.length, ExplicitTypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
}
|
||||
collectAnnotations(((DerivedTypeInfo)info).child(), collector, tree, child, newPrefix);
|
||||
}
|
||||
@@ -464,14 +464,14 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
else if (tokenType == JavaElementType.ANNOTATION) {
|
||||
String anno = LightTreeUtil.toFilteredString(tree, child, null);
|
||||
byte[] typePath = Arrays.copyOf(prefix, prefix.length + nestingLevel);
|
||||
Arrays.fill(typePath, prefix.length, typePath.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
Arrays.fill(typePath, prefix.length, typePath.length, ExplicitTypeAnnotationContainer.Collector.ARRAY_ELEMENT);
|
||||
collector.add(typePath, anno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectAnnotationsFromReference(@NotNull RefTypeInfo info,
|
||||
TypeAnnotationContainer.@NotNull Collector collector,
|
||||
ExplicitTypeAnnotationContainer.@NotNull Collector collector,
|
||||
@NotNull LighterAST tree,
|
||||
@NotNull LighterASTNode child,
|
||||
byte @NotNull [] prefix) {
|
||||
@@ -482,7 +482,7 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
RefTypeInfo outerType = info.outerType();
|
||||
if (outerType != null) {
|
||||
byte[] newPrefix = Arrays.copyOf(prefix, prefix.length + 1);
|
||||
newPrefix[prefix.length] = TypeAnnotationContainer.Collector.ENCLOSING_CLASS;
|
||||
newPrefix[prefix.length] = ExplicitTypeAnnotationContainer.Collector.ENCLOSING_CLASS;
|
||||
collectAnnotationsFromReference(outerType, collector, tree, refChild, newPrefix);
|
||||
}
|
||||
}
|
||||
@@ -493,7 +493,7 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
TypeInfo componentInfo = info.genericComponent(i);
|
||||
if (componentInfo != null) {
|
||||
byte[] newPrefix = Arrays.copyOf(prefix, prefix.length + 2);
|
||||
newPrefix[prefix.length] = TypeAnnotationContainer.Collector.TYPE_ARGUMENT;
|
||||
newPrefix[prefix.length] = ExplicitTypeAnnotationContainer.Collector.TYPE_ARGUMENT;
|
||||
newPrefix[prefix.length + 1] = (byte)i;
|
||||
collectAnnotations(componentInfo, collector, tree, subTypes.get(i), newPrefix);
|
||||
}
|
||||
@@ -700,12 +700,12 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
default:
|
||||
info = kind.isReference() ? new RefTypeInfo(Objects.requireNonNull(kind.text)) : new SimpleTypeInfo(kind);
|
||||
}
|
||||
info.setTypeAnnotations(hasTypeAnnotations ? TypeAnnotationContainer.readTypeAnnotations(record) : TypeAnnotationContainer.EMPTY);
|
||||
info.setTypeAnnotations(hasTypeAnnotations ? ExplicitTypeAnnotationContainer.readTypeAnnotations(record) : TypeAnnotationContainer.EMPTY);
|
||||
return info;
|
||||
}
|
||||
|
||||
public static void writeTYPE(@NotNull StubOutputStream dataStream, @NotNull TypeInfo typeInfo) throws IOException {
|
||||
boolean hasTypeAnnotations = typeInfo.myTypeAnnotations != null && !typeInfo.myTypeAnnotations.isEmpty();
|
||||
boolean hasTypeAnnotations = typeInfo.myTypeAnnotations instanceof ExplicitTypeAnnotationContainer;
|
||||
dataStream.writeByte(typeInfo.kind.ordinal() | (hasTypeAnnotations ? HAS_TYPE_ANNOTATIONS : 0));
|
||||
|
||||
if (typeInfo instanceof DerivedTypeInfo) {
|
||||
@@ -728,7 +728,7 @@ public /*sealed*/ abstract class TypeInfo {
|
||||
}
|
||||
}
|
||||
if (hasTypeAnnotations) {
|
||||
TypeAnnotationContainer.writeTypeAnnotations(dataStream, typeInfo.myTypeAnnotations);
|
||||
ExplicitTypeAnnotationContainer.writeTypeAnnotations(dataStream, (ExplicitTypeAnnotationContainer)typeInfo.myTypeAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P
|
||||
myAnnotations = annotations;
|
||||
String prefix = PsiNameHelper.getOuterClassReference(canonicalText);
|
||||
TypeAnnotationContainer container = prefix.isEmpty() ? TypeAnnotationContainer.EMPTY : annotations.forEnclosingClass();
|
||||
myQualifier = container.isEmpty() ? null : new ClsJavaCodeReferenceElementImpl(this, prefix, container);
|
||||
myQualifier = container == TypeAnnotationContainer.EMPTY ? null : new ClsJavaCodeReferenceElementImpl(this, prefix, container);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.impl.compiled;
|
||||
|
||||
import com.intellij.psi.impl.cache.TypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.ExplicitTypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.TypeInfo;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.org.objectweb.asm.TypePath;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
class ClsTypeAnnotationCollector extends TypeAnnotationContainer.Collector {
|
||||
class ClsTypeAnnotationCollector extends ExplicitTypeAnnotationContainer.Collector {
|
||||
private final @NotNull FirstPassData myFirstPassData;
|
||||
|
||||
ClsTypeAnnotationCollector(@NotNull TypeInfo info, @NotNull FirstPassData classInfo) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.util.NullableLazyValue;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import com.intellij.psi.impl.PsiJavaParserFacadeImpl;
|
||||
import com.intellij.psi.impl.cache.ExternalTypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.TypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.TypeInfo;
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType;
|
||||
@@ -52,7 +53,8 @@ public class ClsTypeElementImpl extends ClsElementImpl implements PsiTypeElement
|
||||
myParent = parent;
|
||||
myTypeText = TypeInfo.internFrequentType(typeText);
|
||||
myVariance = variance;
|
||||
myAnnotations = annotations;
|
||||
myAnnotations = annotations == TypeAnnotationContainer.EMPTY && parent instanceof PsiModifierListOwner ?
|
||||
ExternalTypeAnnotationContainer.create((PsiModifierListOwner)parent) : annotations;
|
||||
myChild = atomicLazyNullable(() -> calculateChild());
|
||||
myCachedType = atomicLazy(() -> calculateType());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.compiled;
|
||||
|
||||
import com.intellij.psi.impl.cache.ExplicitTypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.TypeAnnotationContainer;
|
||||
import com.intellij.psi.impl.cache.TypeInfo;
|
||||
import com.intellij.psi.impl.cache.TypeInfo.TypeKind;
|
||||
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
|
||||
@@ -26,7 +28,7 @@ public final class SignatureParsing {
|
||||
private SignatureParsing() { }
|
||||
|
||||
/**
|
||||
* A function to map JVM class names to {@link com.intellij.psi.impl.cache.TypeInfo.RefTypeInfo}.
|
||||
* A function to map JVM class names to {@link TypeInfo.RefTypeInfo}.
|
||||
* Normally, this function should take into account probable inner classes. This is done by {@link FirstPassData} implementation.
|
||||
* If inner classes information is unavailable, use {@link StubBuildingVisitor#GUESSING_PROVIDER} for heuristic-based mapping
|
||||
*/
|
||||
@@ -138,7 +140,10 @@ public final class SignatureParsing {
|
||||
|
||||
private void createTypeParameter(PsiTypeParameterListStub listStub) {
|
||||
PsiTypeParameterStub stub = new PsiTypeParameterStubImpl(listStub, this.myTypeParameter.text());
|
||||
myTypeParameter.getTypeAnnotations().createAnnotationStubs(stub);
|
||||
TypeAnnotationContainer annotations = myTypeParameter.getTypeAnnotations();
|
||||
if (annotations instanceof ExplicitTypeAnnotationContainer) {
|
||||
((ExplicitTypeAnnotationContainer)annotations).createAnnotationStubs(stub);
|
||||
}
|
||||
TypeInfo[] info = this.myBounds;
|
||||
if (info.length > 0 && info[0] == null) {
|
||||
info = Arrays.copyOfRange(info, 1, info.length);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import typeUse.*;
|
||||
|
||||
public class CompletableFutureWhenComplete {
|
||||
native CompletableFuture<@NotNull String> supply();
|
||||
|
||||
void test() {
|
||||
supply().whenComplete((s, t) -> {
|
||||
if (t != null) {
|
||||
System.out.println(t);
|
||||
}
|
||||
if (s != null) {
|
||||
System.out.println(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -110,10 +110,46 @@ public class ExternalAnnotationsManagerTest extends LightPlatformTestCase {
|
||||
PsiAnnotation annotation = annotationData.getAnnotation(manager);
|
||||
String nameText = annotation.getNameReferenceElement().getText();
|
||||
assertClassFqn(nameText, psiFile, externalName, null);
|
||||
|
||||
String typePath = annotationData.getTypePath();
|
||||
if (typePath != null) {
|
||||
String error = validatePath(typePath);
|
||||
if (error != null) {
|
||||
fail("Invalid typePath: " + error, psiFile, externalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String validatePath(String pathString) {
|
||||
if (pathString.isEmpty()) {
|
||||
return "Empty path";
|
||||
}
|
||||
for (int i = 0; i < pathString.length(); i++) {
|
||||
char c = pathString.charAt(i);
|
||||
switch (c) {
|
||||
case '[', '*', '.':
|
||||
break;
|
||||
default:
|
||||
if (c >= '0' && c <= '9') {
|
||||
int j = i;
|
||||
while (j < pathString.length() && pathString.charAt(j) >= '0' && pathString.charAt(j) <= '9') {
|
||||
j++;
|
||||
}
|
||||
int result = Integer.parseInt(pathString.substring(i, j));
|
||||
if (result >= 0 && result <= 255 && j < pathString.length() && pathString.charAt(j) == ';') {
|
||||
//noinspection AssignmentToForLoopParameter
|
||||
i = j; // Skip the ';' character
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "Invalid path: " + pathString;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PsiClass assertClassFqn(@NotNull String text,
|
||||
@NotNull PsiFile psiFile,
|
||||
@NotNull String externalName,
|
||||
|
||||
@@ -401,4 +401,8 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
public void testConsumedStreamWithoutInline() { doTest(); }
|
||||
public void testLocalityAndConditionalExpression() { doTest(); }
|
||||
public void testParallelStreamThreadId() { doTest(); }
|
||||
public void testCompletableFutureWhenComplete() {
|
||||
setupTypeUseAnnotations("typeUse", myFixture);
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
@@ -3703,6 +3703,7 @@
|
||||
</item>
|
||||
<item name='java.util.Optional java.util.Optional<T> filter(java.util.function.Predicate<? super T>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.NotNull' typePath="0;*"/>
|
||||
</item>
|
||||
<item name='java.util.Optional java.util.Optional<T> of(T) 0'>
|
||||
<annotation name='org.intellij.lang.annotations.Flow'>
|
||||
@@ -3717,15 +3718,19 @@
|
||||
<item
|
||||
name='java.util.Optional java.util.Optional<U> flatMap(java.util.function.Function<? super T,? extends java.util.Optional<? extends U>>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.NotNull' typePath="0;*"/>
|
||||
</item>
|
||||
<item name='java.util.Optional java.util.Optional<U> map(java.util.function.Function<? super T,? extends U>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.NotNull' typePath="0;*"/>
|
||||
</item>
|
||||
<item name='java.util.Optional void ifPresent(java.util.function.Consumer<? super T>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.NotNull' typePath="0;*"/>
|
||||
</item>
|
||||
<item name='java.util.Optional void ifPresentOrElse(java.util.function.Consumer<? super T>, java.lang.Runnable) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.NotNull' typePath="0;*"/>
|
||||
</item>
|
||||
<item name='java.util.Properties java.lang.String getProperty(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NonNls'/>
|
||||
|
||||
@@ -241,6 +241,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<T> whenComplete(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>)'>
|
||||
@@ -249,6 +251,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>, java.util.concurrent.Executor)'>
|
||||
@@ -257,6 +261,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>, java.util.concurrent.Executor) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture<U> applyToEither(java.util.concurrent.CompletionStage<? extends T>, java.util.function.Function<? super T,U>)'>
|
||||
@@ -707,6 +713,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<T> whenComplete(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>)'>
|
||||
@@ -715,6 +723,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>, java.util.concurrent.Executor)'>
|
||||
@@ -723,6 +733,8 @@
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<T> whenCompleteAsync(java.util.function.BiConsumer<? super T,? super java.lang.Throwable>, java.util.concurrent.Executor) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="0;*"/>
|
||||
<annotation name='org.jetbrains.annotations.UnknownNullability' typePath="1;*"/>
|
||||
</item>
|
||||
<item
|
||||
name='java.util.concurrent.CompletionStage java.util.concurrent.CompletionStage<U> applyToEither(java.util.concurrent.CompletionStage<? extends T>, java.util.function.Function<? super T,U>)'>
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.stream.Stream
|
||||
fun test(list: /*T1@*/List</*T0@*/String>) {
|
||||
val x: /*T9@*/List</*T8@*/String> = list/*T1@List<T0@String>*/.stream()/*Stream<T0@String>!!L*/
|
||||
.map</*T3@*/String>({ x: /*T2@*/String -> x/*T2@String*/ + ""/*LIT*//*LIT*/ }/*Function1<T2@String, T10@String>!!L*/)/*Stream<T3@String>*/
|
||||
.collect</*T6@*/List</*T5@*/String>, /*T7@*/Any>(Collectors/*LIT*/.toList</*T4@*/String>()/*Collector<T4@String, *, MutableList<T4@String>>*/)/*T6@List<T5@String>*/
|
||||
.collect</*T6@*/List</*T5@*/String>, /*T7@*/Any>(Collectors/*LIT*/.toList</*T4@*/String>()/*Collector<T4@String, *, MutableList<T4@String>>!!L*/)/*T6@List<T5@String>*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user