mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[groovy] support generics in compiled traits (& tests)
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.psi.impl;
|
||||
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiSubstitutor;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DelegateSubstitutor implements PsiSubstitutor {
|
||||
|
||||
private final @NotNull PsiSubstitutor myDelegate;
|
||||
|
||||
public DelegateSubstitutor(@NotNull PsiSubstitutor delegate) {
|
||||
myDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiType substitute(@NotNull PsiTypeParameter typeParameter) {
|
||||
return myDelegate.substitute(typeParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiType substitute(@Nullable PsiType type) {
|
||||
return myDelegate.substitute(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiType substituteWithBoundsPromotion(@NotNull PsiTypeParameter typeParameter) {
|
||||
return myDelegate.substituteWithBoundsPromotion(typeParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiSubstitutor put(@NotNull PsiTypeParameter classParameter, PsiType mapping) {
|
||||
return myDelegate.put(classParameter, mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiSubstitutor putAll(@NotNull PsiClass parentClass, PsiType[] mappings) {
|
||||
return myDelegate.putAll(parentClass, mappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiSubstitutor putAll(@NotNull PsiSubstitutor another) {
|
||||
return myDelegate.putAll(another);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<PsiTypeParameter, PsiType> getSubstitutionMap() {
|
||||
return myDelegate.getSubstitutionMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return myDelegate.isValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ensureValid() {
|
||||
myDelegate.ensureValid();
|
||||
}
|
||||
}
|
||||
+5
-26
@@ -21,7 +21,6 @@ import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.SimpleModificationTracker;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.DelegateSubstitutor;
|
||||
import com.intellij.psi.impl.compiled.ClsClassImpl;
|
||||
import com.intellij.psi.infos.CandidateInfo;
|
||||
import com.intellij.psi.util.*;
|
||||
@@ -38,7 +37,10 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAc
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrReflectedMethod;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.*;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightField;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitField;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitMethod;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.util.GrClassImplUtil;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.GroovyTraitFieldsFileIndex.TraitFieldDescriptor;
|
||||
@@ -283,40 +285,17 @@ public class GrTypeDefinitionMembersCache {
|
||||
final PsiType classType = TypesUtil.createJavaLangClassType(
|
||||
psiFacade.getElementFactory().createType(trait), trait.getProject(), trait.getResolveScope()
|
||||
);
|
||||
final PsiSubstitutor delegateSubstitutor = getSubstitutor(substitutor, trait);
|
||||
for (PsiMethod method : traitHelper.getMethods()) {
|
||||
if (!method.hasModifierProperty(PsiModifier.STATIC)) continue;
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
if (parameters.length <= 0) continue;
|
||||
final PsiParameter self = parameters[0];
|
||||
if (self.getType().equals(classType)) {
|
||||
addCandidate(GrGdkMethodImpl.createGdkMethod(method, true, "via @Trait"), delegateSubstitutor);
|
||||
addCandidate(GrTraitUtil.createTraitMethodFromCompiledHelperMethod(method, trait), substitutor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DelegateSubstitutor getSubstitutor(@NotNull final PsiSubstitutor substitutor,
|
||||
@NotNull final PsiClass trait) {
|
||||
final Map<String, PsiType> substitutionMap = ContainerUtil.newTroveMap();
|
||||
for (PsiTypeParameter parameter : trait.getTypeParameters()) {
|
||||
substitutionMap.put(parameter.getName(), substitutor.substitute(parameter));
|
||||
}
|
||||
return new DelegateSubstitutor(substitutor) {
|
||||
@Override
|
||||
public PsiType substitute(@Nullable PsiType type) {
|
||||
final PsiType substituted = super.substitute(type);
|
||||
if (type != null && (substituted == null || substituted.equals(type))) {
|
||||
final PsiType byName = substitutionMap.get(type.getCanonicalText());
|
||||
return byName == null ? substituted : byName;
|
||||
}
|
||||
else {
|
||||
return substituted;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}.getResult();
|
||||
for (CandidateInfo candidateInfo : concreteTraitMethods) {
|
||||
List<GrMethod> methodsToAdd = getExpandingMethods(candidateInfo);
|
||||
|
||||
+73
-2
@@ -17,13 +17,17 @@ package org.jetbrains.plugins.groovy.lang.psi.util;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.compiled.ClsClassImpl;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.psi.PsiModifier.ABSTRACT;
|
||||
|
||||
@@ -68,4 +72,71 @@ public class GrTraitUtil {
|
||||
&& containingClass.isInterface()
|
||||
&& AnnotationUtil.isAnnotated(containingClass, "groovy.transform.Trait", false);
|
||||
}
|
||||
|
||||
public static PsiMethod createTraitMethodFromCompiledHelperMethod(final PsiMethod compiledMethod, PsiClass trait) {
|
||||
assert compiledMethod.getParameterList().getParametersCount() > 0;
|
||||
|
||||
final GrLightMethodBuilder result = new GrLightMethodBuilder(compiledMethod.getManager(), compiledMethod.getName());
|
||||
result.setNavigationElement(compiledMethod);
|
||||
result.setOriginInfo("via @Trait");
|
||||
result.addModifier(PsiModifier.STATIC);
|
||||
for (PsiTypeParameter parameter : compiledMethod.getTypeParameters()) {
|
||||
result.getTypeParameterList().addParameter(parameter);
|
||||
}
|
||||
|
||||
final Map<String, PsiTypeParameter> substitutionMap = ContainerUtil.newTroveMap();
|
||||
for (PsiTypeParameter parameter : trait.getTypeParameters()) {
|
||||
substitutionMap.put(parameter.getName(), parameter);
|
||||
}
|
||||
|
||||
final PsiElementFactory myElementFactory = JavaPsiFacade.getInstance(compiledMethod.getProject()).getElementFactory();
|
||||
final PsiTypeVisitor<PsiType> corrector = new PsiTypeMapper() {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiType visitClassType(PsiClassType classType) {
|
||||
final PsiClass resolved = classType.resolve();
|
||||
// if resolved to method parameter -> return as is
|
||||
if (resolved instanceof PsiTypeParameter && compiledMethod.equals(((PsiTypeParameter)resolved).getOwner())) return classType;
|
||||
if (resolved == null) {
|
||||
// if not resolved -> try to get from map
|
||||
final PsiTypeParameter byName = substitutionMap.get(classType.getCanonicalText());
|
||||
return byName == null ? classType : myElementFactory.createType(byName);
|
||||
}
|
||||
else {
|
||||
// if resolved -> get from map anyways
|
||||
final PsiTypeParameter byName = substitutionMap.get(resolved.getName());
|
||||
final PsiTypeVisitor<PsiType> $this = this;
|
||||
final PsiType[] substitutes = !classType.hasParameters() ? PsiType.EMPTY_ARRAY : ContainerUtil.map2Array(
|
||||
classType.getParameters(), PsiType.class, new Function<PsiType, PsiType>() {
|
||||
@Override
|
||||
public PsiType fun(PsiType type) {
|
||||
return type.accept($this);
|
||||
}
|
||||
}
|
||||
);
|
||||
return myElementFactory.createType(byName != null ? byName : resolved, substitutes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 1; i < compiledMethod.getParameterList().getParameters().length; i++) {
|
||||
final PsiParameter originalParameter = compiledMethod.getParameterList().getParameters()[i];
|
||||
final PsiType originalType = originalParameter.getType();
|
||||
final PsiType correctedType = trait.hasTypeParameters() ? originalType.accept(corrector) : originalType;
|
||||
result.addParameter(originalParameter.getName(), correctedType, false);
|
||||
}
|
||||
|
||||
for (PsiClassType type : compiledMethod.getThrowsList().getReferencedTypes()) {
|
||||
final PsiType correctedType = trait.hasTypeParameters() ? type.accept(corrector) : type;
|
||||
result.getThrowsList().addReference(correctedType instanceof PsiClassType ? (PsiClassType)correctedType : type);
|
||||
}
|
||||
|
||||
{
|
||||
final PsiType originalType = compiledMethod.getReturnType();
|
||||
result.setReturnType(originalType != null && trait.hasTypeParameters() ? originalType.accept(corrector) : originalType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+110
@@ -18,6 +18,8 @@ package org.jetbrains.plugins.groovy.lang.resolve
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.ContentEntry
|
||||
import com.intellij.openapi.roots.ModifiableRootModel
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.impl.compiled.ClsClassImpl
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.PsiTestUtil
|
||||
@@ -25,6 +27,7 @@ import groovy.transform.CompileStatic
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.plugins.groovy.GroovyFileType
|
||||
import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitField
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitMethod
|
||||
import org.jetbrains.plugins.groovy.util.TestUtils
|
||||
@@ -176,6 +179,113 @@ class ExternalConcrete implements somepackage.TT {
|
||||
}'''
|
||||
}
|
||||
|
||||
void 'test trait parameter not within its bounds'() {
|
||||
testHighlighting '''\
|
||||
class ExternalConcrete2 implements somepackage.GenericTrait<String, somepackage.Pojo, <warning descr="Type parameter 'java.lang.Integer' is not in its bound; should extend 'A'">Integer</warning>> {}
|
||||
'''
|
||||
}
|
||||
|
||||
void 'test generic trait method'() {
|
||||
def definition = configureTraitInheritor()
|
||||
def method = definition.findMethodsByName("methodWithTraitGenerics", false)[0]
|
||||
assert method.typeParameters.length == 0
|
||||
assert method.returnType.canonicalText == "Pojo"
|
||||
assert method.parameterList.parametersCount == 2
|
||||
assert method.parameterList.parameters[0].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
void 'test generic trait method with type parameters'() {
|
||||
configureTraitInheritor()
|
||||
def reference = configureByText(
|
||||
'foo.groovy',
|
||||
'new ExternalConcrete().<Integer>methodWit<caret>hMethodGenerics(1, "2", null)',
|
||||
GrReferenceExpression
|
||||
)
|
||||
def resolved = reference.advancedResolve()
|
||||
def method = resolved.element as PsiMethod
|
||||
def substitutor = resolved.substitutor
|
||||
assert method.typeParameterList.typeParameters.length == 1
|
||||
assert substitutor.substitute(method.returnType).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parametersCount == 3
|
||||
assert substitutor.substitute(method.parameterList.parameters[0].type).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[2].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
void 'test generic trait method type parameters clashing'() {
|
||||
configureTraitInheritor()
|
||||
def reference = configureByText(
|
||||
'foo.groovy',
|
||||
'new ExternalConcrete().<Integer>methodWith<caret>MethodGenericsClashing(1,"", new PojoInheritor())',
|
||||
GrReferenceExpression
|
||||
)
|
||||
def resolved = reference.advancedResolve()
|
||||
def method = resolved.element as PsiMethod
|
||||
def substitutor = resolved.substitutor
|
||||
assert method.typeParameterList.typeParameters.length == 1
|
||||
assert substitutor.substitute(method.returnType).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parametersCount == 3
|
||||
assert substitutor.substitute(method.parameterList.parameters[0].type).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[2].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
|
||||
void 'test generic trait static method'() {
|
||||
def definition = configureTraitInheritor()
|
||||
def method = definition.findMethodsByName("staticMethodWithTraitGenerics", false)[0]
|
||||
assert method.typeParameters.length == 0
|
||||
assert method.returnType.canonicalText == "Pojo"
|
||||
assert method.parameterList.parametersCount == 2
|
||||
assert method.parameterList.parameters[0].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
void 'test generic trait static method with type parameters'() {
|
||||
configureTraitInheritor()
|
||||
def reference = configureByText(
|
||||
'foo.groovy',
|
||||
'new ExternalConcrete().<Integer>staticMethodWit<caret>hMethodGenerics(1, "2", null)',
|
||||
GrReferenceExpression
|
||||
)
|
||||
def resolved = reference.advancedResolve()
|
||||
def method = resolved.element as PsiMethod
|
||||
def substitutor = resolved.substitutor
|
||||
assert method.typeParameterList.typeParameters.length == 1
|
||||
assert substitutor.substitute(method.returnType).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parametersCount == 3
|
||||
assert substitutor.substitute(method.parameterList.parameters[0].type).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[2].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
void 'test generic trait static method type parameters clashing'() {
|
||||
configureTraitInheritor()
|
||||
def reference = configureByText(
|
||||
'foo.groovy',
|
||||
'new ExternalConcrete().<Integer>staticMethodWith<caret>MethodGenericsClashing(1,"", new PojoInheritor())',
|
||||
GrReferenceExpression
|
||||
)
|
||||
def resolved = reference.advancedResolve()
|
||||
def method = resolved.element as PsiMethod
|
||||
def substitutor = resolved.substitutor
|
||||
assert method.typeParameterList.typeParameters.length == 1
|
||||
assert substitutor.substitute(method.returnType).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parametersCount == 3
|
||||
assert substitutor.substitute(method.parameterList.parameters[0].type).canonicalText == "java.lang.Integer"
|
||||
assert method.parameterList.parameters[1].type.canonicalText == "java.lang.String"
|
||||
assert method.parameterList.parameters[2].type.canonicalText == "PojoInheritor"
|
||||
}
|
||||
|
||||
private PsiClass configureTraitInheritor() {
|
||||
myFixture.addFileToProject "inheritors.groovy", '''\
|
||||
class PojoInheritor extends somepackage.Pojo {}
|
||||
class ExternalConcrete implements somepackage.GenericTrait<Pojo, String, PojoInheritor> {}
|
||||
'''
|
||||
myFixture.findClass("ExternalConcrete")
|
||||
}
|
||||
|
||||
private testHighlighting(String text) {
|
||||
myFixture.configureByText(GroovyFileType.GROOVY_FILE_TYPE, text)
|
||||
myFixture.testHighlighting(true, false, true)
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user