From 20884006fd4b9905a6bdfefdddafa1caca4ac0a7 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Thu, 11 Jul 2013 22:30:35 +0400 Subject: [PATCH] IDEA-110455 Groovy 2.2: Closure is assignable to SAM classes --- plugins/groovy/src/META-INF/plugin.xml | 1 + .../typeEnhancers/ClosureToSamConverter.java | 103 ++++++++++++++++++ .../GrAssignabilityClosureToSamTest.groovy | 88 +++++++++++++++ .../highlighting/GrAssignabilityTest.groovy | 9 ++ 4 files changed, 201 insertions(+) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java create mode 100644 plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index a296218e33c0..155320cf9eac 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -150,6 +150,7 @@ + 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 new file mode 100644 index 000000000000..8c574fe1b4e5 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2013 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.util.LightCacheKey; + +import java.util.Collection; + +/** + * @author Max Medvedev + */ +public class ClosureToSamConverter extends GrTypeConverter { + private static final LightCacheKey> 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 && 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) { + + final PsiType[] samParameterTypes = signature.getParameterTypes(); + + GrSignature closureSignature = ((GrClosureType)rtype).getSignature(); + if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) { + return true; + } + } + } + } + + return null; + } + + @Nullable + private static MethodSignature findSingleAbstractMethodClass(@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(); + } + + return signature != null ? substitute(signature, substitutor): null; + } + + @Nullable + private static MethodSignature doFindSingleAbstractMethodClass(@NotNull PsiClass aClass) { + Collection 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); + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy new file mode 100644 index 000000000000..44641725ae8f --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2013 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.highlighting + +import com.intellij.codeInspection.InspectionProfileEntry +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.codeInspection.assignment.GroovyAssignabilityCheckInspection +/** + * @author Max Medvedev + */ +public class GrAssignabilityClosureToSamTest extends GrHighlightingTestBase { + InspectionProfileEntry[] getCustomInspections() { [new GroovyAssignabilityCheckInspection()] } + + @Override + protected LightProjectDescriptor getProjectDescriptor() { GroovyLightProjectDescriptor.GROOVY_2_2 } + + void testAssignability() { + testHighlighting('''\ +interface A { + def foo() +} +interface B {} + +A a = {print 1} +A a1 = {->print 1} +A a2 = {String s->print 1} +B b = {print 2} +''') + } + + void testAmbiguous() { + testHighlighting('''\ +interface SAM1 { def foo(String s)} +interface SAM2 { def bar(Integer i)} + +def method(x, SAM1 s1){s1.foo(x)} +def method(x, SAM2 s2){s2.bar(x)} + +method (1) {it} // fails because SAM1 and SAM2 are seen as equal +method ("1") {it} // fails because SAM1 and SAM2 are seen as equal + +''') + } + + void testGenerics() { + testHighlighting(''' +interface A { + def foo(T t) +} + +A a1 = {print 1} +A a2 = {String s -> print 1} +A a3 = {int s -> print 1} +A a4 = {int s -> print 1} +A a5 = { print 1} +A a6 = {x -> print 1} +''') + } + + void testGenerics2() { + testHighlighting(''' +interface A { + def foo(T t) +} + +interface B extends A {} + +B b1 = {print 1} +B b2 = {String s -> print 1} +B b3 = {int s -> print 1} +''') + } + +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityTest.groovy index d17efb3b172b..53aa60a1bfa0 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityTest.groovy @@ -774,4 +774,13 @@ new A.Inner(new A(), new A()) //correct ''') } + void testClosureIsNotAssignableToSAMInGroovy2_1() { + testHighlighting('''\ +interface X { + def foo() +} + +X x = {print 2} +''') + } }