IDEA-118234 Closure to SAM coercion: infer generic by SAM return type

This commit is contained in:
Max Medvedev
2014-02-10 19:06:08 +04:00
parent ebffbc9ceb
commit 7128707d9e
5 changed files with 158 additions and 39 deletions
@@ -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;
@@ -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<MethodSignature> 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<MethodSignature> 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;
}
}
@@ -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,
@@ -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);
}
@@ -72,4 +72,69 @@ class TestCase {
}
''', 'java.lang.Integer')
}
void testSAMInference() {
doTest('''\
import groovy.transform.CompileStatic
interface CustomCallable<T> {
T call()
}
class Thing {
static <T> T customType(CustomCallable<T> callable) {
callable.call()
}
@CompileStatic
static void run() {
customType { [] }.ad<caret>d(1) // return type is not inferred - fails compile
}
}
''', "java.lang.Boolean")
}
void testSAMInference2() {
doTest('''\
import groovy.transform.CompileStatic
interface CustomCallable<T> {
List<T> call()
}
class Thing {
static <T> T first(CustomCallable<T> callable) {
callable.call().iterator().next()
}
@CompileStatic
static void run() {
first { [[]] }.ad<caret>d(1) // return type is not inferred - fails compile
}
}
''', "java.lang.Boolean")
}
void testSAMInference3() {
doTest('''\
import groovy.transform.CompileStatic
interface CustomCallable<K, V> {
Map<K, V> call()
}
class Thing {
static <K, V> Map<K, V> customType(CustomCallable<K, V> callable) {
callable.call()
}
@CompileStatic
static void run() {
customType { [(1):3] }.pu<caret>t(1, 5) // return type is not inferred - fails compile
}
}
''', 'java.lang.Integer')
}
}