type inference for closures in groovy1.8
authorMaxim Medvedev <maxim.medvedev@jetbrains.com>
Sat, 27 Nov 2010 18:46:48 +0000 (21:46 +0300)
committerMaxim Medvedev <maxim.medvedev@jetbrains.com>
Sat, 27 Nov 2010 18:46:48 +0000 (21:46 +0300)
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyInsertHandler.java
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrClosureType.java
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java
plugins/groovy/test/org/jetbrains/plugins/groovy/LightGroovyTestCase.java
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java
plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java
plugins/groovy/testdata/mockGroovyLib1.8/groovy-1.8.0-beta-2.jar [new file with mode: 0644]
plugins/groovy/testdata/resolve/inference/typeOfGroupBy/A.groovy [new file with mode: 0644]

index 8346f4fb752f8e9a80dd0f94af01869bfa843461..e0ace5309af5c4241b9cbe60801bce46f6fbd9ef 100644 (file)
@@ -86,10 +86,16 @@ public class GroovyInsertHandler implements InsertHandler<LookupElement> {
 
       if (PsiTreeUtil.getParentOfType(elementAt, GrImportStatement.class) != null) return;
 
-      if (parameters.length == 1 && CLOSURE_CLASS.equals(parameters[0].getType().getCanonicalText())) {
-        document.insertString(offset, " {}");
-        caretModel.moveToOffset(offset + 2);
-        return;
+      if (parameters.length == 1) {
+        final PsiType type = parameters[0].getType();
+        if (type instanceof PsiClassType) {
+          final PsiClass psiClass = ((PsiClassType)type).resolve();
+          if (psiClass != null && CLOSURE_CLASS.equals(psiClass.getQualifiedName())) {
+            document.insertString(offset, " {}");
+            caretModel.moveToOffset(offset + 2);
+            return;
+          }
+        }
       }
 
       PsiDocumentManager docManager = PsiDocumentManager.getInstance(method.getProject());
index 72b4380ecca5078db2c45a487cb35560bbdfd30b..efe551f41c7aa4400fb174898c8f5c0c031d6801 100644 (file)
@@ -19,7 +19,9 @@ package org.jetbrains.plugins.groovy.lang.psi.impl;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.pom.java.LanguageLevel;
 import com.intellij.psi.*;
+import com.intellij.psi.impl.PsiSubstitutorImpl;
 import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.containers.HashMap;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -28,6 +30,8 @@ import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter;
 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureSignature;
 import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil;
 
+import java.util.Map;
+
 /**
  * @author ven
  */
@@ -35,12 +39,20 @@ public class GrClosureType extends PsiClassType {
   private final GlobalSearchScope myScope;
   private final PsiManager myManager;
   private final @NotNull GrClosureSignature mySignature;
+  private final PsiType[] myTypeArgs;
 
   private GrClosureType(LanguageLevel languageLevel, GlobalSearchScope scope, PsiManager manager, @NotNull GrClosureSignature closureSignature) {
     super(languageLevel);
     myScope = scope;
     myManager = manager;
     mySignature = closureSignature;
+    final PsiClass psiClass = resolve();
+    if (psiClass!=null && psiClass.getTypeParameters().length==1) {
+      myTypeArgs = new PsiType[]{mySignature.getReturnType()};
+    }
+    else {
+      myTypeArgs = PsiType.EMPTY_ARRAY;
+    }
   }
 
   @Nullable
@@ -54,19 +66,30 @@ public class GrClosureType extends PsiClassType {
 
   @NotNull
   public PsiType[] getParameters() {
-    //todo
-    return PsiType.EMPTY_ARRAY;
+    return myTypeArgs;
   }
 
   @NotNull
   public ClassResolveResult resolveGenerics() {
+    final PsiClass closure = resolve();
+    final PsiSubstitutor substitutor;
+    if (closure != null && closure.getTypeParameters().length == 1) {
+      final PsiTypeParameter parameter = closure.getTypeParameters()[0];
+      final Map<PsiTypeParameter, PsiType> map = new HashMap<PsiTypeParameter, PsiType>();
+      map.put(parameter, mySignature.getReturnType());
+      substitutor = PsiSubstitutorImpl.createSubstitutor(map);
+    }
+    else {
+      substitutor = PsiSubstitutor.EMPTY;
+    }
+
     return new ClassResolveResult() {
       public PsiClass getElement() {
-        return resolve();
+        return closure;
       }
 
       public PsiSubstitutor getSubstitutor() {
-        return PsiSubstitutor.UNKNOWN;
+        return substitutor;
       }
 
       public boolean isPackagePrefixPackageReference() {
@@ -99,12 +122,22 @@ public class GrClosureType extends PsiClassType {
 
   @NotNull
   public String getPresentableText() {
-    return "Closure";
+    if (myTypeArgs.length == 0 || myTypeArgs[0] == null) {
+      return "Closure";
+    }
+    else {
+      return "Closure<" + myTypeArgs[0].getPresentableText() + ">";
+    }
   }
 
   @Nullable
   public String getCanonicalText() {
-    return GrClosableBlock.GROOVY_LANG_CLOSURE;
+    if (myTypeArgs.length == 0 || myTypeArgs[0] == null) {
+      return GrClosableBlock.GROOVY_LANG_CLOSURE;
+    }
+    else {
+      return GrClosableBlock.GROOVY_LANG_CLOSURE + "<" + myTypeArgs[0].getCanonicalText() + ">";
+    }
   }
 
   @Nullable
index 4603d529bacc93636b8e3b003919e119e2bb7a60..722d5388964bf9a2f118ae43fe0e24e6cc2c4b38 100644 (file)
@@ -520,7 +520,10 @@ public class ResolveUtil {
   }
 
   public static boolean isInUseScope(GroovyResolveResult resolveResult) {
-    final GroovyPsiElement context = resolveResult.getCurrentFileResolveContext();
+    return isInUseScope(resolveResult.getCurrentFileResolveContext());
+  }
+
+  public static boolean isInUseScope(GroovyPsiElement context) {
     if (context instanceof GrMethodCall) {
       final GrExpression expression = ((GrMethodCall)context).getInvokedExpression();
       if (expression instanceof GrReferenceExpression) {
@@ -532,4 +535,20 @@ public class ResolveUtil {
     }
     return false;
   }
+
+  public static boolean isInWithContext(GroovyPsiElement context) {
+    if (context instanceof GrExpression) {
+      final PsiElement parent = context.getParent();
+      if (parent instanceof GrReferenceExpression && ((GrReferenceExpression)parent).getQualifier() == context) {
+        final PsiElement pparent = parent.getParent();
+        if (pparent instanceof GrMethodCall) {
+          final PsiMethod method = ((GrMethodCall)pparent).resolveMethod();
+          if (method instanceof GrGdkMethod && "with".equals(method.getName())) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
 }
index 89dc524d85e9b72dfbcaad520c6cc02af77f5160..88d25bd301f20b7d259581f01bb2945238c74cdd 100644 (file)
@@ -31,7 +31,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
 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.api.types.GrClosureParameter;
@@ -83,14 +83,16 @@ public class MethodResolverProcessor extends ResolverProcessor {
 
       if (method.isConstructor() != myIsConstructor) return true;
       if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
-      substitutor = obtainSubstitutor(substitutor, method);
+      substitutor = obtainSubstitutor(substitutor, method, state);
       boolean isAccessible = isAccessible(method);
-      GroovyPsiElement fileResolveContext = state.get(RESOLVE_CONTEXT);
-      boolean isStaticsOK = isStaticsOK(method, fileResolveContext);
-      if (!myAllVariants && PsiUtil.isApplicable(myArgumentTypes, method, substitutor, fileResolveContext instanceof GrMethodCallExpression, (GroovyPsiElement)myPlace) && isStaticsOK) {
-        addCandidate(new GroovyResolveResultImpl(method, fileResolveContext, substitutor, isAccessible, isStaticsOK));
+      GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT);
+      boolean isStaticsOK = isStaticsOK(method, resolveContext);
+      if (!myAllVariants &&
+          PsiUtil.isApplicable(myArgumentTypes, method, substitutor, ResolveUtil.isInUseScope(resolveContext), (GroovyPsiElement)myPlace) &&
+          isStaticsOK) {
+        addCandidate(new GroovyResolveResultImpl(method, resolveContext, substitutor, isAccessible, isStaticsOK));
       } else {
-        myInapplicableCandidates.add(new GroovyResolveResultImpl(method, fileResolveContext, substitutor, isAccessible, isStaticsOK));
+        myInapplicableCandidates.add(new GroovyResolveResultImpl(method, resolveContext, substitutor, isAccessible, isStaticsOK));
       }
 
       return true;
@@ -99,7 +101,7 @@ public class MethodResolverProcessor extends ResolverProcessor {
     return true;
   }
 
-  private PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method) {
+  private PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method, ResolveState state) {
     final PsiTypeParameter[] typeParameters = method.getTypeParameters();
     if (myTypeArguments.length == typeParameters.length) {
       for (int i = 0; i < typeParameters.length; i++) {
@@ -116,7 +118,13 @@ public class MethodResolverProcessor extends ResolverProcessor {
         assert argTypes != null;
         //type inference should be performed from static method
         PsiType[] newArgTypes = new PsiType[argTypes.length + 1];
-        newArgTypes[0] = myThisType;
+        final GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT);
+        if (ResolveUtil.isInWithContext(resolveContext)) {
+          newArgTypes[0] = ((GrExpression)resolveContext).getType();
+        }
+        else {
+          newArgTypes[0] = myThisType;
+        }
         System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length);
         argTypes = newArgTypes;
 
index ef0f5332af287bf69d281f1c1e6ec86e40485298..8b1a8ee8aa2963bf51e3cf978a4ff65789b8eeb4 100644 (file)
@@ -52,7 +52,7 @@ public abstract class LightGroovyTestCase extends LightCodeInsightFixtureTestCas
     public void configureModule(Module module, ModifiableRootModel model, ContentEntry contentEntry) {
       final Library.ModifiableModel modifiableModel = model.getModuleLibraryTable().createLibrary("GROOVY").getModifiableModel();
       final VirtualFile groovyJar =
-        JarFileSystem.getInstance().refreshAndFindFileByPath(TestUtils.getMockGroovy1_7LibraryName()+"!/");
+        JarFileSystem.getInstance().refreshAndFindFileByPath(TestUtils.getMockGroovy1_8LibraryName()+"!/");
       modifiableModel.addRoot(groovyJar, OrderRootType.CLASSES);
       modifiableModel.commit();
     }
index 114a6b5dc10d559386cafec8a2f189b4c0fc4910..51513f30e03b8089f51f7cb1e29f2d7652165f4f 100644 (file)
@@ -15,9 +15,7 @@
  */
 package org.jetbrains.plugins.groovy.lang.resolve;
 
-import com.intellij.psi.CommonClassNames;
-import com.intellij.psi.PsiIntersectionType;
-import com.intellij.psi.PsiType;
+import com.intellij.psi.*;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
 import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
 import org.jetbrains.plugins.groovy.util.TestUtils;
@@ -142,4 +140,16 @@ public class TypeInferenceTest extends GroovyResolveTestCase {
   public void testRawTypeInReturnExpression() {
     assertNotNull(resolve("A.groovy"));
   }
+
+  private void assertTypeEquals(String expected, String fileName) {
+    final PsiReference ref = configureByFile(getTestName(true) + "/" + fileName);
+    assertInstanceOf(ref, GrReferenceExpression.class);
+    final PsiType type = ((GrReferenceExpression)ref).getType();
+    assertNotNull(type);
+    assertEquals(expected, type.getCanonicalText());
+  }
+
+  public void testTypeOfGroupBy() {
+    assertTypeEquals("java.util.Map<java.lang.Integer,java.util.List<java.lang.Integer>>", "A.groovy");
+  }
 }
index 2012f5d59673328ed5438bbcf87368c87446536e..3e222f3fcbdd24de792fccdd213dfab7469a4be7 100644 (file)
@@ -44,6 +44,7 @@ public abstract class TestUtils {
   public static final String END_MARKER = "<end>";
   public static final String GROOVY_JAR = "groovy-all.jar";
   public static final String GROOVY_JAR_17 = "groovy-all-1.7.jar";
+  public static final String GROOVY_JAR_18 = "groovy-1.8.0-beta-2.jar";
 
   public static String getMockJdkHome() {
     return getAbsoluteTestDataPath() + "/mockJDK";
@@ -61,6 +62,14 @@ public abstract class TestUtils {
     return getMockGroovy1_7LibraryHome()+"/groovy-all-1.7.3.jar";
   }
 
+  public static String getMockGroovy1_8LibraryHome() {
+    return getAbsoluteTestDataPath() + "/mockGroovyLib1.8";
+  }
+
+  public static String getMockGroovy1_8LibraryName() {
+    return getMockGroovy1_8LibraryHome()+"/"+GROOVY_JAR_18;
+  }
+
   public static PsiFile createPseudoPhysicalGroovyFile(final Project project, final String text) throws IncorrectOperationException {
     return createPseudoPhysicalFile(project, TEMP_FILE, text);
   }
diff --git a/plugins/groovy/testdata/mockGroovyLib1.8/groovy-1.8.0-beta-2.jar b/plugins/groovy/testdata/mockGroovyLib1.8/groovy-1.8.0-beta-2.jar
new file mode 100644 (file)
index 0000000..dc45cc1
Binary files /dev/null and b/plugins/groovy/testdata/mockGroovyLib1.8/groovy-1.8.0-beta-2.jar differ
diff --git a/plugins/groovy/testdata/resolve/inference/typeOfGroupBy/A.groovy b/plugins/groovy/testdata/resolve/inference/typeOfGroupBy/A.groovy
new file mode 100644 (file)
index 0000000..d2882c1
--- /dev/null
@@ -0,0 +1,4 @@
+[1, 2, 3].with {
+  def by = groupBy({2})
+  print b<ref>y
+}
\ No newline at end of file