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}
+''')
+ }
}
]