FIR checker: report UNINITIALIZED_PARAMETER

This commit is contained in:
Jinseong Jeon
2021-04-22 23:48:15 -07:00
committed by TeamCityServer
parent d2f831b635
commit bdfc879f00
21 changed files with 146 additions and 39 deletions
@@ -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<*>>()
@@ -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
)
}
}
})
}
}
}
@@ -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)
@@ -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,
@@ -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)
@@ -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
View File
@@ -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
@@ -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) {}
}
@@ -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 = `_`) { }
}
}
@@ -23,7 +23,7 @@ fun foo() {
10
}
fun bar(x: Exception = `_`) {}
class Bar(`_`: Exception = `_`) {
class Bar(`_`: Exception = <!UNINITIALIZED_PARAMETER!>`_`<!>) {
inner class Bar2(x: Exception = `_`) { }
}
}
@@ -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),
@@ -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
@@ -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<*>,