[groovy] make closure always assignable to SAM type

Make closure always assignable to SAM type independently of closure
parameter types. This fixes (IDEA-175184)
This commit is contained in:
Daniil Ovchinnikov
2017-07-07 12:55:21 +03:00
parent 22f9148015
commit 9cbc654745
8 changed files with 182 additions and 173 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -15,18 +15,20 @@
*/
package org.jetbrains.plugins.groovy.lang.psi.typeEnhancers;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiWildcardType;
import com.intellij.psi.util.MethodSignature;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.gpp.GppClosureParameterTypeProvider;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.sam.SamConversionKt;
import java.util.Iterator;
import java.util.List;
@@ -35,10 +37,10 @@ import java.util.List;
* @author Max Medvedev
*/
public class ClosureAsAnonymousParameterEnhancer extends AbstractClosureParameterEnhancer {
@Nullable
@Override
protected PsiType getClosureParameterType(GrClosableBlock closure, int index) {
List<PsiType> expectedTypes;
if (closure.getParent() instanceof GrSafeCastExpression) {
@@ -64,16 +66,28 @@ public class ClosureAsAnonymousParameterEnhancer extends AbstractClosureParamete
}
for (PsiType constraint : expectedTypes) {
final PsiType suggestion = GppClosureParameterTypeProvider.getSingleMethodParameterType(constraint, index, closure);
if (suggestion != null) {
if (GroovyConfigUtils.getInstance().isVersionAtLeast(closure, GroovyConfigUtils.GROOVY2_3)) {
if (suggestion instanceof PsiWildcardType && ((PsiWildcardType)suggestion).isSuper()) {
return ((PsiWildcardType)suggestion).getBound();
}
}
if (!(constraint instanceof PsiClassType)) continue;
return TypesUtil.substituteAndNormalizeType(suggestion, PsiSubstitutor.EMPTY, null, closure);
PsiClassType.ClassResolveResult result = ((PsiClassType)constraint).resolveGenerics();
PsiClass resolved = result.getElement();
if (resolved == null) continue;
MethodSignature sam = SamConversionKt.findSingleAbstractSignature(resolved);
if (sam == null) continue;
PsiType[] parameterTypes = sam.getParameterTypes();
if (index >= parameterTypes.length) continue;
final PsiType suggestion = result.getSubstitutor().substitute(parameterTypes[index]);
if (suggestion == null) continue;
if (GroovyConfigUtils.getInstance().isVersionAtLeast(closure, GroovyConfigUtils.GROOVY2_3)) {
if (suggestion instanceof PsiWildcardType && ((PsiWildcardType)suggestion).isSuper()) {
return ((PsiWildcardType)suggestion).getBound();
}
}
return TypesUtil.substituteAndNormalizeType(suggestion, result.getSubstitutor(), null, closure);
}
return null;
@@ -1,126 +0,0 @@
/*
* 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.
* 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.psi.typeEnhancers;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
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.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.util.LightCacheKey;
import java.util.Collection;
/**
* @author Max Medvedev
*/
public class ClosureToSamConverter extends GrTypeConverter {
private static final LightCacheKey<Ref<MethodSignature>> SAM_SIGNATURE_LIGHT_CACHE_KEY = LightCacheKey.createByJavaModificationCount();
@Override
public boolean isAllowedInMethodCall() {
return true;
}
@Override
public Boolean isConvertible(@NotNull PsiType ltype, @NotNull PsiType rtype, @NotNull final GroovyPsiElement context) {
if (rtype instanceof GrClosureType &&
ltype instanceof PsiClassType &&
isSamConversionAllowed(context) &&
!TypesUtil.isClassType(ltype, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) {
MethodSignature signature = findSAMSignature(ltype);
if (signature != null) {
final PsiType[] samParameterTypes = signature.getParameterTypes();
GrSignature closureSignature = ((GrClosureType)rtype).getSignature();
boolean raw = ((PsiClassType)ltype).isRaw();
if (raw) return true;
if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) {
return true;
}
}
}
return null;
}
public static boolean isSamConversionAllowed(PsiElement context) {
return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2);
}
@Nullable
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 {
Ref<MethodSignature> newCached = Ref.create(doFindSingleAbstractMethodClass(aClass));
signature = SAM_SIGNATURE_LIGHT_CACHE_KEY.putCachedValue(aClass, newCached).get();
}
return signature != null ? substitute(signature, substitutor): null;
}
@Nullable
private static MethodSignature doFindSingleAbstractMethodClass(@NotNull PsiClass aClass) {
Collection<MethodSignature> toImplement = OverrideImplementExploreUtil.getMethodSignaturesToImplement(aClass);
if (toImplement.size() > 1) return null;
MethodSignature abstractSignature = toImplement.isEmpty() ? null : toImplement.iterator().next();
for (PsiMethod method : aClass.getMethods()) {
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
if (abstractSignature != null) return null;
abstractSignature = method.getSignature(PsiSubstitutor.EMPTY);
}
}
return abstractSignature;
}
@NotNull
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) {
if (TypesUtil.isClassType(type, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) return null;
PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics();
PsiClass aClass = result.getElement();
if (aClass != null) {
return findSingleAbstractMethod(aClass, result.getSubstitutor());
}
}
return null;
}
}
@@ -20,7 +20,10 @@ import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.VolatileNotNullLazyValue;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.psi.PsiClassType.ClassResolveResult;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
@@ -41,7 +44,6 @@ 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;
@@ -50,6 +52,8 @@ import java.util.Collection;
import static com.intellij.util.containers.ContainerUtil.emptyList;
import static com.intellij.util.containers.ContainerUtil.newHashSet;
import static org.jetbrains.plugins.groovy.lang.sam.SamConversionKt.findSingleAbstractMethod;
import static org.jetbrains.plugins.groovy.lang.sam.SamConversionKt.isSamConversionAllowed;
/**
* @author Max Medvedev
@@ -214,7 +218,7 @@ public class SubstitutorComputer {
@Nullable
private PsiType handleConversion(@Nullable PsiType paramType, @Nullable PsiType argType) {
if (argType instanceof PsiClassType &&
ClosureToSamConverter.isSamConversionAllowed(myPlace) &&
isSamConversionAllowed(myPlace) &&
InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE) &&
!TypesUtil.isClassType(paramType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) {
PsiType converted = handleConversionOfSAMType(paramType, (PsiClassType)argType);
@@ -235,34 +239,34 @@ public class SubstitutorComputer {
}
@Nullable
private PsiType handleConversionOfSAMType(@Nullable PsiType samType, @NotNull PsiClassType closure) {
if (samType instanceof PsiClassType) {
PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)samType).resolveGenerics();
PsiClass samClass = resolveResult.getElement();
private PsiType handleConversionOfSAMType(@Nullable PsiType targetType, @NotNull PsiClassType closure) {
if (!(targetType instanceof PsiClassType)) return null;
if (samClass != null && samClass.getTypeParameters().length != 0) {
MethodSignature samSignature = ClosureToSamConverter.findSingleAbstractMethod(samClass, PsiSubstitutor.EMPTY);
if (samSignature != null) {
ClassResolveResult resolveResult = ((PsiClassType)targetType).resolveGenerics();
PsiMethod samMethod = MethodSignatureUtil.findMethodBySignature(samClass, samSignature, true);
if (samMethod != null) {
PsiType[] closureArgs = closure.getParameters();
if (closureArgs.length == 1 && samMethod.getReturnType() != null) {
PsiSubstitutor substitutor = myHelper.inferTypeArguments(samClass.getTypeParameters(),
new PsiType[]{samMethod.getReturnType()},
closureArgs,
LanguageLevel.JDK_1_7);
PsiClass samClass = resolveResult.getElement();
if (samClass == null) return null;
if (!substitutor.getSubstitutionMap().isEmpty()) {
return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor);
}
}
}
}
}
}
PsiTypeParameter[] samClassTypeParameters = samClass.getTypeParameters();
if (samClassTypeParameters.length == 0) return null;
return null;
PsiMethod sam = findSingleAbstractMethod(samClass);
if (sam == null) return null;
// at this point we know that target type is actually a SAM type
PsiType samReturnType = sam.getReturnType();
if (samReturnType == null) return null;
PsiType[] closureParameters = closure.getParameters();
if (closureParameters.length != 1) return null;
PsiSubstitutor substitutor = myHelper.inferTypeArguments(
samClassTypeParameters, new PsiType[]{samReturnType}, closureParameters, LanguageLevel.JDK_1_7
);
if (substitutor.getSubstitutionMap().isEmpty()) return null;
return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor);
}
@@ -0,0 +1,47 @@
/*
* Copyright 2000-2017 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.sam
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiType
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter.ApplicableTo.*
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.GROOVY_LANG_CLOSURE
class ClosureToSamConverter : GrTypeConverter() {
private val myPositions = setOf(ASSIGNMENT, RETURN_VALUE, METHOD_PARAMETER)
override fun isApplicableTo(position: ApplicableTo) = position in myPositions
override fun isConvertibleEx(targetType: PsiType, actualType: PsiType, context: GroovyPsiElement,
currentPosition: ApplicableTo): ConversionResult? {
if (targetType !is PsiClassType || actualType !is GrClosureType) return null
if (!isSamConversionAllowed(context)) return null
val result = targetType.resolveGenerics()
val targetClass = result.element ?: return null
val targetFqn = targetClass.qualifiedName ?: return null // anonymous classes has no fqn
if (targetFqn == GROOVY_LANG_CLOSURE) return null
findSingleAbstractSignature(targetClass) ?: return null
return ConversionResult.OK
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2000-2017 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.sam
import com.intellij.psi.*
import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.MethodSignature
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil.isTrait
fun findSingleAbstractMethod(clazz: PsiClass): PsiMethod? = findSingleAbstractSignatureCached(clazz)?.method
fun findSingleAbstractSignature(clazz: PsiClass): MethodSignature? = findSingleAbstractSignatureCached(clazz)
private fun findSingleAbstractSignatureCached(clazz: PsiClass): HierarchicalMethodSignature? {
return CachedValuesManager.getCachedValue(clazz) {
CachedValueProvider.Result.create(doFindSingleAbstractSignature(clazz), clazz)
}
}
private fun doFindSingleAbstractSignature(clazz: PsiClass): HierarchicalMethodSignature? {
var result: HierarchicalMethodSignature? = null
for (signature in clazz.visibleSignatures) {
if (!isEffectivelyAbstractMethod(signature)) continue
if (result != null) return null // found another abstract method
result = signature
}
return result
}
private fun isEffectivelyAbstractMethod(signature: HierarchicalMethodSignature): Boolean {
val method = signature.method
if (!method.hasModifierProperty(PsiModifier.ABSTRACT)) return false
if (isObjectMethod(signature)) return false
if (isImplementedTraitMethod(method)) return false
return true
}
private fun isObjectMethod(signature: HierarchicalMethodSignature): Boolean {
return signature.superSignatures.any {
it.method.containingClass?.qualifiedName == JAVA_LANG_OBJECT
}
}
private fun isImplementedTraitMethod(method: PsiMethod): Boolean {
val clazz = method.containingClass ?: return false
if (!isTrait(clazz)) return false
val traitMethod = method as? GrMethod ?: return false
return traitMethod.block != null
}
fun isSamConversionAllowed(context: PsiElement): Boolean {
return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2)
}
+1 -1
View File
@@ -235,7 +235,7 @@
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrEnumConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrContainerTypeConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureToSamConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.sam.ClosureToSamConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrContainerConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.gpp.GppTypeConverter"/>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -39,7 +39,7 @@ interface B {}
A a = {print 1}
A a1 = {->print 1}
A <warning>a2</warning> = {String s->print 1}
A a2 = {String s->print 1}
B <warning>b</warning> = {print 2}
''')
}
@@ -66,7 +66,7 @@ interface A<T> {
A<String> a1 = {print 1}
A<String> a2 = {String s -> print 1}
A<String> <warning>a3</warning> = {int s -> print 1}
A<String> a3 = {int s -> print 1}
A a4 = {int s -> print 1}
A a7 = {int s, String y -> print 1}
A a5 = { print 1}
@@ -84,7 +84,7 @@ interface B extends A<String> {}
B b1 = {print 1}
B b2 = {String s -> print 1}
B <warning>b3</warning> = {int s -> print 1}
B b3 = {int s -> print 1}
''')
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -52,7 +52,7 @@ public <T, X> void exec(T t, Action<T, X> f, X x) {
def foo() {
exec('foo', { String t, Integer x -> ; }, 1)
exec<warning descr="'exec' in '_' cannot be applied to '(java.lang.String, groovy.lang.Closure<java.lang.Void>, java.lang.Integer)'">('foo', { Integer t, Integer x -> ; }, 1)</warning>
exec('foo', { Integer t, Integer x -> ; }, 1)
}
''')
}
@@ -85,7 +85,7 @@ public <T, X> void exec(T t, Action<T, X> f, X x) {
def foo() {
exec('foo', { String s, Integer x -> print s + x }, 1)
exec<warning descr="'exec' in '_' cannot be applied to '(java.lang.String, groovy.lang.Closure, java.lang.Integer)'">('foo', { Integer s, Integer x -> print 9 }, 1)</warning>
exec('foo', { Integer s, Integer x -> print 9 }, 1)
}
''')
}