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:
Alexander Udalov
2016-06-17 15:19:45 +03:00
parent 5a2f34b351
commit 5a6237b357
9 changed files with 125 additions and 52 deletions
@@ -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);
@@ -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)
@@ -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
}
@@ -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
}
@@ -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