[groovy] IDEA-228890 Add ground type inference for explicitly typed closures

GitOrigin-RevId: 5151deb8f464ace37e19e27fb536c3882891894e
This commit is contained in:
alexey.afanasiev
2020-03-23 08:02:12 +00:00
committed by intellij-monorepo-bot
parent a6abe31943
commit 8e905ce4db
4 changed files with 222 additions and 31 deletions
@@ -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
@@ -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<in ConstraintFormula>): 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<in ConstraintFormula>) {
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<PsiMethod, ClassResolveResult>? {
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)
}
}
@@ -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 <T, X> void exec(T t, Action<T, X> f, X x) {
def foo() {
exec('foo', { String t, Integer x -> ; }, 1)
exec('foo', { Integer t, Integer x -> ; }, 1)
exec<warning descr="'exec' in '_' cannot be applied to '(java.lang.String, groovy.lang.Closure<java.lang.Void>, java.lang.Integer)'">('foo', { Integer t, Integer x -> ; }, 1)</warning>
}
''')
}
@@ -66,7 +66,7 @@ public <T, X> void exec(T t, Action<T, X> 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<warning descr="'exec' in '_' cannot be applied to '(java.lang.String, groovy.lang.Closure, java.lang.Integer)'">('foo', { Integer s, Integer x -> print 9 }, 1)</warning>
}
''')
}
@@ -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<O> {
void accept(O out)
}
def <R> R samMethod(SAM<R> mapper) {
}
s<caret>amMethod({Integer i->})
''', JAVA_LANG_INTEGER
}
void 'test inference from explicit typed SAM argument (nested generic)'() {
doTest '''
interface SAM<O> {
void accept(Collection<List<O>> out)
}
def <R> R samMethod(SAM<R> mapper) {
}
s<caret>amMethod({Collection<List<Integer>> i->})
''', JAVA_LANG_INTEGER
}
void 'test inference from explicit typed SAM argument (several type params)'() {
doTest '''
public interface SAM<T, O> {
void flatMap(T value, Collection<O> out) throws Exception;
}
class C<T> {
public <R> R flatMap(SAM<T, R> f) {
return null
}
}
new C<String>().flat<caret>Map {
String s, Collection<Integer> c ->
}
''', JAVA_LANG_INTEGER
}
void 'test inference from explicit typed SAM argument (with return type)'() {
doTest '''
public interface SAM<T, O> {
void flatMap(T value, Collection<O> out) throws Exception;
}
class C<T> {
public <R> R flatMap(SAM<T, R> f) {
return null
}
}
new C<String>().flat<caret>Map {
String s, Collection<Integer> c -> new String[10]
}
''', JAVA_LANG_INTEGER
}
void 'test inference from explicit typed SAM argument (with lambda)'() {
doTest '''
public interface SAM<T, O> {
void flatMap(T value, Collection<O> out) throws Exception;
}
class C<T> {
public <R> R flatMap(SAM<T, R> f) {
return null
}
}
new C<String>().flat<caret>Map((String s, Collection<Integer> c) -> new String[10])
''', JAVA_LANG_INTEGER
}
void 'test inference from explicit typed SAM argument (with default values)'() {
doTest '''
public interface SAM<T, O> {
void flatMap(T value, Collection<O> out) throws Exception;
}
class C<T> {
public <R> R flatMap(SAM<T, R> f) {
return null
}
}
new C<String>().flat<caret>Map {
String s, Collection<Integer> 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<T, O> {
void flatMap(T value, Collection<O> out) throws Exception;
}
public interface Inheritor<U> extends SAM<String, U> {
}
class C {
public <R> R flatMap(Inheritor<R> f) {
return null
}
}
new C().flat<caret>Map {
String s, Collection<Integer> c -> new String[10]
}
''', JAVA_LANG_INTEGER
}
}