[groovy] support generics in compiled traits (& tests)

This commit is contained in:
Daniil Ovchinnikov
2015-08-31 16:54:38 +03:00
parent 637991005d
commit 5f448fea77
5 changed files with 188 additions and 112 deletions
@@ -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();
}
}
@@ -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);
@@ -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;
}
}
@@ -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)
Binary file not shown.