Recognize possible generic property call chain ('a.b<T>.c') on '::' LHS.
This commit is contained in:
+95
-16
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
|||||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors.*
|
import org.jetbrains.kotlin.diagnostics.Errors.*
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
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.TypeUtils.NO_EXPECTED_TYPE
|
||||||
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo
|
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo
|
||||||
import java.lang.UnsupportedOperationException
|
import java.lang.UnsupportedOperationException
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
sealed class DoubleColonLHS(val type: KotlinType) {
|
sealed class DoubleColonLHS(val type: KotlinType) {
|
||||||
@@ -176,12 +178,55 @@ class DoubleColonExpressionResolver(
|
|||||||
(lhs is KtCallExpression && lhs.canBeReservedGenericPropertyCall())
|
(lhs is KtCallExpression && lhs.canBeReservedGenericPropertyCall())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtCallExpression.canBeReservedGenericPropertyCall(): Boolean =
|
private fun KtExpression.getQualifierChainParts(): List<KtExpression>? {
|
||||||
// This condition captures 'name<T>::...'
|
if (this !is KtQualifiedExpression) return listOf(this)
|
||||||
// TODO 'expr.name<T>', 'expr<T>.name' - discuss
|
|
||||||
calleeExpression is KtNameReferenceExpression &&
|
val result = ArrayDeque<KtExpression>()
|
||||||
valueArguments.isEmpty() &&
|
var finger: KtQualifiedExpression = this
|
||||||
typeArguments.isNotEmpty()
|
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? {
|
private fun resolveReservedExpressionOnLHS(expression: KtExpression, c: ExpressionTypingContext): DoubleColonLHS.Expression? {
|
||||||
val doubleColonExpression = expression.parent as? KtDoubleColonExpression ?:
|
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<Boolean, DoubleColonLHS?> {
|
||||||
|
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? {
|
private fun resolveDoubleColonLHS(doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext): DoubleColonLHS? {
|
||||||
val resultForExpr = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsExpression, this::resolveExpressionOnLHS)
|
val resultForExpr = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsExpression, this::resolveExpressionOnLHS)
|
||||||
if (resultForExpr != null) {
|
if (resultForExpr != null) {
|
||||||
@@ -217,16 +304,8 @@ class DoubleColonExpressionResolver(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val resultForReservedExpr = tryResolveLHS(doubleColonExpression, c,
|
val (isReservedExpressionSyntax, doubleColonLHS) = resolveReservedExpressionSyntaxOnDoubleColonLHS(doubleColonExpression, c)
|
||||||
this::shouldTryResolveLHSAsReservedExpression,
|
if (isReservedExpressionSyntax) return doubleColonLHS
|
||||||
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 resultForType = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsType) { expression, context ->
|
val resultForType = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsType) { expression, context ->
|
||||||
resolveTypeOnLHS(expression, doubleColonExpression, context)
|
resolveTypeOnLHS(expression, doubleColonExpression, context)
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_VARIABLE
|
||||||
|
package test
|
||||||
|
|
||||||
|
object Wrong
|
||||||
|
object Right
|
||||||
|
|
||||||
|
class a {
|
||||||
|
class b<T> {
|
||||||
|
class c {
|
||||||
|
fun foo() = Wrong
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.foo() = Right
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
val a: List<Int> = null!!
|
||||||
|
|
||||||
|
val <T> List<T>.b: Int get() = 42
|
||||||
|
|
||||||
|
val Int.c: Int get() = 42
|
||||||
|
|
||||||
|
val test1: () -> Right = <!RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS!><!DEBUG_INFO_MISSING_UNRESOLVED!>a<!>.<!DEBUG_INFO_MISSING_UNRESOLVED!>b<!><<!DEBUG_INFO_MISSING_UNRESOLVED!>Int<!>>.<!DEBUG_INFO_MISSING_UNRESOLVED!>c<!><!>::<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>foo<!>
|
||||||
|
val test1a: () -> Right = a.b.c::foo
|
||||||
|
|
||||||
|
val test2: () -> Right = <!RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS!><!DEBUG_INFO_MISSING_UNRESOLVED!>a<!>.<!DEBUG_INFO_MISSING_UNRESOLVED!>b<!><<!DEBUG_INFO_MISSING_UNRESOLVED!>Int<!>>.<!DEBUG_INFO_MISSING_UNRESOLVED!>c<!><!>?::<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>foo<!>
|
||||||
|
}
|
||||||
+54
@@ -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<kotlin.Int>
|
||||||
|
public final val test1: () -> test.Right
|
||||||
|
public final val test1a: () -> test.Right
|
||||||
|
public final val test2: () -> test.Right
|
||||||
|
public final val </*0*/ T> kotlin.collections.List<T>.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</*0*/ T> {
|
||||||
|
public constructor b</*0*/ T>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1860,6 +1860,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("syntheticExtensionOnLHS.kt")
|
||||||
public void testSyntheticExtensionOnLHS() throws Exception {
|
public void testSyntheticExtensionOnLHS() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/syntheticExtensionOnLHS.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/syntheticExtensionOnLHS.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user