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 4cfdef710e5a..0a4471eefadf 100644 Binary files a/plugins/groovy/testdata/lib/some-library.jar and b/plugins/groovy/testdata/lib/some-library.jar differ