FIR checker: report UNINITIALIZED_PARAMETER
This commit is contained in:
committed by
TeamCityServer
parent
d2f831b635
commit
bdfc879f00
+3
@@ -605,6 +605,9 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
val UNINITIALIZED_VARIABLE by error<KtSimpleNameExpression> {
|
||||
parameter<FirPropertySymbol>("variable")
|
||||
}
|
||||
val UNINITIALIZED_PARAMETER by error<KtSimpleNameExpression> {
|
||||
parameter<FirVariableSymbol<FirValueParameter>>("parameter")
|
||||
}
|
||||
val UNINITIALIZED_ENUM_ENTRY by error<KtSimpleNameExpression> {
|
||||
parameter<FirVariableSymbol<FirEnumEntry>>("enumEntry")
|
||||
}
|
||||
|
||||
@@ -370,6 +370,7 @@ object FirErrors {
|
||||
|
||||
// Control flow diagnostics
|
||||
val UNINITIALIZED_VARIABLE by error1<KtSimpleNameExpression, FirPropertySymbol>()
|
||||
val UNINITIALIZED_PARAMETER by error1<KtSimpleNameExpression, FirVariableSymbol<FirValueParameter>>()
|
||||
val UNINITIALIZED_ENUM_ENTRY by error1<KtSimpleNameExpression, FirVariableSymbol<FirEnumEntry>>()
|
||||
val UNINITIALIZED_ENUM_COMPANION by error1<KtSimpleNameExpression, FirRegularClassSymbol>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
|
||||
val VAL_REASSIGNMENT by error1<KtExpression, FirVariableSymbol<*>>()
|
||||
|
||||
+35
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirRealSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isInline
|
||||
@@ -12,19 +13,24 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOnWithSuppression
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
|
||||
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.typeContext
|
||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.arrayElementType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.isUnsignedTypeOrNullableUnsignedType
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
|
||||
object FirFunctionParameterChecker : FirFunctionChecker() {
|
||||
override fun check(declaration: FirFunction<*>, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
checkVarargParameters(declaration, context, reporter)
|
||||
checkParameterTypes(declaration, context, reporter)
|
||||
checkUninitializedParameter(declaration, context, reporter)
|
||||
}
|
||||
|
||||
private fun checkParameterTypes(declaration: FirFunction<*>, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
@@ -70,4 +76,33 @@ object FirFunctionParameterChecker : FirFunctionChecker() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUninitializedParameter(function: FirFunction<*>, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
for ((index, parameter) in function.valueParameters.withIndex()) {
|
||||
// Alas, CheckerContext.qualifiedAccesses stack is not available at this point.
|
||||
// Thus, manually visit default value expression and report the diagnostic on qualified accesses of interest.
|
||||
parameter.defaultValue?.accept(object : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
|
||||
val namedReference = qualifiedAccessExpression.calleeReference as? FirResolvedNamedReference ?: return
|
||||
val referredParameter = namedReference.resolvedSymbol.fir as? FirValueParameter ?: return
|
||||
val referredParameterIndex = function.valueParameters.indexOf(referredParameter)
|
||||
// Skip if the referred parameter is not declared in the same function.
|
||||
if (referredParameterIndex < 0) return
|
||||
|
||||
if (index <= referredParameterIndex) {
|
||||
reporter.reportOnWithSuppression(
|
||||
qualifiedAccessExpression,
|
||||
FirErrors.UNINITIALIZED_PARAMETER,
|
||||
referredParameter.symbol,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -271,6 +271,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_ON
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNEXPECTED_SAFE_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_ENUM_COMPANION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_ENUM_ENTRY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_VARIABLE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_LATEINIT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_NOT_NULL_ASSERTION
|
||||
@@ -830,7 +831,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
)
|
||||
|
||||
// Control flow diagnostics
|
||||
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", VARIABLE_NAME)
|
||||
map.put(UNINITIALIZED_VARIABLE, "Variable ''{0}'' must be initialized", VARIABLE_NAME)
|
||||
map.put(UNINITIALIZED_PARAMETER, "Parameter ''{0}'' is uninitialized here", VARIABLE_NAME)
|
||||
map.put(UNINITIALIZED_ENUM_ENTRY, "Enum entry ''{0}'' is uninitialized here", VARIABLE_NAME)
|
||||
map.put(UNINITIALIZED_ENUM_COMPANION, "Companion object of enum class ''{0}'' is uninitialized here", SYMBOL)
|
||||
map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", VARIABLE_NAME)
|
||||
|
||||
+21
-11
@@ -238,7 +238,7 @@ class BodyResolveContext(
|
||||
return FirMemberTypeParameterScope(this)
|
||||
}
|
||||
|
||||
private fun buildSecondaryConstructorParametersScope(constructor: FirConstructor): FirLocalScope =
|
||||
fun buildSecondaryConstructorParametersScope(constructor: FirConstructor): FirLocalScope =
|
||||
constructor.valueParameters.fold(FirLocalScope()) { acc, param -> acc.storeVariable(param) }
|
||||
|
||||
@PrivateForInline
|
||||
@@ -486,6 +486,12 @@ class BodyResolveContext(
|
||||
return withTowerDataCleanup {
|
||||
addLocalScope(FirLocalScope())
|
||||
if (function is FirSimpleFunction) {
|
||||
// Make all value parameters available in the local scope so that even one parameter that refers to another parameter,
|
||||
// which may not be initialized yet, can be resolved. [FirFunctionParameterChecker] will detect and report an error
|
||||
// if an uninitialized parameter is accessed by a preceding parameter.
|
||||
for (parameter in function.valueParameters) {
|
||||
storeVariable(parameter)
|
||||
}
|
||||
val receiverTypeRef = function.receiverTypeRef
|
||||
withLabelAndReceiverType(function.name, function, receiverTypeRef?.coneType, holder, f)
|
||||
} else {
|
||||
@@ -665,19 +671,23 @@ class BodyResolveContext(
|
||||
f: () -> T
|
||||
): T {
|
||||
// Default values of constructor can't access members of constructing class
|
||||
return withTowerDataMode(FirTowerDataMode.CONSTRUCTOR_HEADER) {
|
||||
if (!constructor.isPrimary) {
|
||||
addInaccessibleImplicitReceiverValue(owningClass, holder)
|
||||
}
|
||||
withTowerDataCleanup {
|
||||
addLocalScope(FirLocalScope())
|
||||
f()
|
||||
}
|
||||
}
|
||||
// But, let them get resolved, then [FirFunctionParameterChecker] will detect and report an error
|
||||
// if an uninitialized parameter is accessed by a preceding parameter.
|
||||
return forConstructorParametersOrDelegatedConstructorCall(constructor, owningClass, holder, f)
|
||||
}
|
||||
|
||||
@OptIn(PrivateForInline::class)
|
||||
fun <T> forDelegatedConstructor(
|
||||
inline fun <T> forDelegatedConstructorCall(
|
||||
constructor: FirConstructor,
|
||||
owningClass: FirRegularClass?,
|
||||
holder: SessionHolder,
|
||||
f: () -> T
|
||||
): T {
|
||||
return forConstructorParametersOrDelegatedConstructorCall(constructor, owningClass, holder, f)
|
||||
}
|
||||
|
||||
@OptIn(PrivateForInline::class)
|
||||
inline fun <T> forConstructorParametersOrDelegatedConstructorCall(
|
||||
constructor: FirConstructor,
|
||||
owningClass: FirRegularClass?,
|
||||
holder: SessionHolder,
|
||||
|
||||
+1
-3
@@ -594,9 +594,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
|
||||
ConeSimpleDiagnostic("No type for parameter", DiagnosticKind.ValueParameterWithNoTypeAnnotation)
|
||||
)
|
||||
)
|
||||
return context.withValueParameter(valueParameter) {
|
||||
valueParameter
|
||||
}
|
||||
return valueParameter
|
||||
}
|
||||
|
||||
dataFlowAnalyzer.enterValueParameter(valueParameter)
|
||||
|
||||
+1
-1
@@ -863,7 +863,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
|
||||
var result = delegatedConstructorCall
|
||||
try {
|
||||
val lastDispatchReceiver = implicitReceiverStack.lastDispatchReceiver()
|
||||
context.forDelegatedConstructor(containingConstructor, containingClass as? FirRegularClass, components) {
|
||||
context.forDelegatedConstructorCall(containingConstructor, containingClass as? FirRegularClass, components) {
|
||||
delegatedConstructorCall.transformChildren(transformer, ResolutionMode.ContextDependent)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
warning: ATTENTION!
|
||||
This build uses in-dev FIR:
|
||||
-Xuse-fir
|
||||
compiler/testData/cli/jvm/firError.kt:5:13: error: x must be initialized before access
|
||||
compiler/testData/cli/jvm/firError.kt:5:13: error: variable 'x' must be initialized
|
||||
println(x)
|
||||
^
|
||||
compiler/testData/cli/jvm/firError.kt:10:16: error: public subclass exposes its private-in-file supertype 'Private'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// DONT_TARGET_EXACT_BACKEND: WASM
|
||||
// WASM_MUTE_REASON: EXCEPTIONS_NOT_IMPLEMENTED
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// IGNORE_BACKEND: JS, JS_IR, NATIVE
|
||||
// IGNORE_BACKEND: JS_IR_ES6
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
interface Inter {
|
||||
fun foo(x: Int = <!UNRESOLVED_REFERENCE!>y<!>, y: Int = x)
|
||||
}
|
||||
|
||||
abstract class Abst {
|
||||
abstract fun foo(x: Int = <!UNRESOLVED_REFERENCE!>y<!>, y: Int = x)
|
||||
}
|
||||
|
||||
<!NON_MEMBER_FUNCTION_NO_BODY!>fun extraDiagnostics(x: Int = <!UNRESOLVED_REFERENCE!>y<!>, y: Int)<!>
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
interface Inter {
|
||||
fun foo(x: Int = <!UNINITIALIZED_PARAMETER!>y<!>, y: Int = x)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,33 @@ fun bar(x : Int = "", y : Int = x, z : String = y) {
|
||||
|
||||
// KT-371 Resolve default parameters for constructors
|
||||
|
||||
class A(x : Int = <!UNRESOLVED_REFERENCE!>y<!>, y : Int = x) { // None of the references is resolved, no types checked
|
||||
fun foo(bool: Boolean, a: Int = <!UNRESOLVED_REFERENCE!>b<!>, b: String = a) {}
|
||||
class A(x : Int = <!UNINITIALIZED_PARAMETER!>y<!>, y : Int = x) { // None of the references is resolved, no types checked
|
||||
constructor(x : Int = <!UNINITIALIZED_PARAMETER!>x<!>) : this(x, x)
|
||||
fun foo(bool: Boolean, a: Int = <!UNINITIALIZED_PARAMETER!>b<!>, b: String = a) {}
|
||||
}
|
||||
|
||||
val z = 3
|
||||
|
||||
fun foo(x: Int = <!UNRESOLVED_REFERENCE!>y<!>, y: Int = x, i : Int = z): Int = x + y
|
||||
fun foo(x: Int = <!UNINITIALIZED_PARAMETER!>y<!>, y: Int = x, i : Int = z): Int = x + y
|
||||
|
||||
fun foo(x: () -> Int = { <!UNINITIALIZED_PARAMETER!>y<!> }, y: Int = x(), i : Int = z): Int = x() + y
|
||||
|
||||
fun bar(x: () -> Int = { <!UNINITIALIZED_PARAMETER!>y<!>; 1 }, y: Int) {}
|
||||
|
||||
fun baz(
|
||||
x: () -> Int = {
|
||||
fun bar(xx: () -> Int = { <!UNINITIALIZED_PARAMETER!>y<!>; 1 }) = xx
|
||||
bar()()
|
||||
},
|
||||
y: Int
|
||||
) {
|
||||
}
|
||||
|
||||
fun boo(
|
||||
x: () -> Int = {
|
||||
fun bar(): Int = <!UNINITIALIZED_PARAMETER!>y<!>
|
||||
bar()
|
||||
},
|
||||
y: Int
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -8,9 +8,32 @@ fun bar(x : Int = <!TYPE_MISMATCH!>""<!>, y : Int = x, z : String = <!TYPE_MISMA
|
||||
// KT-371 Resolve default parameters for constructors
|
||||
|
||||
class A(x : Int = <!UNINITIALIZED_PARAMETER!>y<!>, y : Int = x) { // None of the references is resolved, no types checked
|
||||
constructor(x : Int = <!UNINITIALIZED_PARAMETER!>x<!>) : this(x, x)
|
||||
fun foo(bool: Boolean, a: Int = <!TYPE_MISMATCH, UNINITIALIZED_PARAMETER!>b<!>, b: String = <!TYPE_MISMATCH!>a<!>) {}
|
||||
}
|
||||
|
||||
val z = 3
|
||||
|
||||
fun foo(x: Int = <!UNINITIALIZED_PARAMETER!>y<!>, y: Int = x, i : Int = z): Int = x + y
|
||||
|
||||
fun foo(x: () -> Int = { <!UNINITIALIZED_PARAMETER{OI}!>y<!> }, y: Int = x(), i : Int = z): Int = x() + y
|
||||
|
||||
fun bar(x: () -> Int = { <!UNINITIALIZED_PARAMETER{OI}!>y<!>; 1 }, y: Int) {}
|
||||
|
||||
fun baz(
|
||||
x: () -> Int = {
|
||||
fun bar(xx: () -> Int = { <!UNINITIALIZED_PARAMETER{OI}!>y<!>; 1 }) = xx
|
||||
bar()()
|
||||
},
|
||||
y: Int
|
||||
) {
|
||||
}
|
||||
|
||||
fun boo(
|
||||
x: () -> Int = {
|
||||
fun bar(): Int = <!UNINITIALIZED_PARAMETER{OI}!>y<!>
|
||||
bar()
|
||||
},
|
||||
y: Int
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@ package
|
||||
|
||||
public val x: kotlin.String = ""
|
||||
public val z: kotlin.Int = 3
|
||||
public fun bar(/*0*/ x: () -> kotlin.Int = ..., /*1*/ y: kotlin.Int): kotlin.Unit
|
||||
public fun bar(/*0*/ x: kotlin.Int = ..., /*1*/ y: kotlin.Int = ..., /*2*/ z: kotlin.String = ...): kotlin.Unit
|
||||
public fun baz(/*0*/ x: () -> kotlin.Int = ..., /*1*/ y: kotlin.Int): kotlin.Unit
|
||||
public fun boo(/*0*/ x: () -> kotlin.Int = ..., /*1*/ y: kotlin.Int): kotlin.Unit
|
||||
public fun foo(/*0*/ x: () -> kotlin.Int = ..., /*1*/ y: kotlin.Int = ..., /*2*/ i: kotlin.Int = ...): kotlin.Int
|
||||
public fun foo(/*0*/ x: kotlin.Int = ..., /*1*/ y: kotlin.Int = ..., /*2*/ i: kotlin.Int = ...): kotlin.Int
|
||||
|
||||
public final class A {
|
||||
public constructor A(/*0*/ x: kotlin.Int = ...)
|
||||
public constructor A(/*0*/ x: kotlin.Int = ..., /*1*/ y: kotlin.Int = ...)
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun foo(/*0*/ bool: kotlin.Boolean, /*1*/ a: kotlin.Int = ..., /*2*/ b: kotlin.String = ...): kotlin.Unit
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
|
||||
fun foo() {
|
||||
fun bar(x: String, y: String = x) {}
|
||||
fun baz(x: Int = <!UNRESOLVED_REFERENCE!>y<!>, y: Int) {}
|
||||
}
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
|
||||
fun foo() {
|
||||
fun foo(x: Int = <!UNINITIALIZED_PARAMETER!>x<!>) {}
|
||||
fun bar(x: String, y: String = x) {}
|
||||
fun baz(x: Int = <!UNINITIALIZED_PARAMETER!>y<!>, y: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ fun foo() {
|
||||
10
|
||||
}
|
||||
fun bar(x: Exception = `_`) {}
|
||||
class Bar(`_`: Exception = `_`) {
|
||||
class Bar(`_`: Exception = <!UNINITIALIZED_PARAMETER!>`_`<!>) {
|
||||
inner class Bar2(x: Exception = `_`) { }
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -23,7 +23,7 @@ fun foo() {
|
||||
10
|
||||
}
|
||||
fun bar(x: Exception = `_`) {}
|
||||
class Bar(`_`: Exception = `_`) {
|
||||
class Bar(`_`: Exception = <!UNINITIALIZED_PARAMETER!>`_`<!>) {
|
||||
inner class Bar2(x: Exception = `_`) { }
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -1730,6 +1730,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.UNINITIALIZED_PARAMETER) { firDiagnostic ->
|
||||
UninitializedParameterImpl(
|
||||
firSymbolBuilder.variableLikeBuilder.buildVariableLikeSymbol(firDiagnostic.a.fir),
|
||||
firDiagnostic as FirPsiDiagnostic<*>,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.UNINITIALIZED_ENUM_ENTRY) { firDiagnostic ->
|
||||
UninitializedEnumEntryImpl(
|
||||
firSymbolBuilder.variableLikeBuilder.buildVariableLikeSymbol(firDiagnostic.a.fir),
|
||||
|
||||
+5
@@ -1215,6 +1215,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
abstract val variable: KtVariableSymbol
|
||||
}
|
||||
|
||||
abstract class UninitializedParameter : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||
override val diagnosticClass get() = UninitializedParameter::class
|
||||
abstract val parameter: KtVariableLikeSymbol
|
||||
}
|
||||
|
||||
abstract class UninitializedEnumEntry : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||
override val diagnosticClass get() = UninitializedEnumEntry::class
|
||||
abstract val enumEntry: KtVariableLikeSymbol
|
||||
|
||||
+8
@@ -1972,6 +1972,14 @@ internal class UninitializedVariableImpl(
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class UninitializedParameterImpl(
|
||||
override val parameter: KtVariableLikeSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.UninitializedParameter(), KtAbstractFirDiagnostic<KtSimpleNameExpression> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class UninitializedEnumEntryImpl(
|
||||
override val enumEntry: KtVariableLikeSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
|
||||
Reference in New Issue
Block a user