[groovy] IDEA-242412 support generic type inference for method references

GitOrigin-RevId: 938c5511189a2b3a78497fd1fe207fd3898f2850
This commit is contained in:
alexey.afanasiev
2020-06-16 09:20:10 +03:00
committed by intellij-monorepo-bot
parent 51ada9381a
commit 1a1a2b05a6
7 changed files with 194 additions and 118 deletions
@@ -1,12 +1,13 @@
// 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.psi.typeEnhancers;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.source.resolve.graphInference.constraints.ConstraintFormula;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult;
import org.jetbrains.plugins.groovy.lang.resolve.processors.inference.GrConstraintFormula;
import java.util.Collection;
import java.util.Collections;
@@ -42,9 +43,10 @@ public class GrStringConverter extends GrTypeConverter {
@Nullable
@Override
public Collection<GrConstraintFormula> reduceTypeConstraint(@NotNull PsiType leftType,
@NotNull PsiType rightType,
@NotNull Position position) {
public Collection<ConstraintFormula> reduceTypeConstraint(@NotNull PsiType leftType,
@NotNull PsiType rightType,
@NotNull Position position,
@NotNull PsiElement context) {
if (position == Position.METHOD_PARAMETER &&
isClassType(leftType, JAVA_LANG_STRING) &&
isClassType(rightType, GROOVY_LANG_GSTRING)) {
@@ -1,8 +1,10 @@
// 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.psi.typeEnhancers;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.source.resolve.graphInference.constraints.ConstraintFormula;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
@@ -13,7 +15,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrTypeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult;
import org.jetbrains.plugins.groovy.lang.resolve.processors.inference.GrConstraintFormula;
import java.util.Collection;
@@ -59,12 +60,14 @@ public abstract class GrTypeConverter {
@NotNull GroovyPsiElement context);
@Nullable
public Collection<GrConstraintFormula> reduceTypeConstraint(@NotNull PsiType leftType,
@NotNull PsiType rightType,
@NotNull Position position) {
public Collection<ConstraintFormula> reduceTypeConstraint(@NotNull PsiType leftType,
@NotNull PsiType rightType,
@NotNull Position position,
@NotNull PsiElement context) {
return null;
}
public enum Position {
EXPLICIT_CAST,
ASSIGNMENT,
@@ -1,20 +1,13 @@
// 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.openapi.util.registry.Registry
import com.intellij.psi.*
import com.intellij.psi.PsiClassType.ClassResolveResult
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil.getNonWildcardParameterization
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiType
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.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.sam.processSAMConversion
import org.jetbrains.plugins.groovy.lang.typing.GroovyClosureType
class FunctionalExpressionConstraint(private val expression: GrFunctionalExpression,
@@ -34,87 +27,9 @@ class FunctionalExpressionConstraint(private val expression: GrFunctionalExpress
constraints.add(TypeConstraint(parameters[0], returnType, expression))
}
else {
processSAMConversion(constraints)
val closureType = expression.type as? GroovyClosureType ?: return true
constraints += processSAMConversion(leftType, closureType, expression)
}
return true
}
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
}
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? {
if (!Registry.`is`("groovy.use.explicitly.typed.closure.in.inference", true)) return null
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.resolve.processors.inference
import com.intellij.psi.PsiElement
@@ -18,7 +18,7 @@ class TypePositionConstraint(
if (!extension.isApplicableTo(expectedType.position)) {
continue
}
val reduced = extension.reduceTypeConstraint(expectedType.type, rightType, expectedType.position)
val reduced = extension.reduceTypeConstraint(expectedType.type, rightType, expectedType.position, context)
if (reduced != null) {
constraints += reduced
return true
@@ -2,7 +2,9 @@
package org.jetbrains.plugins.groovy.lang.sam
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiType
import com.intellij.psi.impl.source.resolve.graphInference.constraints.ConstraintFormula
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
@@ -35,4 +37,12 @@ class ClosureToSamConverter : GrTypeConverter() {
findSingleAbstractSignature(targetClass) ?: return null
return ConversionResult.OK
}
override fun reduceTypeConstraint(leftType: PsiType,
rightType: PsiType,
position: Position,
context: PsiElement): List<ConstraintFormula>? {
if (rightType !is GroovyClosureType) return null
return processSAMConversion(leftType, rightType, context)
}
}
@@ -1,33 +1,41 @@
/*
* Copyright 2000-2017 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.
*/
// 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.sam
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.*
import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil
import com.intellij.psi.impl.source.resolve.graphInference.constraints.ConstraintFormula
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.MethodSignature
import com.intellij.psi.util.TypeConversionUtil
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil.isTrait
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
import org.jetbrains.plugins.groovy.lang.resolve.api.Applicability
import org.jetbrains.plugins.groovy.lang.resolve.api.ExplicitRuntimeTypeArgument
import org.jetbrains.plugins.groovy.lang.resolve.processors.inference.*
import org.jetbrains.plugins.groovy.lang.typing.GroovyClosureType
fun findSingleAbstractMethod(clazz: PsiClass): PsiMethod? = findSingleAbstractSignatureCached(clazz)?.method
fun findSingleAbstractSignature(clazz: PsiClass): MethodSignature? = findSingleAbstractSignatureCached(clazz)
private fun findSingleAbstractMethodAndClass(type: PsiType): Pair<PsiMethod, PsiClassType.ClassResolveResult>? {
val groundType = (type as? PsiClassType)?.let { FunctionalInterfaceParameterizationUtil.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
}
private fun findSingleAbstractSignatureCached(clazz: PsiClass): HierarchicalMethodSignature? {
return CachedValuesManager.getCachedValue(clazz) {
CachedValueProvider.Result.create(doFindSingleAbstractSignature(clazz), clazz)
@@ -68,3 +76,79 @@ private fun isImplementedTraitMethod(method: PsiMethod): Boolean {
fun isSamConversionAllowed(context: PsiElement): Boolean {
return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2)
}
internal fun processSAMConversion(targetType: PsiType,
closureType: GroovyClosureType,
context: PsiElement): List<ConstraintFormula> {
val constraints = mutableListOf<ConstraintFormula>()
val pair = findSingleAbstractMethodAndClass(targetType)
if (pair == null) {
constraints.add(
TypeConstraint(targetType, TypesUtil.createTypeByFQClassName(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, context), context))
return constraints
}
val (sam, classResolveResult) = pair
val groundClass = classResolveResult.element ?: return constraints
val groundType = groundTypeForClosure(sam, groundClass, closureType, context)
if (groundType != null) {
constraints.add(TypeConstraint(targetType, groundType, context))
}
return constraints
}
private fun returnTypeConstraint(samReturnType: PsiType?,
returnType: PsiType?,
context: PsiElement): ConstraintFormula? {
if (returnType == null || samReturnType == null || samReturnType == PsiType.VOID) {
return null
}
return TypeConstraint(samReturnType, returnType, context)
}
/**
* JLS 18.5.3
* com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil.getFunctionalTypeExplicit
*/
private fun groundTypeForClosure(sam: PsiMethod,
groundClass: PsiClass,
closureType: GroovyClosureType,
context: PsiElement): PsiClassType? {
if (!Registry.`is`("groovy.use.explicitly.typed.closure.in.inference", true)) return null
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, context, false)
argumentMapping.expectedTypes.forEach { (expectedType, argument) ->
val leftType = samSession.substituteWithInferenceVariables(groundClassSubstitutor.substitute(expectedType))
samSession.addConstraint(
TypePositionConstraint(ExpectedType(leftType, GrTypeConverter.Position.METHOD_PARAMETER), argument.type, context))
}
val returnTypeConstraint = returnTypeConstraint(sam.returnType, closureType.returnType(arguments), context)
if (returnTypeConstraint != null) samSession.addConstraint(returnTypeConstraint)
if (!samSession.repeatInferencePhases()) {
return null
}
val resultSubstitutor = samSession.result()
val elementFactory = JavaPsiFacade.getElementFactory(context.project)
return elementFactory.createType(groundClass, resultSubstitutor)
}
@@ -1,9 +1,10 @@
// 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.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyProjectDescriptors
import org.jetbrains.plugins.groovy.codeInspection.assignment.GroovyAssignabilityCheckInspection
import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection
import org.jetbrains.plugins.groovy.lang.GroovyVersionBasedTest
import org.jetbrains.plugins.groovy.util.TestUtils
@@ -65,6 +66,58 @@ I i = {3}
'''
}
void 'test method reference to SAM conversion'() {
highlightingTest '''
class A {
def String m(){
}
}
List<A> list = []
list.sort(Comparator.comparing(A::m))
''', GroovyAssignabilityCheckInspection
}
void 'test method reference to SAM conversion 2'() {
highlightingTest '''
class A {
def String m(){
}
}
List<A> list = []
def c = A::m
list.sort(Comparator.comparing(c))
''', GroovyAssignabilityCheckInspection
}
void 'test method reference to SAM conversion with overload'() {
highlightingTest '''
class A {
String m(Integer i){
return null
}
Integer m(Thread i){
return null
}
}
interface SAM<T> {
T m(Integer a);
}
def <T> T foo(SAM<T> sam) {
}
def a = new A()
foo(a::m).toUpperCase()
''', GrUnresolvedAccessInspection
}
void 'test constructor reference static access'() {
fileHighlightingTest GrUnresolvedAccessInspection
}
@@ -77,3 +130,12 @@ I i = {3}
fileHighlightingTest()
}
}
//
//class A {
// def String m(){
//
// }
//}
//List<A> list = []
//
//list.sort(Comparator.comparing(A::m))