From 3fed4e6dc7d7888c71e7a0d20080bbfedb0cb24d Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Sun, 2 Jul 2017 23:48:13 +0300 Subject: [PATCH] Improve diagnostic for unresolved reference when function expected #KT-10657 Fixed --- .../calls/tasks/TracingStrategyImpl.java | 48 ++++++++++++++++++- ...functionExpectedWhenSeveralInvokesExist.kt | 13 +++++ ...unctionExpectedWhenSeveralInvokesExist.txt | 12 +++++ ...eportFunctionExpectedWhenOneInvokeExist.kt | 12 +++++ ...portFunctionExpectedWhenOneInvokeExist.txt | 11 +++++ .../checkers/DiagnosticsTestGenerated.java | 12 +++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.kt create mode 100644 compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.txt create mode 100644 compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.kt create mode 100644 compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tasks/TracingStrategyImpl.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tasks/TracingStrategyImpl.java index 76641551e5d..2f3d27d56cc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tasks/TracingStrategyImpl.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tasks/TracingStrategyImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,27 @@ package org.jetbrains.kotlin.resolve.calls.tasks; +import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.builtins.FunctionTypesKt; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; +import org.jetbrains.kotlin.descriptors.FunctionDescriptor; +import org.jetbrains.kotlin.descriptors.VariableDescriptor; +import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.psi.Call; import org.jetbrains.kotlin.psi.KtReferenceExpression; import org.jetbrains.kotlin.resolve.BindingTrace; +import org.jetbrains.kotlin.resolve.calls.callResolverUtil.CallResolverUtilKt; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject; import org.jetbrains.kotlin.types.ErrorUtils; +import org.jetbrains.kotlin.types.KotlinType; import java.util.Collection; +import java.util.List; import static org.jetbrains.kotlin.diagnostics.Errors.UNRESOLVED_REFERENCE; import static org.jetbrains.kotlin.diagnostics.Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER; @@ -82,6 +91,41 @@ public class TracingStrategyImpl extends AbstractTracingStrategy { @Override public void unresolvedReferenceWrongReceiver(@NotNull BindingTrace trace, @NotNull Collection> candidates) { - trace.report(UNRESOLVED_REFERENCE_WRONG_RECEIVER.on(reference, candidates)); + VariableDescriptor variableDescriptor = isFunctionExpectedError(candidates); + if (variableDescriptor != null) { + trace.report(Errors.FUNCTION_EXPECTED.on(reference, reference, variableDescriptor.getType())); + } + else { + trace.report(UNRESOLVED_REFERENCE_WRONG_RECEIVER.on(reference, candidates)); + } + } + + @Nullable + private static VariableDescriptor isFunctionExpectedError( + @NotNull Collection> candidates + ) { + List variables = CollectionsKt.map(candidates, TracingStrategyImpl::variableIfFunctionExpectedError); + List distinctVariables = CollectionsKt.distinct(variables); + return CollectionsKt.singleOrNull(distinctVariables); + } + + @Nullable + private static VariableDescriptor variableIfFunctionExpectedError( + @NotNull ResolvedCall candidate + ) { + if (!(candidate instanceof VariableAsFunctionResolvedCall)) return null; + + ResolvedCall variableCall = ((VariableAsFunctionResolvedCall) candidate).getVariableCall(); + ResolvedCall functionCall = ((VariableAsFunctionResolvedCall) candidate).getFunctionCall(); + + KotlinType type = variableCall.getCandidateDescriptor().getType(); + + boolean nonFunctionalVar = variableCall.getStatus().isSuccess() && !FunctionTypesKt.isFunctionType(type); + Call functionPsiCall = functionCall.getCall(); + if (nonFunctionalVar && CallResolverUtilKt.isInvokeCallOnVariable(functionPsiCall) && functionPsiCall.getValueArguments().isEmpty()) { + return variableCall.getCandidateDescriptor(); + } + + return null; } } diff --git a/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.kt b/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.kt new file mode 100644 index 00000000000..2b4de189a43 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.kt @@ -0,0 +1,13 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +fun Int.invoke(a: Int) {} +fun Int.invoke(a: Int, b: Int) {} + +class SomeClass + +fun test(identifier: SomeClass, fn: String.() -> Unit) { + identifier() + identifier(123) + identifier(1, 2) + 1.fn() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.txt b/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.txt new file mode 100644 index 00000000000..0e215f487df --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.txt @@ -0,0 +1,12 @@ +package + +public fun test(/*0*/ identifier: SomeClass, /*1*/ fn: kotlin.String.() -> kotlin.Unit): kotlin.Unit +public fun kotlin.Int.invoke(/*0*/ a: kotlin.Int): kotlin.Unit +public fun kotlin.Int.invoke(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit + +public final class SomeClass { + public constructor SomeClass() + 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 +} diff --git a/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.kt b/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.kt new file mode 100644 index 00000000000..1a602f09cc3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.kt @@ -0,0 +1,12 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +fun Int.invoke() {} + +class SomeClass + +fun test(identifier: SomeClass, fn: String.() -> Unit) { + identifier() + identifier(123) + identifier(1, 2) + 1.fn() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.txt b/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.txt new file mode 100644 index 00000000000..b5050c0960f --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.txt @@ -0,0 +1,11 @@ +package + +public fun test(/*0*/ identifier: SomeClass, /*1*/ fn: kotlin.String.() -> kotlin.Unit): kotlin.Unit +public fun kotlin.Int.invoke(): kotlin.Unit + +public final class SomeClass { + public constructor SomeClass() + 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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 9dc56aca301..882d55b4b56 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -17607,6 +17607,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("functionExpectedWhenSeveralInvokesExist.kt") + public void testFunctionExpectedWhenSeveralInvokesExist() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/invoke/functionExpectedWhenSeveralInvokesExist.kt"); + doTest(fileName); + } + @TestMetadata("implicitInvoke.kt") public void testImplicitInvoke() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/invoke/implicitInvoke.kt"); @@ -17697,6 +17703,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("reportFunctionExpectedWhenOneInvokeExist.kt") + public void testReportFunctionExpectedWhenOneInvokeExist() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/invoke/reportFunctionExpectedWhenOneInvokeExist.kt"); + doTest(fileName); + } + @TestMetadata("valNamedInvoke.kt") public void testValNamedInvoke() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/invoke/valNamedInvoke.kt");