[groovy] make GrTraitType extend PsiIntersectionType. Tests included.
authorDaniil Ovchinnikov <daniil.ovchinnikov@jetbrains.com>
Fri, 6 Nov 2015 19:49:35 +0000 (22:49 +0300)
committerDaniil Ovchinnikov <daniil.ovchinnikov@jetbrains.com>
Fri, 6 Nov 2015 19:51:06 +0000 (22:51 +0300)
- Get rid of mock trait definition and its processing.
- Now we can process it like PsiIntersection type.
- Proper trait ordering in completion and resolve restored.

12 files changed:
java/java-psi-api/src/com/intellij/psi/PsiIntersectionType.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrTraitType.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceResolveRunner.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrWithTraitTypeCalculator.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/types/GrSafeCastExpressionImpl.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/CompleteReferenceExpression.java
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyDocumentationProvider.java
plugins/groovy/test/org/jetbrains/plugins/groovy/LightGroovyTestCase.groovy
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GroovyTraitCoercionTest.groovy [new file with mode: 0644]
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolvePropertyTest.groovy

index f590c38392d607c6380301adeb71b7efd396f6c5..3eec614acb26b074bc0110b8fbb119a032c59df3 100644 (file)
@@ -32,7 +32,7 @@ import java.util.*;
 public class PsiIntersectionType extends PsiType.Stub {
   private final PsiType[] myConjuncts;
 
-  private PsiIntersectionType(@NotNull PsiType[] conjuncts) {
+  protected PsiIntersectionType(@NotNull PsiType[] conjuncts) {
     super(PsiAnnotation.EMPTY_ARRAY);
     myConjuncts = conjuncts;
   }
@@ -67,7 +67,7 @@ public class PsiIntersectionType extends PsiType.Stub {
     }
   }
 
-  private static Set<PsiType> flatten(PsiType[] conjuncts, Set<PsiType> types) {
+  protected static Set<PsiType> flatten(PsiType[] conjuncts, Set<PsiType> types) {
     for (PsiType conjunct : conjuncts) {
       if (conjunct instanceof PsiIntersectionType) {
         PsiIntersectionType type = (PsiIntersectionType)conjunct;
index a8bf629573326b5d6bf84376c152029ce5e631e9..3faf99eaa8d833939b08c982078ead8eda373c3d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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);
   }
 }
index fa201a4713e74bcf4fc302d01cc4de354c3972ef..fdd9b1129fc45c418f716abfed9e3dbdf162dac7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -34,9 +34,6 @@ import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
 import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
 
-import java.util.List;
-import java.util.ListIterator;
-
 /**
  * @author Medvedev Max
  */
@@ -129,11 +126,6 @@ public class GrReferenceResolveRunner {
         }
       }
     }
-    else if (qualifierType instanceof PsiIntersectionType) {
-      for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
-        if (!processQualifierType(conjunct, state)) return false;
-      }
-    }
     else {
       if (!processQualifierType(qualifierType, state)) return false;
       if (qualifier instanceof GrReferenceExpression && !PsiUtil.isSuperReference(qualifier) && !PsiUtil.isInstanceThisRef(qualifier)) {
@@ -174,20 +166,16 @@ public class GrReferenceResolveRunner {
                             ? ((PsiDisjunctionType)originalQualifierType).getLeastUpperBound()
                             : originalQualifierType;
 
-    if (qualifierType instanceof PsiIntersectionType) {
+    if (qualifierType instanceof GrTraitType) {
+      return processTraitType((GrTraitType)qualifierType, state);
+    }
+    else if (qualifierType instanceof PsiIntersectionType) {
       for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
         if (!processQualifierType(conjunct, state)) return false;
       }
       return true;
     }
 
-    if (qualifierType instanceof GrTraitType) {
-      if (!processTraitType((GrTraitType)qualifierType, state)) {
-        return false;
-      }
-      return true;
-    }
-
     if (qualifierType instanceof PsiClassType) {
       PsiClassType.ClassResolveResult qualifierResult = ((PsiClassType)qualifierType).resolveGenerics();
       PsiClass qualifierClass = qualifierResult.getElement();
@@ -217,24 +205,10 @@ public class GrReferenceResolveRunner {
   }
 
   private boolean processTraitType(@NotNull GrTraitType traitType, @NotNull ResolveState state) {
-    GrTypeDefinition mockDefinition = traitType.getMockTypeDefinition();
-    if (mockDefinition != null) {
-      if (!mockDefinition.processDeclarations(processor, state, null, place)) {
-        return false;
-      }
-    }
-    else {
-      PsiClassType exprType = traitType.getExprType();
-
-      if (!processQualifierType(exprType, state)) return false;
-
-      List<PsiClassType> traitTypes = traitType.getTraitTypes();
-      for (ListIterator<PsiClassType> iterator = traitTypes.listIterator(); iterator.hasPrevious(); ) {
-        PsiClassType type = iterator.previous();
-        if (!processQualifierType(type, state)) return false;
-      }
+    final PsiType[] conjuncts = traitType.getConjuncts();
+    for (int i = conjuncts.length - 1; i >= 0; i--) {
+      if (!processQualifierType(conjuncts[i], state)) return false;
     }
-
     return true;
   }
 }
index eb48749a95cf8a138351b2100524363bbcdeef58..7eb1ac7654b2360cc53e4be24faaee3b274d3591 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -310,9 +310,8 @@ public class TypesUtil {
     }
 
     if (rType instanceof GrTraitType) {
-      if (isAssignableWithoutConversions(lType, ((GrTraitType)rType).getExprType(), context)) return true;
-      for (PsiClassType trait : ((GrTraitType)rType).getTraitTypes()) {
-        if (isAssignableWithoutConversions(lType, trait, context)) return true;
+      for (PsiType type : ((GrTraitType)rType).getConjuncts()) {
+        if (isAssignableWithoutConversions(lType, type, context)) return true;
       }
       return false;
     }
index e53d5e394646e8cad0a5ffbf6f82c4a8052e2836..2c0459f20c58eb9c7f75fe9266c47da8c1720ff5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -51,22 +51,22 @@ public class GrWithTraitTypeCalculator extends GrCallExpressionTypeCalculator {
     if (originalObject == null) return null;
 
     PsiType invokedType = originalObject.getType();
-    if (!(invokedType instanceof PsiClassType)) return null;
+    if (!(invokedType instanceof PsiClassType) && !(invokedType instanceof GrTraitType)) return null;
 
     PsiClass containingClass = resolvedMethod.getContainingClass();
     if (containingClass == null || !GroovyCommonClassNames.DEFAULT_GROOVY_METHODS.equals(containingClass.getQualifiedName())) return null;
 
-    List<PsiClassType> traits = ContainerUtil.newArrayList();
+    List<PsiType> traits = ContainerUtil.newArrayList();
     GrExpression[] args = callExpression.getArgumentList().getExpressionArguments();
     for (GrExpression arg : args) {
       PsiType type = arg.getType();
       PsiType classItem = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_CLASS, 0, false);
       PsiClass psiClass = PsiTypesUtil.getPsiClass(classItem);
       if (GrTraitUtil.isTrait(psiClass)) {
-        traits.add((PsiClassType)classItem);
+        traits.add(classItem);
       }
     }
 
-    return GrTraitType.createTraitClassType(callExpression, (PsiClassType)invokedType, traits, callExpression.getResolveScope());
+    return GrTraitType.createTraitClassType(invokedType, traits);
   }
 }
index 78aa01b21860cd1a5cf99f75454db4cf5b6ca0cf..f1e69345f5bf331b3e9614d41496ed63d487edee 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -67,7 +67,7 @@ public class GrSafeCastExpressionImpl extends GrExpressionImpl implements GrSafe
           return JavaPsiFacade.getElementFactory(cast.getProject()).createType(resolved, substitutor);
         }
 
-        GrTraitType traitClassType = GrTraitType.createTraitClassType(cast);
+        PsiType traitClassType = GrTraitType.createTraitClassType(cast);
         if (traitClassType != null) {
           return traitClassType;
         }
index f38b17bc9b831748e99bc1fbabac4216c27554c2..68f7f844094551c823f2b66f657769ae44cd7c2b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -59,7 +59,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatem
 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
 import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
 import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
-import org.jetbrains.plugins.groovy.lang.psi.impl.GrTraitType;
 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
 import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
@@ -369,9 +368,6 @@ public class ResolveUtil {
       final PsiClass superClass = cls.getSuperClass();
       key = cls.getName() + (superClass == null ? CommonClassNames.JAVA_LANG_OBJECT : superClass.getName());
     }
-    else if (base instanceof GrTraitType) {
-      key = ((GrTraitType)base).erasure().getCanonicalText();
-    }
     else if (base instanceof PsiClassType) {
       key = TypesUtil.getQualifiedName(base);
     }
index 1b99010ef18b2efe9bc4a46f67f73829b795a13e..3878f65b556a569c344b160c6eabae8d688d67ba 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -300,24 +300,17 @@ public class CompleteReferenceExpression {
       }
       getVariantsFromQualifierType(TypesUtil.getJavaLangObject(qualifier), project);
     }
+    else if (qualifierType instanceof GrTraitType) {
+      PsiType[] conjuncts = ((GrTraitType)qualifierType).getConjuncts();
+      for (int i = conjuncts.length - 1; i >= 0; i--) {
+        getVariantsFromQualifierType(conjuncts[i], project);
+      }
+    }
     else if (qualifierType instanceof PsiIntersectionType) {
       for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
         getVariantsFromQualifierType(conjunct, project);
       }
     }
-    else if (qualifierType instanceof GrTraitType) {
-      GrTypeDefinition definition = ((GrTraitType)qualifierType).getMockTypeDefinition();
-      if (definition != null) {
-        PsiClassType classType = JavaPsiFacade.getElementFactory(project).createType(definition);
-        getVariantsFromQualifierType(classType, project);
-      }
-      else {
-        getVariantsFromQualifierType(((GrTraitType)qualifierType).getExprType(), project);
-        for (PsiClassType traitType : ((GrTraitType)qualifierType).getTraitTypes()) {
-          getVariantsFromQualifierType(traitType, project);
-        }
-      }
-    }
     else {
       getVariantsFromQualifierType(qualifierType, project);
       if (qualifier instanceof GrReferenceExpression && !PsiUtil.isSuperReference(qualifier) && !PsiUtil.isInstanceThisRef(qualifier)) {
index c3476c287db5fab6eb095f8b2629acea2606682c..1e0e21335cbb5bee10f4a23f7dd7fee861775036 100644 (file)
@@ -263,11 +263,9 @@ public class GroovyDocumentationProvider implements CodeDocumentationProvider, E
   }
 
   private static void generateTraitType(@NotNull StringBuilder buffer, @NotNull GrTraitType type, PsiElement context) {
-    PsiClassType exprType = type.getExprType();
-    List<PsiClassType> traitTypes = type.getTraitTypes();
-    appendTypeString(buffer, exprType, context);
+    appendTypeString(buffer, type.getExprType(), context);
     buffer.append(" as ");
-    for (PsiClassType traitType : traitTypes) {
+    for (PsiType traitType : type.getTraitTypes()) {
       appendTypeString(buffer, traitType, context);
       buffer.append(", ");
     }
index e26d327a2e0645354f56b01d30fb558206e79c6a..2a5f12742fb8c57518e060d9d47d09dfcabb9db5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -19,6 +19,7 @@ package org.jetbrains.plugins.groovy
 import com.intellij.psi.PsiIntersectionType
 import com.intellij.psi.PsiType
 import com.intellij.testFramework.LightProjectDescriptor
+import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
 import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
 import org.jetbrains.annotations.NonNls
 import org.jetbrains.annotations.NotNull
@@ -29,6 +30,10 @@ import org.jetbrains.annotations.Nullable
  */
 public abstract class LightGroovyTestCase extends LightCodeInsightFixtureTestCase {
 
+  JavaCodeInsightTestFixture getFixture() {
+    myFixture
+  }
+
   @Override
   @NotNull
   protected LightProjectDescriptor getProjectDescriptor() {
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GroovyTraitCoercionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GroovyTraitCoercionTest.groovy
new file mode 100644 (file)
index 0000000..ba0fc23
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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)
+    }
+  }
+}
index 4ab258e57d867c65d591a211b168019af0e8142b..6ad716aead4a4385ff2db213f5b54988faaaa6a4 100644 (file)
@@ -30,7 +30,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEn
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitMethod
 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil
 import org.jetbrains.plugins.groovy.util.TestUtils
 
@@ -1476,7 +1475,7 @@ class B {
 
 def v = new B() as A
 print v.fo<caret>o
-''', GrTraitMethod)
+''', GrAccessorMethod)
   }
 
   void testTraitPropertyFromAsOperator2() {