From cb73b8d2d26255f7f799e775f4f9e30f239d49f8 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Mon, 10 Feb 2014 16:51:36 +0400 Subject: [PATCH] IDEA-118222 Closure to SAM interface coercion doesn't handle contravariant types correctly --- .../groovy/config/GroovyConfigUtils.java | 3 +- .../ClosureAsAnonymousParameterEnhancer.java | 13 +++- .../GroovyLightProjectDescriptor.groovy | 6 +- .../lang/resolve/TypeInference2_3Test.groovy | 75 +++++++++++++++++++ .../lang/resolve/TypeInferenceTest.groovy | 28 +------ .../lang/resolve/TypeInferenceTestBase.groovy | 51 +++++++++++++ .../plugins/groovy/util/TestUtils.java | 11 ++- 7 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy create mode 100644 plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTestBase.groovy diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java index 0c405f367d38..bd30a37a254a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java @@ -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. @@ -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_3 = "2.3"; private static GroovyConfigUtils myGroovyConfigUtils; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java index 7290d5298a83..165b5f72bcc3 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 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. @@ -15,9 +15,12 @@ */ package org.jetbrains.plugins.groovy.lang.psi.typeEnhancers; +import com.intellij.psi.PsiSubstitutor; import com.intellij.psi.PsiType; +import com.intellij.psi.PsiWildcardType; 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; @@ -63,7 +66,13 @@ public class ClosureAsAnonymousParameterEnhancer extends AbstractClosureParamete for (PsiType constraint : expectedTypes) { final PsiType suggestion = GppClosureParameterTypeProvider.getSingleMethodParameterType(constraint, index, closure); if (suggestion != null) { - return suggestion; + if (GroovyConfigUtils.getInstance().isVersionAtLeast(closure, GroovyConfigUtils.GROOVY2_3)) { + if (suggestion instanceof PsiWildcardType && ((PsiWildcardType)suggestion).isSuper()) { + return ((PsiWildcardType)suggestion).getBound(); + } + } + + return TypesUtil.substituteBoxAndNormalizeType(suggestion, PsiSubstitutor.EMPTY, null, closure); } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyLightProjectDescriptor.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyLightProjectDescriptor.groovy index 3a2d9883c9ca..1063666fdb70 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyLightProjectDescriptor.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyLightProjectDescriptor.groovy @@ -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. @@ -24,8 +24,7 @@ import com.intellij.openapi.vfs.JarFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor -import static org.jetbrains.plugins.groovy.util.TestUtils.getMockGroovy2_1LibraryName -import static org.jetbrains.plugins.groovy.util.TestUtils.getMockGroovy2_2LibraryName +import static org.jetbrains.plugins.groovy.util.TestUtils.* /** * @author Max Medvedev @@ -33,6 +32,7 @@ import static org.jetbrains.plugins.groovy.util.TestUtils.getMockGroovy2_2Librar class GroovyLightProjectDescriptor extends DefaultLightProjectDescriptor { public static final GroovyLightProjectDescriptor GROOVY_2_1 = new GroovyLightProjectDescriptor(mockGroovy2_1LibraryName) public static final GroovyLightProjectDescriptor GROOVY_2_2 = new GroovyLightProjectDescriptor(mockGroovy2_2LibraryName) + public static final GroovyLightProjectDescriptor GROOVY_2_3 = new GroovyLightProjectDescriptor(mockGroovy2_3LibraryName) private final String myLibPath diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy new file mode 100644 index 000000000000..382322409b10 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy @@ -0,0 +1,75 @@ +/* + * 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.resolve + +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor + +/** + * Created by Max Medvedev on 10/02/14 + */ +class TypeInference2_3Test extends TypeInferenceTestBase { + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return GroovyLightProjectDescriptor.GROOVY_2_3 + } + + public void testContravariantType() throws Exception { + doTest('''\ +import groovy.transform.CompileStatic +import java.util.concurrent.Callable + +@CompileStatic +class TestCase { + + interface Action { + void execute(T thing) + } + + static class Wrapper { + + private final T thing + + Wrapper(T thing) { + this.thing = thing + } + + void contravariantTake(Action action) { + action.execute(thing) + } + + } + + static Wrapper wrap(Callable callable) { + new Wrapper(callable.call()) + } + + static Integer dub(Integer integer) { + integer * 2 + } + + static void main(String[] args) { + wrap { + 1 + } contravariantTake { + dub(it) // fails static compile, 'it' is not known to be Integer + } + } + +} +''', 'java.lang.Integer') + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy index b084562d738e..f02b94000714 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy @@ -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,31 +18,20 @@ package org.jetbrains.plugins.groovy.lang.resolve import com.intellij.psi.PsiIntersectionType import com.intellij.psi.PsiReference import com.intellij.psi.PsiType -import org.intellij.lang.annotations.Language import org.jetbrains.plugins.groovy.lang.psi.GroovyFile import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType -import org.jetbrains.plugins.groovy.util.TestUtils import static com.intellij.psi.CommonClassNames.* /** * @author ven */ -public class TypeInferenceTest extends GroovyResolveTestCase { - final String basePath = TestUtils.testDataPath + "resolve/inference/" - - @Override - protected void setUp() { - super.setUp() - - myFixture.addClass("package java.math; public class BigDecimal extends Number implements Comparable {}"); - } +public class TypeInferenceTest extends TypeInferenceTestBase { public void testTryFinallyFlow() { GrReferenceExpression ref = (GrReferenceExpression)configureByFile("tryFinallyFlow/A.groovy").element; @@ -707,17 +696,4 @@ class Any { doExprTest('1f/2.4', 'java.lang.Double') } - private void doTest(@Language("Groovy") String text, String type) { - def file = myFixture.configureByText('_.groovy', text) - def ref = file.findReferenceAt(myFixture.editor.caretModel.offset) as GrReferenceExpression - def actual = ref.type - assertType(type, actual) - } - - private void doExprTest(@Language("Groovy") String text, String expectedType) { - GroovyFile file = myFixture.configureByText('_.groovy', text) as GroovyFile - GrStatement lastStatement = file.statements.last() - assertInstanceOf lastStatement, GrExpression - assertType(expectedType, (lastStatement as GrExpression).type) - } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTestBase.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTestBase.groovy new file mode 100644 index 000000000000..52c1e14ecfdd --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTestBase.groovy @@ -0,0 +1,51 @@ +/* + * 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.resolve + +import org.intellij.lang.annotations.Language +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression +import org.jetbrains.plugins.groovy.util.TestUtils + +/** + * Created by Max Medvedev on 10/02/14 + */ +abstract class TypeInferenceTestBase extends GroovyResolveTestCase { + final String basePath = TestUtils.testDataPath + "resolve/inference/" + + @Override + protected void setUp() { + super.setUp() + + myFixture.addClass("package java.math; public class BigDecimal extends Number implements Comparable {}"); + } + + protected void doTest(@Language("Groovy") String text, String type) { + def file = myFixture.configureByText('_.groovy', text) + def ref = file.findReferenceAt(myFixture.editor.caretModel.offset) as GrReferenceExpression + def actual = ref.type + assertType(type, actual) + } + + protected void doExprTest(@Language("Groovy") String text, String expectedType) { + GroovyFile file = myFixture.configureByText('_.groovy', text) as GroovyFile + GrStatement lastStatement = file.statements.last() + assertInstanceOf lastStatement, GrExpression + assertType(expectedType, (lastStatement as GrExpression).type) + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java index b7094311ec71..e53433333487 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java @@ -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,6 +59,7 @@ public abstract class TestUtils { public static final String GROOVY_JAR_18 = "groovy-1.8.0-beta-2.jar"; public static final String GROOVY_JAR_21 = "groovy-all-2.1.3.jar"; public static final String GROOVY_JAR_22 = "groovy-all-2.2.0-beta-1.jar"; + public static final String GROOVY_JAR_23 = "groovy-all-2.3.0.jar"; public static String getMockJdkHome() { return getAbsoluteTestDataPath() + "/mockJDK"; @@ -92,6 +93,10 @@ public abstract class TestUtils { return getAbsoluteTestDataPath() + "/mockGroovyLib2.2"; } + private static String getMockGroovy2_3LibraryHome() { + return getAbsoluteTestDataPath() + "/mockGroovyLib2.3"; + } + public static String getMockGroovy1_8LibraryName() { return getMockGroovy1_8LibraryHome()+"/"+GROOVY_JAR_18; } @@ -104,6 +109,10 @@ public abstract class TestUtils { return getMockGroovy2_2LibraryHome() + "/" + GROOVY_JAR_22; } + public static String getMockGroovy2_3LibraryName() { + return getMockGroovy2_3LibraryHome() + "/" + GROOVY_JAR_23; + } + public static PsiFile createPseudoPhysicalGroovyFile(final Project project, final String text) throws IncorrectOperationException { return createPseudoPhysicalFile(project, TEMP_FILE, text); }