[FIR] Add diagnostic for constructor delegation cycles

This commit is contained in:
Nick
2020-07-31 16:48:33 +03:00
committed by Mikhail Glukhikh
parent e841b3a77b
commit bb0e1b7390
24 changed files with 322 additions and 58 deletions
@@ -5,7 +5,7 @@ FILE: annotationClassMember.kt
}
public constructor(s: R|kotlin/Nothing?|): R|A| {
this<R|A|>()
super<R|kotlin/Any|>()
}
init {
@@ -0,0 +1,67 @@
class B
class C
class A() {
constructor(a: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("test") {}
constructor(a: String) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(10) {}
constructor(a: Boolean) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>('\n') {}
constructor(a: Char) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(0.0) {}
constructor(a: Double) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(false) {}
constructor(b: B) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(3.14159265) {}
constructor(c: C) : this() {}
constructor(a: List<Int>) : this(C()) {}
}
class D {
constructor(i: Boolean) {}
constructor(i: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(3) {}
}
class E<T> {
// this is not an error about the
// selection of the proper constructor
// but a type mismatch for the first
// argument
constructor(e: T, i: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(i, 10) {}
}
class I<T> {
// this is not an error about the
// selection of the proper constructor
// but a type mismatch for the first
// argument
constructor(e: T, i: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(i, 10)
}
class J<T> {
constructor(e: T, i: Int) : this(i, 10)
constructor(e: Int, i: Int)
}
class F(s: String) {
constructor(i: Boolean) {}
constructor(i: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(3) {}
}
class G(x: Int) {
constructor() {}
}
class H(x: Int) {
constructor()
}
class K(x: Int) {
constructor() : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>() {}
}
class M {
constructor(m: Int)
}
class U : M {
<!INAPPLICABLE_CANDIDATE!>constructor()<!>
}
@@ -0,0 +1,139 @@
FILE: cyclicConstructorDelegationCall.kt
public final class B : R|kotlin/Any| {
public constructor(): R|B| {
super<R|kotlin/Any|>()
}
}
public final class C : R|kotlin/Any| {
public constructor(): R|C| {
super<R|kotlin/Any|>()
}
}
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public constructor(a: R|kotlin/Int|): R|A| {
this<R|A|>(String(test))
}
public constructor(a: R|kotlin/String|): R|A| {
this<R|A|>(Int(10))
}
public constructor(a: R|kotlin/Boolean|): R|A| {
this<R|A|>(Char(10))
}
public constructor(a: R|kotlin/Char|): R|A| {
this<R|A|>(Double(0.0))
}
public constructor(a: R|kotlin/Double|): R|A| {
this<R|A|>(Boolean(false))
}
public constructor(b: R|B|): R|A| {
this<R|A|>(Double(3.14159265))
}
public constructor(c: R|C|): R|A| {
this<R|A|>()
}
public constructor(a: R|kotlin/collections/List<kotlin/Int>|): R|A| {
this<R|A|>(R|/C.C|())
}
}
public final class D : R|kotlin/Any| {
public constructor(i: R|kotlin/Boolean|): R|D| {
super<R|kotlin/Any|>()
}
public constructor(i: R|kotlin/Int|): R|D| {
this<R|D|>(Int(3))
}
}
public final class E<T> : R|kotlin/Any| {
public constructor<T>(e: R|T|, i: R|kotlin/Int|): R|E<T>| {
this<R|E<T>|>(R|<local>/i|, Int(10))
}
}
public final class I<T> : R|kotlin/Any| {
public constructor<T>(e: R|T|, i: R|kotlin/Int|): R|I<T>| {
this<R|I<T>|>(R|<local>/i|, Int(10))
}
}
public final class J<T> : R|kotlin/Any| {
public constructor<T>(e: R|T|, i: R|kotlin/Int|): R|J<T>| {
this<R|J<T>|>(R|<local>/i|, Int(10))
}
public constructor<T>(e: R|kotlin/Int|, i: R|kotlin/Int|): R|J<T>| {
super<R|kotlin/Any|>()
}
}
public final class F : R|kotlin/Any| {
public constructor(s: R|kotlin/String|): R|F| {
super<R|kotlin/Any|>()
}
public constructor(i: R|kotlin/Boolean|): R|F| {
super<R|kotlin/Any|>()
}
public constructor(i: R|kotlin/Int|): R|F| {
this<R|F|>(Int(3))
}
}
public final class G : R|kotlin/Any| {
public constructor(x: R|kotlin/Int|): R|G| {
super<R|kotlin/Any|>()
}
public constructor(): R|G| {
super<R|kotlin/Any|>()
}
}
public final class H : R|kotlin/Any| {
public constructor(x: R|kotlin/Int|): R|H| {
super<R|kotlin/Any|>()
}
public constructor(): R|H| {
super<R|kotlin/Any|>()
}
}
public final class K : R|kotlin/Any| {
public constructor(x: R|kotlin/Int|): R|K| {
super<R|kotlin/Any|>()
}
public constructor(): R|K| {
this<R|K|>()
}
}
public final class M : R|kotlin/Any| {
public constructor(m: R|kotlin/Int|): R|M| {
super<R|kotlin/Any|>()
}
}
public final class U : R|M| {
public constructor(): R|U| {
super<R|M|>()
}
}
@@ -916,6 +916,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/anonymousObjectByDelegate.kt");
}
@TestMetadata("cyclicConstructorDelegationCall.kt")
public void testCyclicConstructorDelegationCall() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/cyclicConstructorDelegationCall.kt");
}
@TestMetadata("incompatibleModifiers.kt")
public void testIncompatibleModifiers() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/incompatibleModifiers.kt");
@@ -916,6 +916,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/anonymousObjectByDelegate.kt");
}
@TestMetadata("cyclicConstructorDelegationCall.kt")
public void testCyclicConstructorDelegationCall() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/cyclicConstructorDelegationCall.kt");
}
@TestMetadata("incompatibleModifiers.kt")
public void testIncompatibleModifiers() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/incompatibleModifiers.kt");
@@ -20,6 +20,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
override val memberDeclarationCheckers: List<FirMemberDeclarationChecker> = listOf(
FirInfixFunctionDeclarationChecker,
FirExposedVisibilityDeclarationChecker,
FirCyclicConstructorDelegationCallChecker,
)
override val constructorCheckers: List<FirConstructorChecker> = listOf(
@@ -0,0 +1,66 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirCyclicConstructorDelegationCallChecker : FirMemberDeclarationChecker() {
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration !is FirRegularClass) {
return
}
val cyclicConstructors = mutableSetOf<FirConstructor>()
for (it in declaration.declarations) {
if (it is FirConstructor && !it.isPrimary) {
it.findCycle(cyclicConstructors)?.let { visited ->
cyclicConstructors += visited
}
}
}
cyclicConstructors.forEach {
reporter.reportCyclicConstructorDelegationCall(it.delegatedConstructor?.source)
}
}
private fun FirConstructor.findCycle(knownCyclicConstructors: Set<FirConstructor> = emptySet()): Set<FirConstructor>? {
val visitedConstructors = mutableSetOf(this)
var it = this
var delegated = this.getDelegated()
while (!it.isPrimary && delegated != null) {
if (delegated in visitedConstructors || delegated in knownCyclicConstructors) {
return visitedConstructors
}
it = delegated
delegated = delegated.getDelegated()
visitedConstructors.add(it)
}
return null
}
private fun FirConstructor.getDelegated(): FirConstructor? = delegatedConstructor
?.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol
?.fir.safeAs()
private fun DiagnosticReporter.reportCyclicConstructorDelegationCall(source: FirSourceElement?) {
source?.let { report(FirErrors.CYCLIC_CONSTRUCTOR_DELEGATION_CALL.on(it)) }
}
}
@@ -64,6 +64,8 @@ object FirErrors {
val VAR_ANNOTATION_PARAMETER by existing<FirSourceElement, KtParameter>(Errors.VAR_ANNOTATION_PARAMETER)
val NOT_AN_ANNOTATION_CLASS by error1<FirSourceElement, PsiElement, String>()
val CYCLIC_CONSTRUCTOR_DELEGATION_CALL by warning0<FirSourceElement, PsiElement>()
// Exposed visibility group
val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3<FirSourceElement, PsiElement, FirEffectiveVisibility, FirMemberDeclaration, FirEffectiveVisibility>()
val EXPOSED_FUNCTION_RETURN_TYPE by error3<FirSourceElement, PsiElement, FirEffectiveVisibility, FirMemberDeclaration, FirEffectiveVisibility>()
@@ -834,7 +834,7 @@ class DeclarationsConverter(
}
val isImplicit = constructorDelegationCall.asText.isEmpty()
val isThis = (isImplicit && classWrapper.hasPrimaryConstructor) || thisKeywordPresent
val isThis = thisKeywordPresent //|| (isImplicit && classWrapper.hasPrimaryConstructor)
val delegatedType =
when {
isThis -> classWrapper.delegatedSelfTypeRef
@@ -1095,7 +1095,7 @@ class RawFirBuilder(
delegatedSelfTypeRef: FirTypeRef,
hasPrimaryConstructor: Boolean,
): FirDelegatedConstructorCall {
val isThis = isCallToThis || (isImplicit && hasPrimaryConstructor)
val isThis = isCallToThis //|| (isImplicit && hasPrimaryConstructor)
val source = this.toFirSourceElement()
val delegatedType = when {
isThis -> delegatedSelfTypeRef
@@ -7,7 +7,7 @@ class Foo {
bar = ""
}
constructor(a: Int) : this(a) {
constructor(a: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(a) {
bar = "a"
}
}
@@ -7,12 +7,12 @@ class Foo {
bar = ""
}
constructor(a: Int) : this(a, 0, 0) {
constructor(a: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(a, 0, 0) {
}
constructor(a: Int, b: Int) : this(a) {
constructor(a: Int, b: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(a) {
}
constructor(a: Int, b: Int, c: Int) : this(a, b) {
constructor(a: Int, b: Int, c: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(a, b) {
}
}
@@ -7,7 +7,7 @@ expect enum class En(x: Int) {
E2(42),
;
<!NONE_APPLICABLE!>constructor(s: String)<!>
constructor(s: String)
}
expect enum class En2 {
@@ -4,7 +4,7 @@
// FILE: common.kt
expect class Foo(zzz: Int) {
<!NONE_APPLICABLE!>constructor(aaa: Boolean)<!>
constructor(aaa: Boolean)
fun f1(xxx: String): String
}
@@ -1,20 +0,0 @@
object A {
<!CONSTRUCTOR_IN_OBJECT!>constructor()<!>
init {}
}
enum class B {
X() {
<!CONSTRUCTOR_IN_OBJECT, NONE_APPLICABLE!>constructor()<!>
}
}
class C {
companion object {
<!CONSTRUCTOR_IN_OBJECT!>constructor()<!>
}
}
val anonObject = object {
<!CONSTRUCTOR_IN_OBJECT!>constructor()<!>
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
object A {
<!CONSTRUCTOR_IN_OBJECT!>constructor()<!>
init {}
@@ -1,33 +1,33 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class A1 {
constructor(): this()
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>()
}
class A2(x: Byte) {
constructor(x1: Int): this(x1, 1)
constructor(x1: Int, x2: Int): this(x1, x2, 2)
constructor(x1: Int, x2: Int, x3: Int): this(x1)
constructor(x1: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, 1)
constructor(x1: Int, x2: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, x2, 2)
constructor(x1: Int, x2: Int, x3: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1)
// delegating to previously declared cycle
constructor(x1: Double): this(1)
constructor(x1: Double): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(1)
// delegating to cycle declared after
constructor(x1: String): this(1L)
constructor(x1: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(1L)
constructor(x1: Long): this(x1, 1L)
constructor(x1: Long, x2: Long): this(x1, x2, 2L)
constructor(x1: Long, x2: Long, x3: Long): this(x1)
constructor(x1: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, 1L)
constructor(x1: Long, x2: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, x2, 2L)
constructor(x1: Long, x2: Long, x3: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1)
// no cycle, just call to primary constuctor
constructor(x1: Double, x2: Double): this(x1, x2, 1.0)
constructor(x1: Double, x2: Double, x3: Double): this(x1, x2, x3, 1.0)
constructor(x1: Double, x2: Double, x3: Double, x4: Double): this(1.toByte())
constructor(): this("x", "y")
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("x", "y")
constructor(x1: String, x2: String): this(x1, x2, "")
constructor(x1: String, x2: String, x3: String): this(x1, x2)
constructor(x1: String, x2: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, x2, "")
constructor(x1: String, x2: String, x3: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x1, x2)
}
open class B(x: Byte)
@@ -17,5 +17,5 @@ class A1<R> : B<R> {
}
class A2<R> {
constructor(t: R, i: Int) : this(i, 1)
constructor(t: R, i: Int) : <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(i, 1)
}
@@ -3,13 +3,13 @@
open class B<R1, R2>(x: R1, y: R2)
class A0<T1, T2> {
constructor(x: T1, y: T2): this(x, y)
constructor(x: T1, y: T2): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, y)
constructor(x: T1, y: T2, z: T2): this(x, 1) // ok, delegates to constructor(x: T1, y: Int)
constructor(x: T1, y: T2, z: T2): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, 1) // ok, delegates to constructor(x: T1, y: Int)
constructor(x: T1, y: Int): this(x, "")
constructor(x: T1): this(x, 1)
constructor(x: T1, y: T2, z: String): this(y, x)
constructor(x: T1, y: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, "")
constructor(x: T1): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, 1)
constructor(x: T1, y: T2, z: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(y, x)
}
class A1<T1, T2> : B<T1, T2> {
@@ -2,6 +2,6 @@
open class A(p1: String)
class B() : A("") {
constructor(s: String) {
<!INAPPLICABLE_CANDIDATE!>constructor(s: String)<!> {
}
}
@@ -1,3 +0,0 @@
class X<T>(val t: T) {
constructor(t: String): this(t)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class X<T>(val t: T) {
constructor(t: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(t)
}
@@ -1,4 +1,4 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class X<T> {
constructor(t: T, i: Int): this(i, 1)
constructor(t: T, i: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(i, 1)
}
@@ -1,9 +1,9 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class A(x: String = "", y: String = "") {
constructor(x: String, y: String): this(x, y)
constructor(): this("", "")
constructor(): this("", "")
constructor(x: String, y: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, y)
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("", "")
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("", "")
}
class B {
@@ -14,9 +14,9 @@ fun B(x: Int) {}
class Outer {
class A(x: String = "", y: String = "") {
constructor(x: String, y: String): this(x, y)
constructor(): this("", "")
constructor(): this("", "")
constructor(x: String, y: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>(x, y)
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("", "")
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this<!>("", "")
}
class B {