[FIR] Implement FUNCTION_CALL_EXPECTED

This commit is contained in:
Ivan Kochurkin
2021-06-22 23:23:02 +03:00
parent d048bccfa2
commit 59257e47c9
21 changed files with 106 additions and 65 deletions
@@ -102,6 +102,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val CALL_RESOLUTION by object : DiagnosticGroup("Call resolution") {
val CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS by error<KtExpression>()
val FUNCTION_CALL_EXPECTED by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) {
parameter<String>("functionName")
parameter<Boolean>("hasValueParameters")
}
}
val SUPER by object : DiagnosticGroup("Super") {
@@ -119,6 +119,7 @@ object FirErrors {
// Call resolution
val CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS by error0<KtExpression>()
val FUNCTION_CALL_EXPECTED by error2<PsiElement, String, Boolean>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
// Super
val SUPER_IS_NOT_AN_EXPRESSION by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.AMBI
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FQ_NAMES_IN_TYPES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FUNCTION_PARAMETERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NOT_RENDERED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING
@@ -149,6 +150,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_UPPER_BOUND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_VARARG_PARAMETER_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_CALL_EXPECTED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_DECLARATION_WITH_NO_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUN_INTERFACE_ABSTRACT_METHOD_WITH_DEFAULT_VALUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUN_INTERFACE_ABSTRACT_METHOD_WITH_TYPE_PARAMETERS
@@ -443,6 +445,7 @@ class FirDefaultErrorMessages {
TO_STRING
)
map.put(CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS, "Cannot create an instance of an abstract class")
map.put(FUNCTION_CALL_EXPECTED, "Function invocation ''{0}({1})'' expected", TO_STRING, FUNCTION_PARAMETERS)
// Supertypes
map.put(ENUM_AS_SUPERTYPE, "Enum as supertype")
@@ -119,4 +119,6 @@ object FirDiagnosticRenderers {
val NOT_RENDERED = Renderer<Any?> {
""
}
val FUNCTION_PARAMETERS = Renderer { hasValueParameters: Boolean -> if (hasValueParameters) "..." else "" }
}
@@ -40,6 +40,7 @@ private fun ConeDiagnostic.toFirDiagnostic(
is ConeUnresolvedSymbolError -> FirErrors.UNRESOLVED_REFERENCE.createOn(source, this.classId.asString())
is ConeUnresolvedNameError -> FirErrors.UNRESOLVED_REFERENCE.createOn(source, this.name.asString())
is ConeUnresolvedQualifierError -> FirErrors.UNRESOLVED_REFERENCE.createOn(source, this.qualifier)
is ConeFunctionCallExpectedError -> FirErrors.FUNCTION_CALL_EXPECTED.createOn(source, this.name.asString(), this.hasValueParameters)
is ConeHiddenCandidateError -> FirErrors.INVISIBLE_REFERENCE.createOn(source, this.candidateSymbol)
is ConeAmbiguityError -> when {
applicability.isSuccess -> FirErrors.OVERLOAD_RESOLUTION_AMBIGUITY.createOn(source, this.candidates.map { it.symbol })
@@ -136,14 +136,18 @@ class FirCallResolver(
val info: CallInfo, val applicability: CandidateApplicability, val candidates: Collection<Candidate>,
)
private fun <T : FirQualifiedAccess> collectCandidates(qualifiedAccess: T, name: Name): ResolutionResult {
private fun <T : FirQualifiedAccess> collectCandidates(
qualifiedAccess: T,
name: Name,
forceCallKind: CallKind? = null
): ResolutionResult {
val explicitReceiver = qualifiedAccess.explicitReceiver
val argumentList = (qualifiedAccess as? FirFunctionCall)?.argumentList ?: FirEmptyArgumentList
val typeArguments = (qualifiedAccess as? FirFunctionCall)?.typeArguments.orEmpty()
val info = CallInfo(
qualifiedAccess,
if (qualifiedAccess is FirFunctionCall) CallKind.Function else CallKind.VariableAccess,
if (qualifiedAccess is FirFunctionCall || forceCallKind == CallKind.Function) CallKind.Function else CallKind.VariableAccess,
name,
explicitReceiver,
argumentList,
@@ -195,16 +199,7 @@ class FirCallResolver(
return resolvedQualifierPart
}
val result = collectCandidates(qualifiedAccess, callee.name)
val reducedCandidates = result.candidates
val nameReference = createResolvedNamedReference(
callee,
callee.name,
result.info,
reducedCandidates,
result.applicability,
qualifiedAccess.explicitReceiver,
)
var result = collectCandidates(qualifiedAccess, callee.name)
if (qualifiedAccess.explicitReceiver == null) {
if (!result.applicability.isSuccess) {
@@ -217,6 +212,26 @@ class FirCallResolver(
qualifiedResolver.reset()
}
var functionCallExpected = false
if (result.candidates.isEmpty() && qualifiedAccess !is FirFunctionCall) {
val newResult = collectCandidates(qualifiedAccess, callee.name, CallKind.Function)
if (newResult.candidates.isNotEmpty()) {
result = newResult
functionCallExpected = true
}
}
val reducedCandidates = result.candidates
val nameReference = createResolvedNamedReference(
callee,
callee.name,
result.info,
reducedCandidates,
result.applicability,
qualifiedAccess.explicitReceiver,
functionCallExpected = functionCallExpected
)
val referencedSymbol = when (nameReference) {
is FirResolvedNamedReference -> nameReference.resolvedSymbol
is FirNamedReferenceWithCandidate -> nameReference.candidateSymbol
@@ -544,10 +559,34 @@ class FirCallResolver(
candidates: Collection<Candidate>,
applicability: CandidateApplicability,
explicitReceiver: FirExpression? = null,
createResolvedReferenceWithoutCandidateForLocalVariables: Boolean = true
createResolvedReferenceWithoutCandidateForLocalVariables: Boolean = true,
functionCallExpected: Boolean = false
): FirNamedReference {
val source = reference.source
return when {
functionCallExpected -> {
fun isValueParametersNotEmpty(candidate: Candidate): Boolean {
return (candidate.symbol.fir as? FirFunction)?.valueParameters?.size?.let { it > 0 } ?: false
}
val candidate = candidates.singleOrNull()
if (candidate != null) {
createErrorReferenceWithExistingCandidate(
candidate,
ConeFunctionCallExpectedError(name, isValueParametersNotEmpty(candidate)),
source,
transformer.resolutionContext,
components.resolutionStageRunner
)
} else {
buildErrorReference(
callInfo,
ConeFunctionCallExpectedError(name, candidates.any { isValueParametersNotEmpty(it) }),
source
)
}
}
candidates.isEmpty() -> buildErrorReference(
callInfo,
ConeUnresolvedNameError(name),
@@ -44,6 +44,10 @@ class ConeUnresolvedNameError(val name: Name) : ConeUnresolvedError() {
override val reason: String get() = "Unresolved name: $name"
}
class ConeFunctionCallExpectedError(val name: Name, val hasValueParameters: Boolean) : ConeDiagnostic() {
override val reason: String get() = "Function call expected: $name(${if (hasValueParameters) "..." else ""})"
}
class ConeHiddenCandidateError(
val candidateSymbol: FirBasedSymbol<*>
) : ConeDiagnostic() {
+2 -2
View File
@@ -21,8 +21,8 @@ fun test(l : java.util.List<Int>) {
val f : java.io.File? = null
Collections.<!UNRESOLVED_REFERENCE!>emptyList<!>
Collections.<!UNRESOLVED_REFERENCE!>emptyList<!><Int>
Collections.<!FUNCTION_CALL_EXPECTED!>emptyList<!>
Collections.<!FUNCTION_CALL_EXPECTED!>emptyList<!><<!CANNOT_INFER_PARAMETER_TYPE!>Int<!>>
Collections.emptyList<Int>()
Collections.<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>emptyList<!>()
@@ -1 +0,0 @@
fun test() = ("").<!UNRESOLVED_REFERENCE!>hashCode<!>::hashCode
@@ -1 +1,2 @@
// FIR_IDENTICAL
fun test() = ("").<!FUNCTION_CALL_EXPECTED!>hashCode<!>::hashCode
@@ -9,8 +9,8 @@ class Foo {
}
fun x(f : Foo) {
f.<!UNRESOLVED_REFERENCE!>prop<!>
f.<!UNRESOLVED_REFERENCE!>bar<!>
f.<!FUNCTION_CALL_EXPECTED!>prop<!>
f.<!FUNCTION_CALL_EXPECTED!>bar<!>
f.<!UNRESOLVED_REFERENCE!>a<!>()
<!UNRESOLVED_REFERENCE!>c<!>()
@@ -7,8 +7,8 @@ import checkSubtype
fun main(args : Array<String>) {
val x = checkSubtype<Any>(args[0])
if(x is java.lang.CharSequence) {
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!>) x.<!UNRESOLVED_REFERENCE!>length<!> else x.length() // OK
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!> || <!EQUALITY_NOT_APPLICABLE!>"b" == x<!>) x.<!UNRESOLVED_REFERENCE!>length<!> else x.length() // < THEN ERROR
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!> && <!EQUALITY_NOT_APPLICABLE!>"a" == x<!>) x.<!UNRESOLVED_REFERENCE!>length<!> else x.length() // < ELSE ERROR
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!>) x.<!FUNCTION_CALL_EXPECTED!>length<!> else x.length() // OK
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!> || <!EQUALITY_NOT_APPLICABLE!>"b" == x<!>) x.<!FUNCTION_CALL_EXPECTED!>length<!> else x.length() // < THEN ERROR
if (<!EQUALITY_NOT_APPLICABLE!>"a" == x<!> && <!EQUALITY_NOT_APPLICABLE!>"a" == x<!>) x.<!FUNCTION_CALL_EXPECTED!>length<!> else x.length() // < ELSE ERROR
}
}
@@ -1,5 +1,5 @@
fun import() {
<!UNRESOLVED_REFERENCE!>import<!> a<!SYNTAX!>.<!><!UNRESOLVED_REFERENCE!>*<!><!SYNTAX!><!>
<!FUNCTION_CALL_EXPECTED!>import<!> <!UNRESOLVED_REFERENCE!>a<!><!SYNTAX!>.<!><!UNRESOLVED_REFERENCE!>*<!><!SYNTAX!><!>
}
fun composite() {
@@ -7,9 +7,9 @@ fun composite() {
}
fun html() {
<!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>html<!>><!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>/<!><!UNRESOLVED_REFERENCE!>html<!>><!SYNTAX!><!>
<!SYNTAX!><<!><!FUNCTION_CALL_EXPECTED!>html<!><!UNRESOLVED_REFERENCE!>><!><!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>/<!><!FUNCTION_CALL_EXPECTED!>html<!>><!SYNTAX!><!>
}
fun html1() {
<!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>html<!>><!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>/<!><!UNRESOLVED_REFERENCE!>html<!>><!UNRESOLVED_REFERENCE!>html<!>
<!SYNTAX!><<!><!FUNCTION_CALL_EXPECTED!>html<!><!UNRESOLVED_REFERENCE!>><!><!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>/<!><!FUNCTION_CALL_EXPECTED!>html<!>><!ARGUMENT_TYPE_MISMATCH, FUNCTION_CALL_EXPECTED!>html<!>
}
@@ -1,6 +1,6 @@
// NI_EXPECTED_FILE
// See EA-76890 / KT-10843: NPE during analysis
fun lambda(x : Int?) = x?.<!UNRESOLVED_REFERENCE!>let<!> l {
fun lambda(x : Int?) = x?.<!FUNCTION_CALL_EXPECTED!>let<!> <!UNRESOLVED_REFERENCE!>l<!> {
y ->
if (y <!UNRESOLVED_REFERENCE!>><!> 0) return@l x
y
@@ -58,7 +58,7 @@ fun test(a: A, b: B) {
b.(foo)()
(b.<!UNRESOLVED_REFERENCE!>foo<!>)()
(<!UNRESOLVED_REFERENCE!>b.<!FUNCTION_CALL_EXPECTED!>foo<!><!>)()
foo(b)
(foo)(b)
@@ -1,37 +0,0 @@
// FILE: KotlinFile.kt
fun foo(javaClass: JavaClass) {
javaClass.isSomething = !javaClass.isSomething
javaClass.isSomething2 = !javaClass.isSomething2
javaClass.<!UNRESOLVED_REFERENCE!>something<!>
javaClass.isSomethingWrong
javaClass.<!UNRESOLVED_REFERENCE!>somethingWrong<!>
javaClass.<!UNRESOLVED_REFERENCE!>issueFlag<!>
javaClass.<!UNRESOLVED_REFERENCE!>isSueFlag<!>
}
// FILE: JavaClass.java
public class JavaClass {
public boolean isSomething() {
return true;
}
public void setSomething(boolean value) {
}
public boolean getIsSomething2() {
return true;
}
public void setIsSomething2(boolean value) {
}
public int isSomethingWrong() {
return 1;
}
public boolean issueFlag() {
return true;
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: KotlinFile.kt
fun foo(javaClass: JavaClass) {
javaClass.isSomething = !javaClass.isSomething
@@ -283,6 +283,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.FUNCTION_CALL_EXPECTED) { firDiagnostic ->
FunctionCallExpectedImpl(
firDiagnostic.a,
firDiagnostic.b,
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.SUPER_IS_NOT_AN_EXPRESSION) { firDiagnostic ->
SuperIsNotAnExpressionImpl(
firDiagnostic as FirPsiDiagnostic,
@@ -220,6 +220,12 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = CreatingAnInstanceOfAbstractClass::class
}
abstract class FunctionCallExpected : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = FunctionCallExpected::class
abstract val functionName: String
abstract val hasValueParameters: Boolean
}
abstract class SuperIsNotAnExpression : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = SuperIsNotAnExpression::class
}
@@ -329,6 +329,15 @@ internal class CreatingAnInstanceOfAbstractClassImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class FunctionCallExpectedImpl(
override val functionName: String,
override val hasValueParameters: Boolean,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.FunctionCallExpected(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class SuperIsNotAnExpressionImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
+2 -2
View File
@@ -18,8 +18,8 @@ fun test(l : List<Int>) {
val f : java.io.File? = null
Collections.<error descr="[UNRESOLVED_REFERENCE] Unresolved reference: emptyList">emptyList</error>
Collections.<error descr="[UNRESOLVED_REFERENCE] Unresolved reference: emptyList">emptyList</error><Int>
Collections.<error descr="[FUNCTION_CALL_EXPECTED] Function invocation 'emptyList()' expected">emptyList</error>
Collections.<error descr="[FUNCTION_CALL_EXPECTED] Function invocation 'emptyList()' expected">emptyList</error><<error descr="[CANNOT_INFER_PARAMETER_TYPE] cannot infer a type for this parameter. Please specify it explicitly.">Int</error>>
Collections.emptyList<Int>()
Collections.<error descr="[NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER] T">emptyList</error>()