/*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * 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.
*/
package org.jetbrains.plugins.groovy.lang.psi.impl;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.Condition;
-import com.intellij.openapi.util.VolatileNotNullLazyValue;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.pom.java.LanguageLevel;
-import com.intellij.psi.*;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.util.*;
-import com.intellij.util.ArrayUtil;
+import com.intellij.psi.PsiClassType;
+import com.intellij.psi.PsiIntersectionType;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.Function;
-import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
-import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression;
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause;
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrReferenceList;
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
-import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
-import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameter;
-import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameterList;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
-import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
-/**
- * Created by Max Medvedev on 20/05/14
- */
-public class GrTraitType extends PsiClassType {
-
- private static final Logger LOG = Logger.getInstance(GrTraitType.class);
-
- private final GrExpression myOriginal;
-
- private final PsiClassType myExprType;
- private final List<PsiClassType> myTraitTypes;
+public class GrTraitType extends PsiIntersectionType {
- private final GlobalSearchScope myResolveScope;
+ private final @NotNull PsiType myExprType;
+ private final @NotNull List<PsiType> myTraitTypes;
- private final VolatileNotNullLazyValue<PsiType[]> myParameters = new VolatileNotNullLazyValue<PsiType[]>() {
- @NotNull
- @Override
- protected PsiType[] compute() {
- List<PsiType> result = ContainerUtil.newArrayList();
- ContainerUtil.addAll(result, myExprType.getParameters());
- for (PsiClassType type : myTraitTypes) {
- ContainerUtil.addAll(result, type.getParameters());
- }
- return result.toArray(new PsiType[result.size()]);
- }
- };
-
-
- public GrTraitType(@NotNull GrExpression original,
- @NotNull PsiClassType exprType,
- @NotNull List<PsiClassType> traitTypes,
- @NotNull GlobalSearchScope resolveScope, LanguageLevel languageLevel) {
- super(languageLevel);
- myOriginal = original;
- myResolveScope = resolveScope;
- myExprType = exprType;
- myTraitTypes = ContainerUtil.newArrayList(traitTypes);
+ private GrTraitType(@NotNull PsiType[] types) {
+ super(types);
+ myExprType = types[0];
+ myTraitTypes = ContainerUtil.newArrayList(types, 1, types.length);
}
@NotNull
- @Override
- public String getPresentableText() {
- return myExprType.getPresentableText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiClassType, String>() {
- @Override
- public String fun(PsiClassType type) {
- return type.getPresentableText();
- }
- }), ", ");
+ public PsiType getExprType() {
+ return myExprType;
}
@NotNull
- @Override
- public String getCanonicalText() {
- return myExprType.getCanonicalText();
+ public List<PsiType> getTraitTypes() {
+ return myTraitTypes;
}
@NotNull
@Override
- public String getInternalCanonicalText() {
- return myExprType.getCanonicalText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiClassType, String>() {
+ public String getPresentableText() {
+ return myExprType.getPresentableText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiType, String>() {
@Override
- public String fun(PsiClassType type) {
- return type.getCanonicalText();
+ public String fun(PsiType type) {
+ return type.getPresentableText();
}
}), ", ");
}
- @Override
- public boolean isValid() {
- return myExprType.isValid() && ContainerUtil.find(myTraitTypes, new Condition<PsiClassType>() {
- @Override
- public boolean value(PsiClassType type) {
- return !type.isValid();
- }
- }) == null;
- }
-
- @Override
- public boolean equalsToText(@NotNull @NonNls String text) {
- return false;
- }
-
- @NotNull
- @Override
- public LanguageLevel getLanguageLevel() {
- return myLanguageLevel;
- }
-
@NotNull
@Override
- public PsiClassType setLanguageLevel(@NotNull LanguageLevel languageLevel) {
- return new GrTraitType(myOriginal, myExprType, myTraitTypes, myResolveScope, languageLevel);
- }
-
- @Nullable
- @Override
- public PsiClass resolve() {
- return getMockTypeDefinition();
- }
-
- @Override
- public String getClassName() {
- return null;
- }
-
- @NotNull
- @Override
- public PsiType[] getParameters() {
- return myParameters.getValue();
- }
-
- @NotNull
- @Override
- public PsiType[] getSuperTypes() {
- PsiType[] result = new PsiType[myTraitTypes.size() + 1];
- result[0] = myExprType;
- ArrayUtil.copy(myTraitTypes, result, 1);
- return result;
- }
-
- @NotNull
- @Override
- public ClassResolveResult resolveGenerics() {
- return CachedValuesManager.getCachedValue(myOriginal, new CachedValueProvider<ClassResolveResult>() {
- @Nullable
- @Override
- public Result<ClassResolveResult> compute() {
- final GrTypeDefinition definition = new MockTypeBuilder().buildMockTypeDefinition();
- final PsiSubstitutor substitutor = new SubstitutorBuilder(definition).buildSubstitutor();
-
- return Result.<ClassResolveResult>create(new TraitResolveResult(definition, substitutor), PsiModificationTracker.MODIFICATION_COUNT);
- }
- });
- }
-
- @NotNull
- @Override
- public PsiClassType rawType() {
- return new GrTraitType(myOriginal, myExprType.rawType(), ContainerUtil.map(myTraitTypes, new Function<PsiClassType, PsiClassType>() {
- @Override
- public PsiClassType fun(PsiClassType type) {
- return type.rawType();
- }
- }), myResolveScope, myLanguageLevel);
- }
-
- @NotNull
- @Override
- public GlobalSearchScope getResolveScope() {
- return myResolveScope;
- }
-
- @Nullable
- public GrTypeDefinition getMockTypeDefinition() {
- return CachedValuesManager.getCachedValue(myOriginal, new CachedValueProvider<GrTypeDefinition>() {
- @Nullable
- @Override
- public Result<GrTypeDefinition> compute() {
- return Result.create(new MockTypeBuilder().buildMockTypeDefinition(), PsiModificationTracker.MODIFICATION_COUNT);
- }
- });
- }
-
- public PsiClassType getExprType() {
- return myExprType;
- }
-
- public List<PsiClassType> getTraitTypes() {
- return Collections.unmodifiableList(myTraitTypes);
- }
-
- public GrTraitType erasure() {
- PsiClassType exprType = (PsiClassType)TypeConversionUtil.erasure(myExprType);
- List<PsiClassType> traitTypes = ContainerUtil.map(myTraitTypes, new Function<PsiClassType, PsiClassType>() {
+ public String getInternalCanonicalText() {
+ return myExprType.getCanonicalText() + " as " + StringUtil.join(ContainerUtil.map(myTraitTypes, new Function<PsiType, String>() {
@Override
- public PsiClassType fun(PsiClassType type) {
- return (PsiClassType)TypeConversionUtil.erasure(type);
+ public String fun(PsiType type) {
+ return type.getInternalCanonicalText();
}
- });
- return new GrTraitType(myOriginal, exprType, traitTypes, myResolveScope, LanguageLevel.JDK_1_5);
+ }), ", ");
}
+ // todo move this method to org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.types.GrSafeCastExpressionImpl
@Nullable
- public static GrTraitType createTraitClassType(@NotNull GrSafeCastExpression safeCastExpression) {
+ public static PsiType createTraitClassType(@NotNull GrSafeCastExpression safeCastExpression) {
GrExpression operand = safeCastExpression.getOperand();
PsiType exprType = operand.getType();
- if (!(exprType instanceof PsiClassType)) return null;
+ if (!(exprType instanceof PsiClassType) && !(exprType instanceof GrTraitType)) return null;
GrTypeElement typeElement = safeCastExpression.getCastTypeElement();
if (typeElement == null) return null;
PsiType type = typeElement.getType();
if (!GrTraitUtil.isTrait(PsiTypesUtil.getPsiClass(type))) return null;
- return new GrTraitType(safeCastExpression, ((PsiClassType)exprType), Collections.singletonList((PsiClassType)type), safeCastExpression.getResolveScope(),
- LanguageLevel.JDK_1_5);
- }
-
-
- @NotNull
- public static GrTraitType createTraitClassType(@NotNull GrExpression context,
- @NotNull PsiClassType exprType,
- @NotNull List<PsiClassType> traitTypes,
- @NotNull GlobalSearchScope resolveScope) {
- return new GrTraitType(context, exprType, traitTypes, resolveScope, LanguageLevel.JDK_1_5);
- }
-
- private static class TraitResolveResult implements ClassResolveResult {
-
- private final GrTypeDefinition myDefinition;
- private final PsiSubstitutor mySubstitutor;
-
- public TraitResolveResult(GrTypeDefinition definition, PsiSubstitutor substitutor) {
- myDefinition = definition;
- mySubstitutor = substitutor;
- }
-
- @Override
- public GrTypeDefinition getElement() {
- return myDefinition;
- }
-
- @NotNull
- @Override
- public PsiSubstitutor getSubstitutor() {
- return mySubstitutor;
- }
-
- @Override
- public boolean isPackagePrefixPackageReference() {
- return false;
- }
-
- @Override
- public boolean isAccessible() {
- return true;
- }
-
- @Override
- public boolean isStaticsScopeCorrect() {
- return true;
- }
-
- @Override
- public PsiElement getCurrentFileResolveScope() {
- return null;
- }
-
- @Override
- public boolean isValidResult() {
- return true;
- }
+ return createTraitClassType(exprType, ContainerUtil.newSmartList(type));
}
- private class MockTypeBuilder {
- GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myOriginal.getProject());
-
- @Nullable
- public GrTypeDefinition buildMockTypeDefinition() {
- try {
-
- StringBuilder buffer = new StringBuilder("class _____________Temp___________ ");
- prepareGenerics(buffer);
- buffer.append(" extends Super implements Trait {}");
- GrTypeDefinition definition = factory.createTypeDefinition(buffer.toString());
- replaceReferenceWith(definition.getExtendsClause(), myExprType);
- addReferencesWith(definition.getImplementsClause(), myTraitTypes, myExprType.getParameterCount());
- return definition;
- }
- catch (IncorrectOperationException e) {
- return null;
- }
- }
-
- private void prepareGenerics(StringBuilder buffer) {
- int count = myExprType.getParameterCount();
- for (PsiClassType trait : myTraitTypes) {
- count += trait.getParameterCount();
- }
- if (count == 0) return;
-
- buffer.append('<');
- for (int i = 0; i < count; i++) {
- buffer.append("T").append(i).append(",");
- }
- buffer.replace(buffer.length() - 1, buffer.length(), ">");
- }
-
- private void addReferencesWith(@Nullable GrImplementsClause clause, @NotNull List<PsiClassType> traitTypes, int parameterOffset) {
- LOG.assertTrue(clause != null);
- clause.getReferenceElementsGroovy()[0].delete();
- for (PsiClassType type : traitTypes) {
- processType(clause, type, parameterOffset);
- parameterOffset += type.getParameterCount();
- }
- }
-
- private void replaceReferenceWith(@Nullable GrReferenceList clause, @NotNull PsiClassType type) {
- LOG.assertTrue(clause != null);
- clause.getReferenceElementsGroovy()[0].delete();
- processType(clause, type, 0);
- }
-
- private void processType(@NotNull GrReferenceList clause, @NotNull PsiClassType type, int parameterOffset) {
- PsiClass resolved = type.resolve();
- if (resolved != null) {
- String qname = resolved.getQualifiedName();
- StringBuilder buffer = new StringBuilder();
- buffer.append(qname);
- int parameterCount = type.getParameterCount();
- if (parameterCount > 0) {
- buffer.append('<');
- for (int i = 0; i < parameterCount; i++) {
- buffer.append("T").append(parameterOffset + i).append(',');
- }
- buffer.replace(buffer.length() - 1, buffer.length(), ">");
- }
-
- GrCodeReferenceElement ref = factory.createCodeReferenceElementFromText(buffer.toString());
- clause.add(ref);
- }
- }
+ public static PsiType createTraitClassType(@NotNull PsiType type, @NotNull List<PsiType> traits) {
+ return createTraitClassType(ContainerUtil.prepend(traits, type));
}
- private class SubstitutorBuilder {
-
- private final GrTypeParameter[] myParameters;
- private int myOffset = 0;
-
- public SubstitutorBuilder(@NotNull GrTypeDefinition definition) {
- GrTypeParameterList typeParameterList = definition.getTypeParameterList();
- myParameters = typeParameterList != null ? typeParameterList.getTypeParameters() : GrTypeParameter.EMPTY_ARRAY;
- }
-
- @NotNull
- public PsiSubstitutor buildSubstitutor() {
- if (myParameters.length == 0) return PsiSubstitutor.EMPTY;
-
- Map<PsiTypeParameter, PsiType> map = ContainerUtil.newLinkedHashMap();
- putMappingAndReturnOffset(map, myExprType);
- for (PsiClassType type : myTraitTypes) {
- putMappingAndReturnOffset(map, type);
- }
-
- return JavaPsiFacade.getElementFactory(myOriginal.getProject()).createSubstitutor(map);
- }
-
- private void putMappingAndReturnOffset(@NotNull Map<PsiTypeParameter, PsiType> map, @NotNull PsiClassType type) {
- PsiType[] args = type.getParameters();
- for (int i = 0; i < args.length; i++) {
- map.put(myParameters[myOffset + i], args[i]);
+ public static PsiType createTraitClassType(@NotNull List<PsiType> types) {
+ final Set<PsiType> flattened = flatten(types.toArray(createArray(types.size())), new LinkedHashSet<PsiType>() {
+ @Override
+ public boolean add(PsiType type) {
+ remove(type);
+ return super.add(type);
}
- myOffset += args.length;
- }
+ });
+ final PsiType[] conjuncts = flattened.toArray(createArray(flattened.size()));
+ if (conjuncts.length == 1) return conjuncts[0];
+ return new GrTraitType(conjuncts);
}
}
--- /dev/null
+/*
+ * 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 org.jetbrains.plugins.groovy.lang.resolve
+
+import com.intellij.codeInsight.completion.CompletionType
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.testFramework.LightProjectDescriptor
+import org.jetbrains.plugins.groovy.GroovyFileType
+import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
+
+class GroovyTraitCoercionTest extends GroovyResolveTestCase {
+
+ String basePath = null
+ LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_2_3_9
+
+ @Override
+ void setUp() {
+ super.setUp()
+ 'add necessary classes'()
+ }
+
+ public void 'add necessary classes'() {
+ fixture.addFileToProject 'classes.groovy', '''\
+trait T1 {
+ String foo() {}
+ def bar() {}
+}
+
+trait T2 {
+ Integer foo() {}
+}
+
+interface I {
+ def someMethod()
+}
+
+class Foo implements I {
+ Foo foo() {}
+ def someMethod() {}
+ def fooMethod() {}
+}
+'''
+ }
+
+ void 'test types: `as` and chained `as`'() {
+ testExpressionTypes([
+ 'new Foo() as T1' : 'Foo as T1',
+ 'new Foo() as T2' : 'Foo as T2',
+ '(new Foo() as T1) as T2': 'Foo as T1, T2',
+ '(new Foo() as T2) as T1': 'Foo as T2, T1',
+ ])
+ }
+
+ void 'test types: `withTraits()` and chained `withTraits()`'() {
+ testExpressionTypes([
+ 'new Foo().withTraits(T1)' : 'Foo as T1',
+ 'new Foo().withTraits(T2)' : 'Foo as T2',
+ 'new Foo().withTraits(T1).withTraits(T2)': 'Foo as T1, T2',
+ 'new Foo().withTraits(T2).withTraits(T1)': 'Foo as T2, T1',
+ ])
+ }
+
+ void 'test types: remove duplicates'() {
+ testExpressionTypes([
+ '(new Foo() as T1) as T1' : 'Foo as T1',
+ '(new Foo() as T1).withTraits(T1)' : 'Foo as T1',
+ 'new Foo().withTraits(T2) as T2' : 'Foo as T2',
+ 'new Foo().withTraits(T2).withTraits(T2)': 'Foo as T2',
+ ])
+ }
+
+ void 'test types: mixed `as` and `withTraits()`'() {
+ testExpressionTypes([
+ '(new Foo() as T1).withTraits(T2)': 'Foo as T1, T2',
+ '(new Foo() as T2).withTraits(T1)': 'Foo as T2, T1',
+ 'new Foo().withTraits(T1) as T2' : 'Foo as T1, T2',
+ 'new Foo().withTraits(T2) as T1' : 'Foo as T2, T1',
+ ])
+ }
+
+ void 'test types: with two traits'() {
+ testExpressionTypes(
+ 'new Foo().withTraits(T1, T2)': 'Foo as T1, T2'
+ )
+ }
+
+ void 'test types: traits duplicates and order'() {
+ testExpressionTypes(
+ '(new Foo() as T1).withTraits(T2, T1)': 'Foo as T2, T1'
+ )
+ }
+
+ void 'test `as` operator'() {
+ testResolveContainingClass([
+ '(new Foo() as T1).fo<caret>o()' : 'T1',
+ '(new Foo() as T1).ba<caret>r()' : 'T1',
+ '(new Foo() as T1).some<caret>Method()' : 'Foo',
+ '(new Foo() as T1).foo<caret>Method()' : 'Foo',
+ '((new Foo() as T1) as T2).fo<caret>o()': 'T2',
+ '((new Foo() as T1) as T2).ba<caret>r()': 'T1',
+ ])
+ }
+
+ void 'test `withTraits()`'() {
+ testResolveContainingClass([
+ 'new Foo().withTraits(T1).fo<caret>o()' : 'T1',
+ 'new Foo().withTraits(T1).ba<caret>r()' : 'T1',
+ 'new Foo().withTraits(T1).withTraits(T2).fo<caret>o()': 'T2',
+ 'new Foo().withTraits(T2).withTraits(T1).fo<caret>o()': 'T1',
+ ])
+ }
+
+ void 'test duplicates and order'() {
+ testResolveContainingClass([
+ '(new Foo() as T1).withTraits(T2, T1).fo<caret>o()': 'T1'
+ ])
+ }
+
+ void 'test completion proirity'() {
+ fixture.configureByText GroovyFileType.GROOVY_FILE_TYPE, '''\
+(new Foo().withTraits(T1, T2).f<caret>)
+'''
+ def method = fixture.findClass('T2').findMethodsByName('foo', false)[0]
+ def lookupElements = fixture.complete(CompletionType.BASIC)
+ assert lookupElements.find { it.psiElement == method }
+ }
+
+ def <T extends GrExpression> T configureByExpression(String text, Class<T> expressionType = GrExpression) {
+ assert text
+ fixture.configureByText GroovyFileType.GROOVY_FILE_TYPE, text
+ PsiTreeUtil.findFirstParent(fixture.file.findElementAt(0), { PsiElement element ->
+ element in expressionType && element.text == text
+ }).asType(expressionType)
+ }
+
+ def testExpressionType(String expressionString, String typeString) {
+ configureByExpression(expressionString).with { GrExpression expression ->
+ assert expression.type: expression.getText()
+ assert expression.type.internalCanonicalText == typeString: "$expression.text: $expression.type.internalCanonicalText == $typeString"
+ }
+ }
+
+ def testExpressionTypes(Map<String, String> data) {
+ data.each this.&testExpressionType
+ }
+
+ def testResolveContainingClass(Map<String, String> data) {
+ data.each { k, v ->
+ assert resolveByText(k, GrMethod).containingClass == fixture.findClass(v)
+ }
+ }
+}