diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrStringConverter.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrStringConverter.java index 54de570b64e6..b9c983e73735 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrStringConverter.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrStringConverter.java @@ -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 reduceTypeConstraint(@NotNull PsiType leftType, - @NotNull PsiType rightType, - @NotNull Position position) { + public Collection 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)) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrTypeConverter.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrTypeConverter.java index 317c33cd1455..99fe6b0eee92 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrTypeConverter.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/GrTypeConverter.java @@ -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 reduceTypeConstraint(@NotNull PsiType leftType, - @NotNull PsiType rightType, - @NotNull Position position) { + public Collection reduceTypeConstraint(@NotNull PsiType leftType, + @NotNull PsiType rightType, + @NotNull Position position, + @NotNull PsiElement context) { return null; } + public enum Position { EXPLICIT_CAST, ASSIGNMENT, 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 52d775cd8e25..1bcc1921a898 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,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) { - 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? { - 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) - } } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/TypePositionConstraint.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/TypePositionConstraint.kt index eaa3ce5b8968..543d12f7724e 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/TypePositionConstraint.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/inference/TypePositionConstraint.kt @@ -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 diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt index 1360d0140d55..b07020550842 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt @@ -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? { + if (rightType !is GroovyClosureType) return null + return processSAMConversion(leftType, rightType, context) + } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt index a41b17d66e7a..c479fdd6ed04 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt @@ -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? { + 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 { + val constraints = mutableListOf() + 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) +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy30HighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy30HighlightingTest.groovy index 9312b10d520c..d28d4539c16b 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy30HighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy30HighlightingTest.groovy @@ -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 list = [] + +list.sort(Comparator.comparing(A::m)) +''', GroovyAssignabilityCheckInspection + } + + void 'test method reference to SAM conversion 2'() { + highlightingTest ''' + +class A { + def String m(){ + + } +} +List 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 m(Integer a); +} + +def T foo(SAM 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 list = [] +// +//list.sort(Comparator.comparing(A::m))