From 7128707d9eaa580c1be28166c8028e8e3dee0ef6 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Mon, 10 Feb 2014 19:01:28 +0400 Subject: [PATCH] IDEA-118234 Closure to SAM coercion: infer generic by SAM return type --- .../groovy/config/GroovyConfigUtils.java | 1 + .../typeEnhancers/ClosureToSamConverter.java | 40 +++++++----- .../processors/MethodResolverProcessor.java | 26 ++++---- .../processors/SubstitutorComputer.java | 65 +++++++++++++++---- .../lang/resolve/TypeInference2_3Test.groovy | 65 +++++++++++++++++++ 5 files changed, 158 insertions(+), 39 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java index bd30a37a254a..ff052491e6fd 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java @@ -55,6 +55,7 @@ public abstract class GroovyConfigUtils extends AbstractConfigUtils { public static final String GROOVY2_0 = "2.0"; public static final String GROOVY2_1 = "2.1"; public static final String GROOVY2_2 = "2.2"; + public static final String GROOVY2_2_2 = "2.2.2"; public static final String GROOVY2_3 = "2.3"; private static GroovyConfigUtils myGroovyConfigUtils; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java index 8c574fe1b4e5..7516dece6207 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -45,18 +45,13 @@ public class ClosureToSamConverter extends GrTypeConverter { @Override public Boolean isConvertible(@NotNull PsiType ltype, @NotNull PsiType rtype, @NotNull final GroovyPsiElement context) { if (rtype instanceof GrClosureType && ltype instanceof PsiClassType && GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2)) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)ltype).resolveGenerics(); - final PsiClass resolved = resolveResult.getElement(); - if (resolved != null) { - final MethodSignature signature = findSingleAbstractMethodClass(resolved, resolveResult.getSubstitutor()); - if (signature != null) { + MethodSignature signature = findSAMSignature(ltype); + if (signature != null) { + final PsiType[] samParameterTypes = signature.getParameterTypes(); - final PsiType[] samParameterTypes = signature.getParameterTypes(); - - GrSignature closureSignature = ((GrClosureType)rtype).getSignature(); - if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) { - return true; - } + GrSignature closureSignature = ((GrClosureType)rtype).getSignature(); + if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) { + return true; } } } @@ -65,16 +60,15 @@ public class ClosureToSamConverter extends GrTypeConverter { } @Nullable - private static MethodSignature findSingleAbstractMethodClass(@NotNull PsiClass aClass, - @NotNull PsiSubstitutor substitutor) { + public static MethodSignature findSingleAbstractMethod(@NotNull PsiClass aClass, @NotNull PsiSubstitutor substitutor) { MethodSignature signature; Ref cached = SAM_SIGNATURE_LIGHT_CACHE_KEY.getCachedValue(aClass); if (cached != null) { signature = cached.get(); } else { - cached = Ref.create(doFindSingleAbstractMethodClass(aClass)); - signature = SAM_SIGNATURE_LIGHT_CACHE_KEY.putCachedValue(aClass, cached).get(); + Ref newCached = Ref.create(doFindSingleAbstractMethodClass(aClass)); + signature = SAM_SIGNATURE_LIGHT_CACHE_KEY.putCachedValue(aClass, newCached).get(); } return signature != null ? substitute(signature, substitutor): null; @@ -100,4 +94,18 @@ public class ClosureToSamConverter extends GrTypeConverter { private static MethodSignature substitute(@NotNull MethodSignature signature, @NotNull PsiSubstitutor substitutor) { return MethodSignatureUtil.createMethodSignature(signature.getName(), signature.getParameterTypes(), PsiTypeParameter.EMPTY_ARRAY, substitutor, false); } + + @Nullable + public static MethodSignature findSAMSignature(@Nullable PsiType type) { + if (type instanceof PsiClassType) { + PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics(); + PsiClass aClass = result.getElement(); + + if (aClass != null) { + return findSingleAbstractMethod(aClass, result.getSubstitutor()); + } + } + + return null; + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java index a1d5874d793e..c929a20cd48c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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,7 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth private boolean myStopExecuting = false; private final boolean myByShape; - + private final SubstitutorComputer mySubstitutorComputer; private final boolean myTypedContext; @@ -88,7 +88,7 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth myAllVariants = allVariants; myByShape = byShape; - mySubstitutorComputer = new SubstitutorComputer(thisType, argumentTypes, typeArguments, allVariants, place, myPlace.getParent()); + mySubstitutorComputer = new SubstitutorComputer(myThisType, myArgumentTypes, typeArguments, myAllVariants, myPlace, myPlace.getParent()); myTypedContext = GppTypeConverter.hasTypedContext(myPlace); } @@ -98,15 +98,12 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth if (myStopExecuting) { return false; } - PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY); if (element instanceof PsiMethod) { PsiMethod method = (PsiMethod) element; if (method.isConstructor() != myIsConstructor) return true; - if (substitutor == null) substitutor = PsiSubstitutor.EMPTY; - if (!myByShape) { - substitutor = mySubstitutorComputer.obtainSubstitutor(substitutor, method, state); - } + + PsiSubstitutor substitutor = inferSubstitutor(method, state); PsiElement resolveContext = state.get(RESOLVE_CONTEXT); final SpreadState spreadState = state.get(SpreadState.SPREAD_STATE); @@ -124,13 +121,20 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth else { myInapplicableCandidates.add(candidate); } - - return true; } return true; } + @NotNull + private PsiSubstitutor inferSubstitutor(@NotNull PsiMethod method, @NotNull ResolveState state) { + PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY); + if (substitutor == null) substitutor = PsiSubstitutor.EMPTY; + + return myByShape ? substitutor + : mySubstitutorComputer.obtainSubstitutor(substitutor, method, state); + } + @Override @NotNull public GroovyResolveResult[] getCandidates() { @@ -226,7 +230,7 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth return 0; } - + private static boolean isMoreConcreteThan(@NotNull PsiMethod method, @NotNull final PsiSubstitutor substitutor, @NotNull PsiMethod another, diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java index 7e6c0bd6fc0f..7158f91b2ca3 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -18,11 +18,12 @@ package org.jetbrains.plugins.groovy.lang.resolve.processors; import com.intellij.openapi.diagnostic.Logger; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.psi.util.*; import com.intellij.util.containers.hash.HashSet; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; +import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature; @@ -39,7 +40,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMe import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; +import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureToSamConverter; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import java.util.Set; @@ -60,6 +63,7 @@ public class SubstitutorComputer { private final GrControlFlowOwner myFlowOwner; private final PsiElement myPlaceToInferContext; + private PsiResolveHelper myHelper; public SubstitutorComputer(PsiType thisType, @@ -81,6 +85,9 @@ public class SubstitutorComputer { else { myFlowOwner = null; } + + myHelper = JavaPsiFacade.getInstance(myPlace.getProject()).getResolveHelper(); + } @Nullable @@ -186,11 +193,10 @@ public class SubstitutorComputer { i++; } } - final PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); - PsiSubstitutor substitutor = helper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.JDK_1_7); + PsiSubstitutor substitutor = myHelper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.JDK_1_7); for (PsiTypeParameter typeParameter : typeParameters) { if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) { - substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor, helper); + substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor); if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) { substitutor = substitutor.put(typeParameter, null); } @@ -200,7 +206,16 @@ public class SubstitutorComputer { return partialSubstitutor.putAll(substitutor); } - private PsiType handleConversion(PsiType paramType, PsiType argType) { + @Nullable + private PsiType handleConversion(@Nullable PsiType paramType, @Nullable PsiType argType) { + if (GroovyConfigUtils.getInstance().isVersionAtLeast(myPlace, GroovyConfigUtils.GROOVY2_2_2) && + InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { + PsiType converted = handleConversionOfSAMType(paramType, (PsiClassType)argType); + if (converted != null) { + return converted; + } + } + if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, myPlace) && TypesUtil.isAssignableByMethodCallConversion(paramType, argType, myPlace)) { return paramType; @@ -208,13 +223,39 @@ public class SubstitutorComputer { return argType; } - private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, - PsiType lType, - PsiSubstitutor substitutor, - PsiResolveHelper helper) { + @Nullable + private PsiType handleConversionOfSAMType(@Nullable PsiType samType, @NotNull PsiClassType closure) { + if (samType instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)samType).resolveGenerics(); + PsiClass samClass = resolveResult.getElement(); + + if (samClass != null && samClass.getTypeParameters().length != 0) { + MethodSignature samSignature = ClosureToSamConverter.findSingleAbstractMethod(samClass, PsiSubstitutor.EMPTY); + if (samSignature != null) { + + PsiMethod samMethod = MethodSignatureUtil.findMethodBySignature(samClass, samSignature, true); + if (samMethod != null) { + PsiSubstitutor substitutor = myHelper.inferTypeArguments(samClass.getTypeParameters(), + new PsiType[]{samMethod.getReturnType()}, + closure.getParameters(), + LanguageLevel.JDK_1_7); + + return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor); + } + } + } + } + + return null; + } + + + private PsiSubstitutor inferFromContext(@NotNull PsiTypeParameter typeParameter, + @Nullable PsiType lType, + @NotNull PsiSubstitutor substitutor) { if (myPlace == null) return substitutor; - final PsiType inferred = helper.getSubstitutionForTypeParameter(typeParameter, lType, inferContextType(), false, LanguageLevel.JDK_1_7); + final PsiType inferred = myHelper.getSubstitutionForTypeParameter(typeParameter, lType, inferContextType(), false, LanguageLevel.JDK_1_7); if (inferred != PsiType.NULL) { return substitutor.put(typeParameter, inferred); } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy index 382322409b10..731023d74549 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy @@ -72,4 +72,69 @@ class TestCase { } ''', 'java.lang.Integer') } + + void testSAMInference() { + doTest('''\ +import groovy.transform.CompileStatic + +interface CustomCallable { + T call() +} + +class Thing { + static T customType(CustomCallable callable) { + callable.call() + } + + @CompileStatic + static void run() { + customType { [] }.add(1) // return type is not inferred - fails compile + } +} +''', "java.lang.Boolean") + } + + void testSAMInference2() { + doTest('''\ +import groovy.transform.CompileStatic + +interface CustomCallable { + List call() +} + +class Thing { + static T first(CustomCallable callable) { + callable.call().iterator().next() + } + + @CompileStatic + static void run() { + first { [[]] }.add(1) // return type is not inferred - fails compile + } +} +''', "java.lang.Boolean") + } + + void testSAMInference3() { + doTest('''\ +import groovy.transform.CompileStatic + +interface CustomCallable { + Map call() +} + +class Thing { + static Map customType(CustomCallable callable) { + callable.call() + } + + @CompileStatic + static void run() { + customType { [(1):3] }.put(1, 5) // return type is not inferred - fails compile + } +} + +''', 'java.lang.Integer') + } + }