diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt index ba3220b9495..bd6d3380f66 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.diagnostics.Errors.* +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode @@ -50,6 +51,7 @@ import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo import java.lang.UnsupportedOperationException +import java.util.* import javax.inject.Inject sealed class DoubleColonLHS(val type: KotlinType) { @@ -176,12 +178,55 @@ class DoubleColonExpressionResolver( (lhs is KtCallExpression && lhs.canBeReservedGenericPropertyCall()) } - private fun KtCallExpression.canBeReservedGenericPropertyCall(): Boolean = - // This condition captures 'name::...' - // TODO 'expr.name', 'expr.name' - discuss - calleeExpression is KtNameReferenceExpression && - valueArguments.isEmpty() && - typeArguments.isNotEmpty() + private fun KtExpression.getQualifierChainParts(): List? { + if (this !is KtQualifiedExpression) return listOf(this) + + val result = ArrayDeque() + var finger: KtQualifiedExpression = this + while (true) { + if (finger.operationSign != KtTokens.DOT) return null + + finger.selectorExpression?.let { result.push(it) } + + val receiver = finger.receiverExpression + if (receiver is KtQualifiedExpression) { + finger = receiver + } + else { + result.push(receiver) + return result.toList() + } + } + } + + private fun shouldTryResolveLHSAsReservedCallChain(expression: KtDoubleColonExpression): Boolean { + val lhs = (expression.receiverExpression as? KtQualifiedExpression) ?: return false + val parts = lhs.getQualifierChainParts() ?: return false + return parts.all { it.canBeReservedGenericPropertyCall() } && + parts.any { it is KtCallExpression && it.typeArguments.isNotEmpty() } + } + + private fun KtExpression?.canBeReservedGenericPropertyCall(): Boolean = + getQualifiedNameStringPart() != null + + private fun KtExpression?.getQualifiedNameStringPart(): String? = + when (this) { + is KtNameReferenceExpression -> + text + is KtCallExpression -> + if (valueArguments.isEmpty() && typeArguments.isNotEmpty()) + (calleeExpression as? KtNameReferenceExpression)?.text + else + null + else -> + null + } + + private fun KtQualifiedExpression.buildNewExpressionForReservedGenericPropertyCallChainResolution(): KtExpression? { + val parts = this.getQualifierChainParts()?.map { it.getQualifiedNameStringPart() ?: return null } ?: return null + val qualifiedExpressionText = parts.joinToString(separator = ".") + return KtPsiFactory(this).createExpression(qualifiedExpressionText) + } private fun resolveReservedExpressionOnLHS(expression: KtExpression, c: ExpressionTypingContext): DoubleColonLHS.Expression? { val doubleColonExpression = expression.parent as? KtDoubleColonExpression ?: @@ -206,6 +251,48 @@ class DoubleColonExpressionResolver( } } + private fun resolveReservedCallChainOnLHS(expression: KtExpression, c: ExpressionTypingContext): DoubleColonLHS.Expression? { + if (expression !is KtQualifiedExpression) return null + + val newExpression = expression.buildNewExpressionForReservedGenericPropertyCallChainResolution() ?: return null + + val temporaryTraceAndCache = TemporaryTraceAndCache.create(c, "resolve reserved generic property call chain in '::' LHS", newExpression) + val contextForCallChainResolution = + c.replaceTraceAndCache(temporaryTraceAndCache) + .replaceExpectedType(NO_EXPECTED_TYPE) + .replaceContextDependency(ContextDependency.INDEPENDENT) + + return resolveExpressionOnLHS(expression, contextForCallChainResolution) + } + + private fun resolveReservedExpressionSyntaxOnDoubleColonLHS(doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext): + Pair { + val resultForReservedExpr = tryResolveLHS(doubleColonExpression, c, + this::shouldTryResolveLHSAsReservedExpression, + this::resolveReservedExpressionOnLHS) + if (resultForReservedExpr != null) { + val lhs = resultForReservedExpr.lhs + if (lhs != null) { + c.trace.report(RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS.on(resultForReservedExpr.expression)) + return Pair(true, resultForReservedExpr.commit()) + } + } + + val resultForReservedCallChain = tryResolveLHS(doubleColonExpression, c, + this::shouldTryResolveLHSAsReservedCallChain, + this::resolveReservedCallChainOnLHS) + if (resultForReservedCallChain != null) { + val lhs = resultForReservedCallChain.lhs + if (lhs != null) { + c.trace.report(RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS.on(resultForReservedCallChain.expression)) + // DO NOT commit trace from resultForReservedCallChain here + return Pair(true, null) + } + } + + return Pair(false, null) + } + private fun resolveDoubleColonLHS(doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext): DoubleColonLHS? { val resultForExpr = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsExpression, this::resolveExpressionOnLHS) if (resultForExpr != null) { @@ -217,16 +304,8 @@ class DoubleColonExpressionResolver( } } - val resultForReservedExpr = tryResolveLHS(doubleColonExpression, c, - this::shouldTryResolveLHSAsReservedExpression, - this::resolveReservedExpressionOnLHS) - if (resultForReservedExpr != null) { - val lhs = resultForReservedExpr.lhs - if (lhs != null) { - c.trace.report(RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS.on(resultForReservedExpr.expression)) - return resultForReservedExpr.commit() - } - } + val (isReservedExpressionSyntax, doubleColonLHS) = resolveReservedExpressionSyntaxOnDoubleColonLHS(doubleColonExpression, c) + if (isReservedExpressionSyntax) return doubleColonLHS val resultForType = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsType) { expression, context -> resolveTypeOnLHS(expression, doubleColonExpression, context) diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.kt b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.kt new file mode 100644 index 00000000000..a8865eae1d6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.kt @@ -0,0 +1,28 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +package test + +object Wrong +object Right + +class a { + class b { + class c { + fun foo() = Wrong + } + } +} + +fun Int.foo() = Right + +class Test { + val a: List = null!! + + val List.b: Int get() = 42 + + val Int.c: Int get() = 42 + + val test1: () -> Right = a.b<Int>.c::foo + val test1a: () -> Right = a.b.c::foo + + val test2: () -> Right = a.b<Int>.c?::foo +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.txt b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.txt new file mode 100644 index 00000000000..41f98de3f0f --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.txt @@ -0,0 +1,54 @@ +package + +package test { + public fun kotlin.Int.foo(): test.Right + + public object Right { + private constructor Right() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class Test { + public constructor Test() + public final val a: kotlin.collections.List + public final val test1: () -> test.Right + public final val test1a: () -> test.Right + public final val test2: () -> test.Right + public final val kotlin.collections.List.b: kotlin.Int + public final val kotlin.Int.c: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public object Wrong { + private constructor Wrong() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class a { + public constructor a() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class b { + public constructor b() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class c { + public constructor c() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): test.Wrong + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 10c682fa4ef..2327e6148ab 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -1860,6 +1860,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("reservedExpressionSyntax3.kt") + public void testReservedExpressionSyntax3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax3.kt"); + doTest(fileName); + } + @TestMetadata("syntheticExtensionOnLHS.kt") public void testSyntheticExtensionOnLHS() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/syntheticExtensionOnLHS.kt");