[FIR] Report VAL_REASSIGNMENT on assign to non-local vals

In this commit reporting on member properties in init section of class
  is not supported (see KT-55528)

^KT-55493 Fixed
This commit is contained in:
Dmitriy Novozhilov
2022-12-16 17:05:32 +02:00
committed by Space Team
parent e87a064cdd
commit 02e327277e
29 changed files with 321 additions and 49 deletions
@@ -1086,6 +1086,18 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
public void testInitializationInTry() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.kt");
}
@Test
@TestMetadata("reassignOfNonLocalProperty_initializedProperties.kt")
public void testReassignOfNonLocalProperty_initializedProperties() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonLocalProperty_initializedProperties.kt");
}
@Test
@TestMetadata("reassignOfNonMemberProperty_lateInitialization.kt")
public void testReassignOfNonMemberProperty_lateInitialization() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonMemberProperty_lateInitialization.kt");
}
}
@Nested
@@ -940,6 +940,16 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
public void testInitializationInTry() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.kt");
}
@TestMetadata("reassignOfNonLocalProperty_initializedProperties.kt")
public void testReassignOfNonLocalProperty_initializedProperties() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonLocalProperty_initializedProperties.kt");
}
@TestMetadata("reassignOfNonMemberProperty_lateInitialization.kt")
public void testReassignOfNonMemberProperty_lateInitialization() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonMemberProperty_lateInitialization.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/cfg")
@@ -0,0 +1,47 @@
FILE: reassignOfNonLocalProperty_initializedProperties.kt
public final val z: R|kotlin/String| = String(ok)
public get(): R|kotlin/String|
public final val R|Some|.y: R|kotlin/String|
public get(): R|kotlin/String| {
^ String(ok)
}
public final class Some : R|kotlin/Any| {
public constructor(): R|Some| {
super<R|kotlin/Any|>()
}
public final val x: R|kotlin/String| = String(ok)
public get(): R|kotlin/String|
init {
this@R|/Some|.R|/Some.x| = String(error)
this@R|/Some|.R|/y| = String(error)
R|/z| = String(error)
}
public final val a: R|kotlin/String| = this@R|/Some|.R|kotlin/run|<R|Some|, R|kotlin/String|>(<L> = run@fun R|Some|.<anonymous>(): R|kotlin/String| <inline=Inline, kind=EXACTLY_ONCE> {
this@R|special/anonymous|.R|/Some.x| = String(error)
this@R|special/anonymous|.R|/y| = String(error)
R|/z| = String(error)
^ String(hello)
}
)
public get(): R|kotlin/String|
public final fun test_1(): R|kotlin/Unit| {
this@R|/Some|.R|/Some.x| = String(error)
this@R|/Some|.R|/y| = String(error)
R|/z| = String(error)
}
}
public final fun R|Some|.test_2(): R|kotlin/Unit| {
this@R|/test_2|.R|/Some.x| = String(error)
this@R|/test_2|.R|/y| = String(error)
R|/z| = String(error)
}
public final fun test_3(some: R|Some|): R|kotlin/Unit| {
R|<local>/some|.R|/Some.x| = String(error)
R|<local>/some|.R|/y| = String(error)
R|/z| = String(error)
}
@@ -0,0 +1,42 @@
// ISSUE: KT-55493
// WITH_STDLIB
val z: String = "ok"
val Some.y: String
get() = "ok"
class Some {
val x: String = "ok"
init {
<!VAL_REASSIGNMENT!>x<!> = "error"
<!VAL_REASSIGNMENT!>y<!> = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
val a: String = run {
<!VAL_REASSIGNMENT!>x<!> = "error"
<!VAL_REASSIGNMENT!>y<!> = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
"hello"
}
fun test_1() {
<!VAL_REASSIGNMENT!>x<!> = "error"
<!VAL_REASSIGNMENT!>y<!> = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
}
fun Some.test_2() {
<!VAL_REASSIGNMENT!>x<!> = "error"
<!VAL_REASSIGNMENT!>y<!> = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
fun test_3(some: Some) {
some.<!VAL_REASSIGNMENT!>x<!> = "error"
some.<!VAL_REASSIGNMENT!>y<!> = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
@@ -0,0 +1,39 @@
FILE: reassignOfNonMemberProperty_lateInitialization.kt
public final val R|Some|.z: R|kotlin/String|
public get(): R|kotlin/String| {
^ String(ok)
}
public final class Some : R|kotlin/Any| {
public constructor(): R|Some| {
super<R|kotlin/Any|>()
}
public final val x: R|kotlin/String|
public get(): R|kotlin/String|
public final val y: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/Some|.R|/Some.x| = String(ok)
this@R|/Some|.R|/Some.x| = String(error)
this@R|/Some|.R|/z| = String(error)
}
public final val a: R|kotlin/String| = this@R|/Some|.R|kotlin/run|<R|Some|, R|kotlin/String|>(<L> = run@fun R|Some|.<anonymous>(): R|kotlin/String| <inline=Inline, kind=EXACTLY_ONCE> {
this@R|special/anonymous|.R|/Some.x| = String(error)
this@R|special/anonymous|.R|/Some.y| = String(ok)
this@R|special/anonymous|.R|/Some.y| = String(error)
this@R|special/anonymous|.R|/z| = String(error)
^ String(hello)
}
)
public get(): R|kotlin/String|
init {
this@R|/Some|.R|/Some.x| = String(error)
this@R|/Some|.R|/Some.y| = String(error)
this@R|/Some|.R|/z| = String(error)
}
}
@@ -0,0 +1,31 @@
// ISSUE: KT-55493
// WITH_STDLIB
val Some.z: String
get() = "ok"
class Some {
val x: String
val y: String
init {
x = "ok"
x = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
val a: String = run {
x = "error"
y = "ok"
y = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
"hello"
}
init {
x = "error"
y = "error"
<!VAL_REASSIGNMENT!>z<!> = "error"
}
}
@@ -35,7 +35,7 @@ fun main(d1: D1, d2: D2, d3: D3) {
d2.bar(1, "").checkType { _<String>() }
d3.x.checkType { _<String>() }
d3.x = ""
d3.<!VAL_REASSIGNMENT!>x<!> = ""
d3.foo().checkType { _<String?>() }
d3.bar(1, "").checkType { _<String?>() }
}
@@ -7,7 +7,7 @@ class B(p0: String) {
p3 = p1
}
init {
p1 = <!ASSIGNMENT_TYPE_MISMATCH!>p0.length<!>
<!VAL_REASSIGNMENT!>p1<!> = <!ASSIGNMENT_TYPE_MISMATCH!>p0.length<!>
p3 = ""
}
}
@@ -1086,6 +1086,18 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
public void testInitializationInTry() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.kt");
}
@Test
@TestMetadata("reassignOfNonLocalProperty_initializedProperties.kt")
public void testReassignOfNonLocalProperty_initializedProperties() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonLocalProperty_initializedProperties.kt");
}
@Test
@TestMetadata("reassignOfNonMemberProperty_lateInitialization.kt")
public void testReassignOfNonMemberProperty_lateInitialization() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonMemberProperty_lateInitialization.kt");
}
}
@Nested
@@ -1086,6 +1086,18 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
public void testInitializationInTry() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.kt");
}
@Test
@TestMetadata("reassignOfNonLocalProperty_initializedProperties.kt")
public void testReassignOfNonLocalProperty_initializedProperties() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonLocalProperty_initializedProperties.kt");
}
@Test
@TestMetadata("reassignOfNonMemberProperty_lateInitialization.kt")
public void testReassignOfNonMemberProperty_lateInitialization() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/cfa/reassignOfNonMemberProperty_lateInitialization.kt");
}
}
@Nested
@@ -73,7 +73,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
} else {
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION, symbol, context)
}
} else if (symbol is FirSyntheticPropertySymbol || data.getValue(node).values.any { it[symbol]?.canBeRevisited() == true }) {
} else if (data.getValue(node).values.any { it[symbol]?.canBeRevisited() == true }) {
reporter.reportOn(node.fir.lValue.source, FirErrors.VAL_REASSIGNMENT, symbol, context)
}
}
@@ -57,10 +57,6 @@ class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlow
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: return
if (symbol is FirSyntheticPropertySymbol) {
symbols[symbol] = true
return
}
// Check if this variable assignment is inside a lambda or a local function.
if (lambdaOrLocalFunctionStack.isEmpty()) return
@@ -12,7 +12,12 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
@@ -20,10 +25,15 @@ import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
import org.jetbrains.kotlin.fir.references.FirBackingFieldReference
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
import org.jetbrains.kotlin.fir.references.toResolvedValueParameterSymbol
import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
import org.jetbrains.kotlin.fir.types.toSymbol
import org.jetbrains.kotlin.fir.visibilityChecker
object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker() {
@@ -32,6 +42,7 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
checkValReassignmentViaBackingField(expression, context, reporter)
checkValReassignmentOnValueParameter(expression, context, reporter)
checkAssignmentToThis(expression, context, reporter)
checkValReassignmentOfNonLocalProperty(expression, context, reporter)
}
private fun checkInvisibleSetter(
@@ -114,4 +125,41 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
reporter.reportOn(expression.lValue.source, FirErrors.VARIABLE_EXPECTED, context)
}
}
private fun checkValReassignmentOfNonLocalProperty(
expression: FirVariableAssignment,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val property = expression.lValue.toResolvedPropertySymbol() ?: return
if (property.isLocal || property.isVar) return
val assignIsIllegal = when (val dispatchReceiverType = property.dispatchReceiverType) {
null -> true
else -> when {
property is FirSyntheticPropertySymbol -> true
property.hasDelegate || !property.hasBackingField -> true
property.hasInitializer -> true
else -> !isInsideInitializationOfClass(dispatchReceiverType, context)
}
}
if (assignIsIllegal) {
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
}
}
private fun isInsideInitializationOfClass(classType: ConeSimpleKotlinType, context: CheckerContext): Boolean {
val session = context.session
val classSymbol = classType.toSymbol(session) ?: return false
for (containingDeclaration in context.containingDeclarations) {
val containingClassSymbol = when (containingDeclaration) {
is FirProperty -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
is FirAnonymousInitializer -> containingDeclaration.dispatchReceiverType?.toSymbol(session)
is FirConstructor -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
else -> null
} ?: continue
if (containingClassSymbol == classSymbol) return true
}
return false
}
}
@@ -1,4 +1,6 @@
// IGNORE_BACKEND: JS, NATIVE
// IGNORE_BACKEND_K2: ANY
// FIR_STATUS: KT-35565
// java.lang.AssertionError
// at org.jetbrains.kotlin.js.translate.context.TranslationContext.getDispatchReceiver(TranslationContext.java:590)
// at org.jetbrains.kotlin.js.translate.utils.TranslationUtils.backingFieldReference(TranslationUtils.java:237)
@@ -1,4 +1,6 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND_K2: ANY
// FIR_STATUS: KT-35565
// This test should become irrelevant after KT-35565 is fixed.
@@ -11,14 +11,14 @@ class C() : B() {
this.x = 34
this.b = 123
super.b = 23
this.c = 34
super.c = 3535 //repeat for 'c'
this.<!VAL_REASSIGNMENT!>c<!> = 34
super.<!VAL_REASSIGNMENT!>c<!> = 3535 //repeat for 'c'
<!VARIABLE_EXPECTED!>getInt()<!> = 12
}
fun foo1(c: C) {
super.c = 34
super.<!VAL_REASSIGNMENT!>c<!> = 34
}
fun bar(c: C) {
@@ -116,8 +116,8 @@ class AnonymousInitializers(var a: String, val b: String) {
a = "30"
a = "s"
b = "3"
b = "tt" //repeat for b
<!VAL_REASSIGNMENT!>b<!> = "3"
<!VAL_REASSIGNMENT!>b<!> = "tt" //repeat for b
}
val i: Int
@@ -127,7 +127,7 @@ class AnonymousInitializers(var a: String, val b: String) {
init {
x = 11
z = 10
<!VAL_REASSIGNMENT!>z<!> = 10
}
val j: Int
@@ -135,7 +135,7 @@ class AnonymousInitializers(var a: String, val b: String) {
init {
i = 13
j = 34
<!VAL_REASSIGNMENT!>j<!> = 34
}
val k: String
@@ -228,13 +228,13 @@ class Outer() {
inner class Inner() {
init {
a++
<!VAL_REASSIGNMENT!>a<!>++
b++
}
}
fun foo() {
a++
<!VAL_REASSIGNMENT!>a<!>++
b++
}
}
@@ -271,8 +271,8 @@ fun foo() {
z = 3
}
fun foo() {
y = 10
z = 13
<!VAL_REASSIGNMENT!>y<!> = 10
<!VAL_REASSIGNMENT!>z<!> = 13
}
}
}
@@ -290,12 +290,12 @@ class TestObjectExpression() {
x = 1
}
fun inner1() {
y = 101
a = 231
<!VAL_REASSIGNMENT!>y<!> = 101
<!VAL_REASSIGNMENT!>a<!> = 231
}
fun inner2() {
y = 101
a = 231
<!VAL_REASSIGNMENT!>y<!> = 101
<!VAL_REASSIGNMENT!>a<!> = 231
}
}
}
@@ -311,7 +311,7 @@ object TestObjectDeclaration {
}
fun foo() {
y = 10
<!VAL_REASSIGNMENT!>y<!> = 10
val i: Int
if (1 < 3) {
i = 10
@@ -338,11 +338,11 @@ class M() {
}
fun test(m : M) {
m.x = 23
m.<!VAL_REASSIGNMENT!>x<!> = 23
m.y = 23
}
fun test1(m : M) {
m.x++
m.<!VAL_REASSIGNMENT!>x<!>++
m.y--
}
@@ -86,5 +86,5 @@ class My {
<!MUST_BE_INITIALIZED!>val top: Int<!>
fun init() {
top = 1
<!VAL_REASSIGNMENT!>top<!> = 1
}
@@ -8,8 +8,8 @@ fun foo(a: A) {
get() = 42
}
a.z = 23
o.y = 11 //Should be an error here
a.<!VAL_REASSIGNMENT!>z<!> = 23
o.<!VAL_REASSIGNMENT!>y<!> = 11 //Should be an error here
}
class A() {
@@ -0,0 +1,20 @@
//KT-897 Don't allow assignment to a property before it is defined
package kt897
class A() {
init {
<!INITIALIZATION_BEFORE_DECLARATION, VAL_REASSIGNMENT!>i<!> = 11
}
val i : Int? = null // must be an error
init {
<!INITIALIZATION_BEFORE_DECLARATION!>j<!> = 1
}
var j : Int = 2
init {
<!INITIALIZATION_BEFORE_DECLARATION!>k<!> = 3
}
val k : Int
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
//KT-897 Don't allow assignment to a property before it is defined
package kt897
@@ -6,9 +6,9 @@ class Outer {
outerProp // use of outerProp is ok because we're suppose that Outer instance should be initialized
this@Outer.outerProp
this@Outer.outerProp = "1"
outerProp = "2" // do not repeat the same diagnostic with this receiver of outer class
outer.outerProp = "3"
this@Outer.<!VAL_REASSIGNMENT!>outerProp<!> = "1"
<!VAL_REASSIGNMENT!>outerProp<!> = "2" // do not repeat the same diagnostic with this receiver of outer class
outer.<!VAL_REASSIGNMENT!>outerProp<!> = "3"
innerProp = "4" + inner.innerProp
this@Inner.innerProp = "5"
@@ -7,7 +7,7 @@ open class M() {
class N() : M() {
val a : Int
get() {
super.b = super.b + 1
super.<!VAL_REASSIGNMENT!>b<!> = super.b + 1
return super.b + 1
}
override val b: Int = a + 1
@@ -63,6 +63,6 @@ class InitializationForbiddenInNonInitSection {
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val x: Int<!>
fun setup() {
myRun { x = 42 }
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
}
}
@@ -26,7 +26,7 @@ class Case1() {
fun case2() {
var b= Case2()
--b.a
--b.<!VAL_REASSIGNMENT!>a<!>
}
class Case2() {
@@ -26,7 +26,7 @@ class Case1() {
fun case2() {
var b= Case2()
++b.a
++b.<!VAL_REASSIGNMENT!>a<!>
}
class Case2() {
@@ -7,8 +7,8 @@
*/
fun case1() {
val x : Case1? = Case1()
x.x = "0"
x?.x = "0"
x.<!VAL_REASSIGNMENT!>x<!> = "0"
x?.<!VAL_REASSIGNMENT!>x<!> = "0"
<!VARIABLE_EXPECTED!>x::x<!> = TODO()
}
@@ -23,8 +23,8 @@ class Case1{
*/
fun case2() {
val x : Case2? = Case2(null)
x.x = "0"
x?.x = "0"
x.<!VAL_REASSIGNMENT!>x<!> = "0"
x?.<!VAL_REASSIGNMENT!>x<!> = "0"
<!VARIABLE_EXPECTED!>x::x<!> = TODO()
}
@@ -36,8 +36,8 @@ class Case2(val x: Any?) {}
*/
fun case3() {
val x : Case3? = Case3()
x.x = "0"
x?.x = "0"
x.<!VAL_REASSIGNMENT!>x<!> = "0"
x?.<!VAL_REASSIGNMENT!>x<!> = "0"
<!VARIABLE_EXPECTED!>x::x<!> = TODO()
}
@@ -32,5 +32,5 @@ fun `should not report an error for assign return type for unannotated class`()
data class IntTask(val input: IntProperty)
val task = IntTask(IntProperty(42))
task.input = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
task.<!VAL_REASSIGNMENT!>input<!> = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
}
@@ -14,16 +14,16 @@ data class Task(val input: StringProperty)
fun `should not work with assignment when there is no annotation on a type`() {
val task = Task(StringProperty("Fail"))
task.input = <!ASSIGNMENT_TYPE_MISMATCH!>"OK"<!>
task.input = StringProperty("OK")
task.<!VAL_REASSIGNMENT!>input<!> = <!ASSIGNMENT_TYPE_MISMATCH!>"OK"<!>
task.<!VAL_REASSIGNMENT!>input<!> = StringProperty("OK")
task.apply {
input = <!ASSIGNMENT_TYPE_MISMATCH!>"OK"<!>
<!VAL_REASSIGNMENT!>input<!> = <!ASSIGNMENT_TYPE_MISMATCH!>"OK"<!>
}
task.apply {
input = StringProperty("OK")
<!VAL_REASSIGNMENT!>input<!> = StringProperty("OK")
}
task.input = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
task.<!VAL_REASSIGNMENT!>input<!> = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
task.apply {
input = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
<!VAL_REASSIGNMENT!>input<!> = <!ASSIGNMENT_TYPE_MISMATCH!>42<!>
}
}