From 5a6237b3573374ad14c0508ea37b2c7b5431626c Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 17 Jun 2016 15:19:45 +0300 Subject: [PATCH] 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 --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 +- .../rendering/DefaultErrorMessages.java | 2 +- .../resolve/calls/CallExpressionResolver.kt | 2 +- .../DoubleColonExpressionResolver.kt | 148 ++++++++++++------ .../tests/callableReference/packageInLhs.kt | 4 +- .../illegalSelectorCallableReference.kt | 8 + .../illegalSelectorCallableReference.txt | 3 + .../checkers/DiagnosticsTestGenerated.java | 6 + .../createFunction/call/kt10983.kt | 2 +- 9 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.kt create mode 100644 compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 01d1bbdd1d4..947ac003265 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -510,7 +510,7 @@ public interface Errors { // Call resolution - DiagnosticFactory1 ILLEGAL_SELECTOR = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 ILLEGAL_SELECTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory2 FUNCTION_EXPECTED = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 FUNCTION_CALL_EXPECTED = DiagnosticFactory2.create(ERROR, CALL_EXPRESSION); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 8d426b6f253..935ac29993d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt index 384e6a1ea93..4fa9e588f20 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt @@ -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) 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 8daf4ae4869..04d8b194162 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt @@ -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?> { - 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) diff --git a/compiler/testData/diagnostics/tests/callableReference/packageInLhs.kt b/compiler/testData/diagnostics/tests/callableReference/packageInLhs.kt index 36b68744fac..43086090ff1 100644 --- a/compiler/testData/diagnostics/tests/callableReference/packageInLhs.kt +++ b/compiler/testData/diagnostics/tests/callableReference/packageInLhs.kt @@ -4,7 +4,7 @@ package foo fun test() { - foo::test + foo::test } // FILE: qualifiedName.kt @@ -12,5 +12,5 @@ fun test() { package foo.bar fun test() { - foo.bar::test + foo.bar::test } diff --git a/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.kt b/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.kt new file mode 100644 index 00000000000..36a67ca024b --- /dev/null +++ b/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.kt @@ -0,0 +1,8 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION + +fun test() { + "a"."b"::foo + "a"."b"::class + "a"."b"."c"::foo + "a"."b"."c"::class +} diff --git a/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.txt b/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.txt new file mode 100644 index 00000000000..93e27f34c8c --- /dev/null +++ b/compiler/testData/diagnostics/tests/incompleteCode/illegalSelectorCallableReference.txt @@ -0,0 +1,3 @@ +package + +public fun test(): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 92f9acdd1c6..3315bcfe0c1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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"); diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/kt10983.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/kt10983.kt index ce36696205d..248efa48ce8 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/call/kt10983.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/kt10983.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 Iterable.firstOrNull(predicate: (T) -> Boolean): T?
cannot be applied to
receiver: Collection> arguments: ((List) -> () -> Boolean)
// ERROR: Type mismatch: inferred type is (List) -> () -> Boolean but (List) -> Boolean was expected // ERROR: Unresolved reference: maximumSizeOfGroup