From 5f448fea77eaa999818e62e31ad60ebf42d43b27 Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Mon, 31 Aug 2015 16:27:20 +0300 Subject: [PATCH] [groovy] support generics in compiled traits (& tests) --- .../psi/impl/DelegateSubstitutor.java | 84 ------------- .../typedef/GrTypeDefinitionMembersCache.java | 31 +---- .../groovy/lang/psi/util/GrTraitUtil.java | 75 +++++++++++- .../resolve/ResolveCompiledTraitTest.groovy | 110 ++++++++++++++++++ plugins/groovy/testdata/lib/some-library.jar | Bin 8715 -> 15709 bytes 5 files changed, 188 insertions(+), 112 deletions(-) delete mode 100644 java/java-psi-impl/src/com/intellij/psi/impl/DelegateSubstitutor.java diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/DelegateSubstitutor.java b/java/java-psi-impl/src/com/intellij/psi/impl/DelegateSubstitutor.java deleted file mode 100644 index fa8b1bef1a24..000000000000 --- a/java/java-psi-impl/src/com/intellij/psi/impl/DelegateSubstitutor.java +++ /dev/null @@ -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 getSubstitutionMap() { - return myDelegate.getSubstitutionMap(); - } - - @Override - public boolean isValid() { - return myDelegate.isValid(); - } - - @Override - public void ensureValid() { - myDelegate.ensureValid(); - } -} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrTypeDefinitionMembersCache.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrTypeDefinitionMembersCache.java index 88f0162218db..945cdb422106 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrTypeDefinitionMembersCache.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/typedef/GrTypeDefinitionMembersCache.java @@ -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 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 methodsToAdd = getExpandingMethods(candidateInfo); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GrTraitUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GrTraitUtil.java index bc5d3dfb64d2..8af949509fd6 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GrTraitUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GrTraitUtil.java @@ -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 substitutionMap = ContainerUtil.newTroveMap(); + for (PsiTypeParameter parameter : trait.getTypeParameters()) { + substitutionMap.put(parameter.getName(), parameter); + } + + final PsiElementFactory myElementFactory = JavaPsiFacade.getInstance(compiledMethod.getProject()).getElementFactory(); + final PsiTypeVisitor 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 $this = this; + final PsiType[] substitutes = !classType.hasParameters() ? PsiType.EMPTY_ARRAY : ContainerUtil.map2Array( + classType.getParameters(), PsiType.class, new Function() { + @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; + } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveCompiledTraitTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveCompiledTraitTest.groovy index eb5592383405..be82ee7d173a 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveCompiledTraitTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveCompiledTraitTest.groovy @@ -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.GenericTraitInteger> {} +''' + } + + 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().methodWithMethodGenerics(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().methodWithMethodGenericsClashing(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().staticMethodWithMethodGenerics(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().staticMethodWithMethodGenericsClashing(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 {} +''' + myFixture.findClass("ExternalConcrete") + } + private testHighlighting(String text) { myFixture.configureByText(GroovyFileType.GROOVY_FILE_TYPE, text) myFixture.testHighlighting(true, false, true) diff --git a/plugins/groovy/testdata/lib/some-library.jar b/plugins/groovy/testdata/lib/some-library.jar index 4cfdef710e5a5201736a2484d7e1cf42e21d046b..0a4471eefadfc7bf620d8873a2e03acdb92beb76 100644 GIT binary patch delta 7190 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 delta 270 zcmcax)$PI?;LXg!#lXP9z_4|V=tN#^<~3_XH(JZ^Fs}x3tz{=qkX;92Zg!N*W&z1< zzO8kR5zJVlbC?Un0IJ&DZ+?c68EC-dI7@4g*yK5u_JTld?yGHEXY?>IFf0LLk;!i? zWhcj5izt9(TsL+pYOyddJmF+u5Ch60fHd#q06B@ttF3v!MitsfGj$41?z1sb0GjOX zHjRs=9OzN*Jg}eDY5l39!OPyi-b7FfuTtG6StfQK%p{+0a&wkDY;? M;WbbJmjXx@0GUoiqW}N^