mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[groovy] IDEA-228890 Add ground type inference for explicitly typed closures
GitOrigin-RevId: 5151deb8f464ace37e19e27fb536c3882891894e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a6abe31943
commit
8e905ce4db
+6
@@ -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
|
||||
+89
-27
@@ -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)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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>
|
||||
}
|
||||
''')
|
||||
}
|
||||
|
||||
+124
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user