IDEA-110455 Groovy 2.2: Closure is assignable to SAM classes

This commit is contained in:
Max Medvedev
2013-07-11 22:31:11 +04:00
parent d5c1814e79
commit 20884006fd
4 changed files with 201 additions and 0 deletions
+1
View File
@@ -150,6 +150,7 @@
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrStringTypeConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrBooleanTypeConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrGenericTypeConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureToSamConverter"/>
<typeConverter implementation="org.jetbrains.plugins.groovy.gpp.GppTypeConverter"/>
<expectedTypesContributor implementation="org.jetbrains.plugins.groovy.gpp.GppExpectedTypesContributor"/>
@@ -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<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 && 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<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();
}
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);
}
}
@@ -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 <warning>a2</warning> = {String s->print 1}
B <warning>b</warning> = {print 2}
''')
}
void testAmbiguous() {
testHighlighting('''\
interface SAM1 { def foo(String s)}
interface SAM2 { def bar(Integer i)}
def method(x, SAM1 s1){s1.foo<weak_warning>(x)</weak_warning>}
def method(x, SAM2 s2){s2.bar<weak_warning>(x)</weak_warning>}
method <warning>(1)</warning> {it} // fails because SAM1 and SAM2 are seen as equal
method <warning>("1")</warning> {it} // fails because SAM1 and SAM2 are seen as equal
''')
}
void testGenerics() {
testHighlighting('''
interface A<T> {
def foo(T t)
}
A<String> a1 = {print 1}
A<String> a2 = {String s -> print 1}
A<String> <warning>a3</warning> = {int s -> print 1}
A <warning>a4</warning> = {int s -> print 1}
A a5 = { print 1}
A a6 = {x -> print 1}
''')
}
void testGenerics2() {
testHighlighting('''
interface A<T> {
def foo(T t)
}
interface B extends A<String> {}
B b1 = {print 1}
B b2 = {String s -> print 1}
B <warning>b3</warning> = {int s -> print 1}
''')
}
}
@@ -774,4 +774,13 @@ new A.Inner(new A(), new A()) //correct
''')
}
void testClosureIsNotAssignableToSAMInGroovy2_1() {
testHighlighting('''\
interface X {
def foo()
}
X <warning>x</warining> = {print 2}
''')
}
}