diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/api/ExplicitRuntimeTypeArgument.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/api/ExplicitRuntimeTypeArgument.kt new file mode 100644 index 000000000000..3b5d0abf4aef --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/api/ExplicitRuntimeTypeArgument.kt @@ -0,0 +1,6 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.groovy.lang.resolve.api + +import com.intellij.psi.PsiType + +class ExplicitRuntimeTypeArgument(override val type: PsiType?, override val runtimeType: PsiType?) : Argument diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/FunctionalExpressionConstraint.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/FunctionalExpressionConstraint.kt index 075cf42addea..892802930dd0 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/FunctionalExpressionConstraint.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/FunctionalExpressionConstraint.kt @@ -1,24 +1,30 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.resolve.processors.inference -import com.intellij.psi.PsiClassType -import com.intellij.psi.PsiType +import com.intellij.psi.* +import com.intellij.psi.PsiClassType.ClassResolveResult import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil.getNonWildcardParameterization import com.intellij.psi.impl.source.resolve.graphInference.constraints.ConstraintFormula +import com.intellij.psi.util.TypeConversionUtil import org.jetbrains.plugins.groovy.lang.psi.api.GrFunctionalExpression 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.lang.psi.typeEnhancers.GrTypeConverter.Position.METHOD_PARAMETER +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.GROOVY_LANG_CLOSURE +import org.jetbrains.plugins.groovy.lang.resolve.api.Applicability +import org.jetbrains.plugins.groovy.lang.resolve.api.ExplicitRuntimeTypeArgument import org.jetbrains.plugins.groovy.lang.sam.findSingleAbstractMethod import org.jetbrains.plugins.groovy.lang.sam.isSamConversionAllowed +import org.jetbrains.plugins.groovy.lang.typing.GroovyClosureType -class FunctionalExpressionConstraint(private val expression: GrFunctionalExpression, private val leftType: PsiType) : GrConstraintFormula() { +class FunctionalExpressionConstraint(private val expression: GrFunctionalExpression, + private val leftType: PsiType) : GrConstraintFormula() { override fun reduce(session: GroovyInferenceSession, constraints: MutableList): Boolean { if (leftType !is PsiClassType) return true val returnType by lazy(LazyThreadSafetyMode.NONE) { expression.returnType } - if (TypesUtil.isClassType(leftType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { + if (TypesUtil.isClassType(leftType, GROOVY_LANG_CLOSURE)) { val parameters = leftType.parameters if (parameters.size != 1) return true if (returnType == null || returnType == PsiType.VOID) { @@ -27,30 +33,86 @@ class FunctionalExpressionConstraint(private val expression: GrFunctionalExpress constraints.add(TypeConstraint(parameters[0], returnType, expression)) } else { - val samReturnType = callSamReturnType() - if (samReturnType == null) { - constraints.add(TypeConstraint(leftType, TypesUtil.createTypeByFQClassName(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, expression), expression)) - return true - } - if (returnType == null || returnType == PsiType.VOID) { - return true - } - constraints.add(TypeConstraint(samReturnType, returnType, expression)) + processSAMConversion(constraints) } return true } - private fun callSamReturnType(): PsiType? { - if (isSamConversionAllowed(expression)) { - val groundType = (leftType as? PsiClassType)?.let { getNonWildcardParameterization(it) } ?: return null - val resolveResult = (groundType as PsiClassType).resolveGenerics() - - val samClass = resolveResult.element ?: return null - - val sam = findSingleAbstractMethod(samClass) ?: return null - - return resolveResult.substitutor.substitute(sam.returnType) + private fun processSAMConversion(constraints: MutableList) { + val pair = getSingleAbstractMethod() + if (pair == null) { + constraints.add(TypeConstraint(leftType, TypesUtil.createTypeByFQClassName(GROOVY_LANG_CLOSURE, expression), expression)) + return } - return null + val (sam, classResolveResult) = pair + + val groundClass = classResolveResult.element ?: return + val groundType = groundTypeForExplicitlyTypedClosure(sam, groundClass) + + if (groundType != null) { + constraints.add(TypeConstraint(leftType, groundType, expression)) + } + + val samReturnType = classResolveResult.substitutor.substitute(sam.returnType) + if (samReturnType == null || samReturnType == PsiType.VOID) { + return + } + val returnType = expression.returnType + if (returnType == null) { + return + } + + constraints.add(TypeConstraint(samReturnType, returnType, expression)) } -} + + private fun getSingleAbstractMethod(): Pair? { + if (!isSamConversionAllowed(expression)) return null + val groundType = (leftType as? PsiClassType)?.let { getNonWildcardParameterization(it) } ?: return null + val resolveResult = (groundType as PsiClassType).resolveGenerics() + + val samClass = resolveResult.element ?: return null + + val sam = findSingleAbstractMethod(samClass) ?: return null + return sam to resolveResult + } + + /** + * JLS 18.5.3 + * com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil.getFunctionalTypeExplicit + */ + private fun groundTypeForExplicitlyTypedClosure(sam: PsiMethod, groundClass: PsiClass): PsiClassType? { + val closureType = expression.type as? GroovyClosureType ?: return null + val parameters = expression.parameters + val types = parameters.map { it.declaredType } + if (types.filterNotNull().isEmpty()) return null // implicitly typed Closure + + val typeParameters = groundClass.typeParameters ?: return null + if (typeParameters.isEmpty()) return null + + val samContainingClass = sam.containingClass ?: return null + val groundClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(samContainingClass, groundClass, PsiSubstitutor.EMPTY) + + // erase all ground class parameters to null, otherwise explicit closure signature will be inapplicable + val erasingSubstitutor = PsiSubstitutor.createSubstitutor(typeParameters.associate { it to PsiType.NULL }) + val samParameterTypes = sam.parameterList.parameters.map { it.type } + val arguments = samParameterTypes.map { + val withInheritance = groundClassSubstitutor.substitute(it) + ExplicitRuntimeTypeArgument(withInheritance, TypeConversionUtil.erasure(erasingSubstitutor.substitute(withInheritance))) + } + + val argumentMapping = closureType.applyTo(arguments).find { it.applicability() == Applicability.applicable } ?: return null + + val samSession = GroovyInferenceSession(typeParameters, PsiSubstitutor.EMPTY, expression) + argumentMapping.expectedTypes.forEach { (expectedType, argument) -> + val leftType = samSession.substituteWithInferenceVariables(groundClassSubstitutor.substitute(expectedType)) + samSession.addConstraint(TypePositionConstraint(ExpectedType(leftType, METHOD_PARAMETER), argument.type, expression)) + } + if (!samSession.repeatInferencePhases()) { + return null + } + val resultSubstitutor = samSession.result() + + val elementFactory = JavaPsiFacade.getElementFactory(expression.project) + return elementFactory.createType(groundClass, resultSubstitutor) + } +} \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy index 384f74db0429..a303e35d70d5 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.highlighting import com.intellij.codeInsight.generation.OverrideImplementExploreUtil @@ -33,7 +33,7 @@ public void exec(T t, Action f, X x) { def foo() { exec('foo', { String t, Integer x -> ; }, 1) - exec('foo', { Integer t, Integer x -> ; }, 1) + exec('foo', { Integer t, Integer x -> ; }, 1) } ''') } @@ -66,7 +66,7 @@ public void exec(T t, Action f, X x) { def foo() { exec('foo', { String s, Integer x -> print s + x }, 1) - exec('foo', { Integer s, Integer x -> print 9 }, 1) + exec('foo', { Integer s, Integer x -> print 9 }, 1) } ''') } 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 index 5d9812efbe6d..0f769deaa771 100644 --- 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 @@ -1,4 +1,4 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.resolve import com.intellij.testFramework.LightProjectDescriptor @@ -6,6 +6,8 @@ import org.jetbrains.plugins.groovy.GroovyProjectDescriptors import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall import org.jetbrains.plugins.groovy.util.TypingTest +import static com.intellij.psi.CommonClassNames.JAVA_LANG_INTEGER + /** * Created by Max Medvedev on 10/02/14 */ @@ -289,4 +291,125 @@ class B { } ''', GrMethodCall, 'java.util.List' } + + void 'test inference from explicit typed SAM argument'() { + doTest ''' +interface SAM { + void accept(O out) +} + +def R samMethod(SAM mapper) { +} + +samMethod({Integer i->}) + +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (nested generic)'() { + doTest ''' +interface SAM { + void accept(Collection> out) +} + +def R samMethod(SAM mapper) { +} + +samMethod({Collection> i->}) + +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (several type params)'() { + doTest ''' +public interface SAM { + void flatMap(T value, Collection out) throws Exception; +} + +class C { + public R flatMap(SAM f) { + return null + } +} + +new C().flatMap { + String s, Collection c -> +} +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (with return type)'() { + doTest ''' +public interface SAM { + void flatMap(T value, Collection out) throws Exception; +} + +class C { + public R flatMap(SAM f) { + return null + } +} + +new C().flatMap { + String s, Collection c -> new String[10] +} +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (with lambda)'() { + doTest ''' +public interface SAM { + void flatMap(T value, Collection out) throws Exception; +} + +class C { + public R flatMap(SAM f) { + return null + } +} + +new C().flatMap((String s, Collection c) -> new String[10]) + + +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (with default values)'() { + doTest ''' +public interface SAM { + void flatMap(T value, Collection out) throws Exception; +} + +class C { + public R flatMap(SAM f) { + return null + } +} + +new C().flatMap { + String s, Collection c, Double d = 1.0 -> new String[10] +} +''', JAVA_LANG_INTEGER + } + + void 'test inference from explicit typed SAM argument (with SAM inheritance)'() { + doTest ''' +public interface SAM { + void flatMap(T value, Collection out) throws Exception; +} + +public interface Inheritor extends SAM { +} + +class C { + public R flatMap(Inheritor f) { + return null + } +} + +new C().flatMap { + String s, Collection c -> new String[10] +} +''', JAVA_LANG_INTEGER + } }