Refactor resolution of double colon expression LHS
Try resolving the LHS only when it looks (PSI-wise) like it could represent a type element. This, for example, allows "illegal selector" error to be reported on weird expressions like '""?.""::class'. Also remove expression text from the "illegal selector" diagnostic, it's not needed and can screw up the error message if the text is too big
This commit is contained in:
@@ -510,7 +510,7 @@ public interface Errors {
|
||||
|
||||
// Call resolution
|
||||
|
||||
DiagnosticFactory1<KtExpression, String> ILLEGAL_SELECTOR = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory0<KtExpression> ILLEGAL_SELECTOR = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory2<KtExpression, KtExpression, KotlinType> FUNCTION_EXPECTED = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory2<KtExpression, KtExpression, Boolean> FUNCTION_CALL_EXPECTED = DiagnosticFactory2.create(ERROR, CALL_EXPRESSION);
|
||||
|
||||
+1
-1
@@ -480,7 +480,7 @@ public class DefaultErrorMessages {
|
||||
|
||||
MAP.put(INSTANCE_ACCESS_BEFORE_SUPER_CALL, "Cannot access ''{0}'' before superclass constructor has been called", NAME);
|
||||
|
||||
MAP.put(ILLEGAL_SELECTOR, "Expression ''{0}'' cannot be a selector (occur after a dot)", STRING);
|
||||
MAP.put(ILLEGAL_SELECTOR, "The expression cannot be a selector (occur after a dot)");
|
||||
|
||||
MAP.put(NO_TAIL_CALLS_FOUND, "A function is marked as tail-recursive but no tail calls are found");
|
||||
MAP.put(VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION, "A type annotation is required on a value parameter");
|
||||
|
||||
@@ -308,7 +308,7 @@ class CallExpressionResolver(
|
||||
selectorExpression, receiver, callOperationNode, context, initialDataFlowInfoForArguments)
|
||||
is KtExpression -> {
|
||||
expressionTypingServices.getTypeInfo(selectorExpression, context)
|
||||
context.trace.report(ILLEGAL_SELECTOR.on(selectorExpression, selectorExpression.text))
|
||||
context.trace.report(ILLEGAL_SELECTOR.on(selectorExpression))
|
||||
noTypeInfo(context)
|
||||
}
|
||||
else /*null*/ -> noTypeInfo(context)
|
||||
|
||||
+102
-46
@@ -84,7 +84,7 @@ class DoubleColonExpressionResolver(
|
||||
c.trace.report(UNSUPPORTED.on(expression, "Class literals with empty left hand side are not yet supported"))
|
||||
}
|
||||
else {
|
||||
val result = resolveDoubleColonLHS(expression.receiverExpression!!, expression, c)
|
||||
val result = resolveDoubleColonLHS(expression, c)
|
||||
val type = result?.type
|
||||
if (type != null && !type.isError) {
|
||||
checkClassLiteral(c, expression, result!!)
|
||||
@@ -134,12 +134,12 @@ class DoubleColonExpressionResolver(
|
||||
|
||||
private fun KtExpression.canBeConsideredProperType(): Boolean {
|
||||
return when (this) {
|
||||
is KtSimpleNameExpression -> true
|
||||
is KtDotQualifiedExpression -> {
|
||||
receiverExpression.canBeConsideredProperType() && selectorExpression.let { selector ->
|
||||
selector is KtSimpleNameExpression || (selector is KtCallExpression && selector.isWithoutValueArguments)
|
||||
}
|
||||
}
|
||||
is KtSimpleNameExpression ->
|
||||
true
|
||||
is KtCallExpression ->
|
||||
isWithoutValueArguments
|
||||
is KtDotQualifiedExpression ->
|
||||
receiverExpression.canBeConsideredProperType() && selectorExpression.let { it != null && it.canBeConsideredProperType() }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -152,45 +152,101 @@ class DoubleColonExpressionResolver(
|
||||
return lhs.canBeConsideredProperExpression() && !expression.hasQuestionMarks /* TODO: test this */
|
||||
}
|
||||
|
||||
private fun resolveDoubleColonLHS(
|
||||
expression: KtExpression, doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext
|
||||
): DoubleColonLHS? {
|
||||
// First, try resolving the LHS as expression, if possible
|
||||
private fun shouldTryResolveLHSAsType(expression: KtDoubleColonExpression): Boolean {
|
||||
val lhs = expression.receiverExpression
|
||||
return lhs != null && lhs.canBeConsideredProperType()
|
||||
}
|
||||
|
||||
if (shouldTryResolveLHSAsExpression(doubleColonExpression)) {
|
||||
val traceForExpr = TemporaryTraceAndCache.create(c, "resolve '::' LHS as expression", expression)
|
||||
val contextForExpr = c.replaceTraceAndCache(traceForExpr).replaceExpectedType(NO_EXPECTED_TYPE)
|
||||
val typeInfo = expressionTypingServices.getTypeInfo(expression, contextForExpr)
|
||||
val type = typeInfo.type
|
||||
|
||||
if (type != null) {
|
||||
// Be careful not to call a utility function to get a resolved call by an expression which may accidentally
|
||||
// deparenthesize that expression, as this is undesirable here
|
||||
val call = traceForExpr.trace.bindingContext[BindingContext.CALL, expression.getQualifiedElementSelector()]
|
||||
val resolvedCall = call.getResolvedCall(traceForExpr.trace.bindingContext)
|
||||
val implicitReferenceToCompanion =
|
||||
resolvedCall != null &&
|
||||
resolvedCall.resultingDescriptor.let { result ->
|
||||
result is FakeCallableDescriptorForObject &&
|
||||
result.classDescriptor.companionObjectDescriptor != null
|
||||
}
|
||||
|
||||
val resolvedToFunctionWithoutArguments =
|
||||
resolvedCall != null &&
|
||||
expression.canBeConsideredProperType() &&
|
||||
resolvedCall.resultingDescriptor !is VariableDescriptor
|
||||
|
||||
if (!implicitReferenceToCompanion && !resolvedToFunctionWithoutArguments) {
|
||||
traceForExpr.commit()
|
||||
return DoubleColonLHS.Expression(typeInfo).apply {
|
||||
c.trace.record(BindingContext.DOUBLE_COLON_LHS, expression, this)
|
||||
}
|
||||
}
|
||||
private fun resolveDoubleColonLHS(doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext): DoubleColonLHS? {
|
||||
val resultForExpr = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsExpression, this::resolveExpressionOnLHS)
|
||||
if (resultForExpr != null) {
|
||||
val lhs = resultForExpr.lhs
|
||||
if (lhs != null) {
|
||||
return resultForExpr.commit()
|
||||
}
|
||||
}
|
||||
|
||||
// Then, try resolving it as type
|
||||
val resultForType = tryResolveLHS(doubleColonExpression, c, this::shouldTryResolveLHSAsType) { expression, context ->
|
||||
resolveTypeOnLHS(expression, doubleColonExpression, context)
|
||||
}
|
||||
if (resultForType != null) {
|
||||
val lhs = resultForType.lhs
|
||||
if (lhs != null) {
|
||||
return resultForType.commit()
|
||||
}
|
||||
}
|
||||
|
||||
// If the LHS could be resolved neither as an expression nor as a type, we should still type-check it to allow all diagnostics
|
||||
// to be reported and references to be resolved. For that, we commit one of the applicable traces here, preferring the expression
|
||||
if (resultForExpr != null) return resultForExpr.commit()
|
||||
if (resultForType != null) return resultForType.commit()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private class LHSResolutionResult(
|
||||
val lhs: DoubleColonLHS?,
|
||||
val expression: KtExpression,
|
||||
val traceAndCache: TemporaryTraceAndCache
|
||||
) {
|
||||
fun commit(): DoubleColonLHS? {
|
||||
if (lhs != null) {
|
||||
traceAndCache.trace.record(BindingContext.DOUBLE_COLON_LHS, expression, lhs)
|
||||
}
|
||||
traceAndCache.commit()
|
||||
return lhs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if the LHS is definitely not an expression. Returns a non-null result if a resolution was attempted and led to
|
||||
* either a successful result or not.
|
||||
*/
|
||||
private fun tryResolveLHS(
|
||||
doubleColonExpression: KtDoubleColonExpression,
|
||||
context: ExpressionTypingContext,
|
||||
criterion: (KtDoubleColonExpression) -> Boolean,
|
||||
resolve: (KtExpression, ExpressionTypingContext) -> DoubleColonLHS?
|
||||
): LHSResolutionResult? {
|
||||
val expression = doubleColonExpression.receiverExpression ?: return null
|
||||
|
||||
if (!criterion(doubleColonExpression)) return null
|
||||
|
||||
val traceAndCache = TemporaryTraceAndCache.create(context, "resolve '::' LHS", doubleColonExpression)
|
||||
val c = context.replaceTraceAndCache(traceAndCache).replaceExpectedType(NO_EXPECTED_TYPE)
|
||||
|
||||
val lhs = resolve(expression, c)
|
||||
return LHSResolutionResult(lhs, expression, traceAndCache)
|
||||
}
|
||||
|
||||
private fun resolveExpressionOnLHS(expression: KtExpression, c: ExpressionTypingContext): DoubleColonLHS.Expression? {
|
||||
val typeInfo = expressionTypingServices.getTypeInfo(expression, c)
|
||||
|
||||
// TODO: do not lose data flow info maybe
|
||||
if (typeInfo.type == null) return null
|
||||
|
||||
// Be careful not to call a utility function to get a resolved call by an expression which may accidentally
|
||||
// deparenthesize that expression, as this is undesirable here
|
||||
val call = c.trace.bindingContext[BindingContext.CALL, expression.getQualifiedElementSelector()]
|
||||
val resolvedCall = call.getResolvedCall(c.trace.bindingContext)
|
||||
|
||||
if (resolvedCall != null) {
|
||||
val resultingDescriptor = resolvedCall.resultingDescriptor
|
||||
if (resultingDescriptor is FakeCallableDescriptorForObject) {
|
||||
val classDescriptor = resultingDescriptor.classDescriptor
|
||||
if (classDescriptor.companionObjectDescriptor != null) return null
|
||||
}
|
||||
|
||||
// Check if this is resolved to a function (with the error "arguments expected"), such as in "Runnable::class"
|
||||
if (expression.canBeConsideredProperType() && resultingDescriptor !is VariableDescriptor) return null
|
||||
}
|
||||
|
||||
return DoubleColonLHS.Expression(typeInfo)
|
||||
}
|
||||
|
||||
private fun resolveTypeOnLHS(
|
||||
expression: KtExpression, doubleColonExpression: KtDoubleColonExpression, c: ExpressionTypingContext
|
||||
): DoubleColonLHS.Type? {
|
||||
val qualifierResolutionResult =
|
||||
qualifiedExpressionResolver.resolveDescriptorForDoubleColonLHS(expression, c.scope, c.trace, c.isDebuggerContext)
|
||||
|
||||
@@ -229,9 +285,7 @@ class DoubleColonExpressionResolver(
|
||||
TypeUtils.makeNullableAsSpecified(possiblyBareType.actualType, doubleColonExpression.hasQuestionMarks)
|
||||
}
|
||||
|
||||
return DoubleColonLHS.Type(type, possiblyBareType).apply {
|
||||
c.trace.record(BindingContext.DOUBLE_COLON_LHS, expression, this)
|
||||
}
|
||||
return DoubleColonLHS.Type(type, possiblyBareType)
|
||||
}
|
||||
|
||||
private fun isAllowedInClassLiteral(type: KotlinType): Boolean {
|
||||
@@ -256,7 +310,7 @@ class DoubleColonExpressionResolver(
|
||||
fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression, c: ExpressionTypingContext): KotlinTypeInfo {
|
||||
val callableReference = expression.callableReference
|
||||
if (callableReference.getReferencedName().isEmpty()) {
|
||||
expression.receiverExpression?.let { resolveDoubleColonLHS(it, expression, c) }
|
||||
if (!expression.isEmptyLHS) resolveDoubleColonLHS(expression, c)
|
||||
c.trace.report(UNRESOLVED_REFERENCE.on(callableReference, callableReference))
|
||||
val errorType = ErrorUtils.createErrorType("Empty callable reference")
|
||||
return dataFlowAnalyzer.createCheckedTypeInfo(errorType, c, expression)
|
||||
@@ -354,7 +408,9 @@ class DoubleColonExpressionResolver(
|
||||
context: ExpressionTypingContext,
|
||||
resolveArgumentsMode: ResolveArgumentsMode
|
||||
): Pair<DoubleColonLHS?, OverloadResolutionResults<*>?> {
|
||||
val lhsResult = expression.receiverExpression?.let { resolveDoubleColonLHS(it, expression, context) }
|
||||
val lhsResult =
|
||||
if (expression.isEmptyLHS) null
|
||||
else resolveDoubleColonLHS(expression, context)
|
||||
|
||||
val resolutionResults =
|
||||
resolvePossiblyAmbiguousCallableReference(expression, lhsResult, context, resolveArgumentsMode, callResolver)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package foo
|
||||
|
||||
fun test() {
|
||||
<!UNRESOLVED_REFERENCE!>foo<!>::test
|
||||
<!EXPRESSION_EXPECTED_PACKAGE_FOUND!>foo<!>::test
|
||||
}
|
||||
|
||||
// FILE: qualifiedName.kt
|
||||
@@ -12,5 +12,5 @@ fun test() {
|
||||
package foo.bar
|
||||
|
||||
fun test() {
|
||||
foo.<!UNRESOLVED_REFERENCE!>bar<!>::test
|
||||
foo.<!EXPRESSION_EXPECTED_PACKAGE_FOUND!>bar<!>::test
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION
|
||||
|
||||
fun test() {
|
||||
"a".<!ILLEGAL_SELECTOR!>"b"<!>::<!UNRESOLVED_REFERENCE!>foo<!>
|
||||
"a".<!ILLEGAL_SELECTOR!>"b"<!>::class
|
||||
"a".<!ILLEGAL_SELECTOR!>"b"<!>.<!ILLEGAL_SELECTOR!>"c"<!>::<!UNRESOLVED_REFERENCE!>foo<!>
|
||||
"a".<!ILLEGAL_SELECTOR!>"b"<!>.<!ILLEGAL_SELECTOR!>"c"<!>::class
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package
|
||||
|
||||
public fun test(): kotlin.Unit
|
||||
@@ -8607,6 +8607,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("illegalSelectorCallableReference.kt")
|
||||
public void testIllegalSelectorCallableReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inExpr.kt")
|
||||
public void testInExpr() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/incompleteCode/inExpr.kt");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// "Create function" "false"
|
||||
// ERROR: A 'return' expression required in a function with a block body ('{...}')
|
||||
// ERROR: Expression 'return groupsByLength.values.firstOrNull { group -> {group.size == maximumSizeOfGroup} }' cannot be a selector (occur after a dot)
|
||||
// ERROR: The expression cannot be a selector (occur after a dot)
|
||||
// ERROR: Type inference failed: inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T?<br>cannot be applied to<br>receiver: Collection<List<String>> arguments: ((List<String>) -> () -> Boolean)<br>
|
||||
// ERROR: Type mismatch: inferred type is (List<String>) -> () -> Boolean but (List<String>) -> Boolean was expected
|
||||
// ERROR: Unresolved reference: maximumSizeOfGroup
|
||||
|
||||
Reference in New Issue
Block a user