From b76f757d4731f52dc94233e3c5256f887f75f1cc Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Aug 2020 15:07:54 +0300 Subject: [PATCH] [FIR] Add diagnostic for missing primary constructor --- ...ypeInitializedWithoutPrimaryConstructor.kt | 11 +++ ...peInitializedWithoutPrimaryConstructor.txt | 43 +++++++++++ .../fir/FirDiagnosticsTestGenerated.java | 5 ++ ...DiagnosticsWithLightTreeTestGenerated.java | 5 ++ .../declaration/DefaultDeclarationCheckers.kt | 1 + ...ypeInitializedWithoutPrimaryConstructor.kt | 74 +++++++++++++++++++ .../fir/analysis/diagnostics/FirErrors.kt | 1 + .../converter/DeclarationsConverter.kt | 7 +- .../headerSupertypeInitialization.fir.kt | 4 +- 9 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt create mode 100644 compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.txt create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirSupertypeInitializedWithoutPrimaryConstructor.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt new file mode 100644 index 00000000000..30d66077a9b --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt @@ -0,0 +1,11 @@ +class A +class B : A + +class C(x: Int) +class D : C +class E : C(10) +class F() : C(10) + +class G : C(10) { + constructor() : super(1) +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.txt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.txt new file mode 100644 index 00000000000..9792e605f87 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.txt @@ -0,0 +1,43 @@ +FILE: supertypeInitializedWithoutPrimaryConstructor.kt + public final class A : R|kotlin/Any| { + public constructor(): R|A| { + super() + } + + } + public final class B : R|A| { + public constructor(): R|B| { + super() + } + + } + public final class C : R|kotlin/Any| { + public constructor(x: R|kotlin/Int|): R|C| { + super() + } + + } + public final class D : R|C| { + public constructor(): R|D| { + super() + } + + } + public final class E : R|C| { + public constructor(): R|E| { + super(Int(10)) + } + + } + public final class F : R|C| { + public constructor(): R|F| { + super(Int(10)) + } + + } + public final class G : R|C| { + public constructor(): R|G| { + super(Int(1)) + } + + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index 03e65b19e72..f36dc679ef8 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -986,6 +986,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt"); } + @TestMetadata("supertypeInitializedWithoutPrimaryConstructor.kt") + public void testSupertypeInitializedWithoutPrimaryConstructor() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt"); + } + @TestMetadata("typeArgumentsNotAllowed.kt") public void testTypeArgumentsNotAllowed() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeArgumentsNotAllowed.kt"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index 54655d015e0..3fccb9fb92f 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -986,6 +986,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt"); } + @TestMetadata("supertypeInitializedWithoutPrimaryConstructor.kt") + public void testSupertypeInitializedWithoutPrimaryConstructor() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/supertypeInitializedWithoutPrimaryConstructor.kt"); + } + @TestMetadata("typeArgumentsNotAllowed.kt") public void testTypeArgumentsNotAllowed() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeArgumentsNotAllowed.kt"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt index b550a6a9e2e..28b43c8772e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt @@ -21,6 +21,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { FirInfixFunctionDeclarationChecker, FirExposedVisibilityDeclarationChecker, FirCyclicConstructorDelegationCallChecker, + FirSupertypeInitializedWithoutPrimaryConstructor, ) override val constructorCheckers: List = listOf( diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirSupertypeInitializedWithoutPrimaryConstructor.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirSupertypeInitializedWithoutPrimaryConstructor.kt new file mode 100644 index 00000000000..5dd90601109 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirSupertypeInitializedWithoutPrimaryConstructor.kt @@ -0,0 +1,74 @@ +/* + * 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 com.intellij.lang.LighterASTNode +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiErrorElement +import com.intellij.util.diff.FlyweightCapableTreeStructure +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.fir.FirLightSourceElement +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.lightNode +import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry + +object FirSupertypeInitializedWithoutPrimaryConstructor : FirMemberDeclarationChecker() { + override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + if (declaration !is FirRegularClass || declaration.classKind == ClassKind.INTERFACE) { + return + } + + val hasSupertypeWithConstructor = declaration.source?.anySupertypeHasConstructorParentheses() == true + val hasPrimaryConstructor = declaration.declarations.any { it is FirConstructor && it.isPrimary } + + if (hasSupertypeWithConstructor && !hasPrimaryConstructor) { + reporter.report(declaration.source) + } + } + + private fun FirSourceElement.anySupertypeHasConstructorParentheses(): Boolean { + val localPsi = psi + val localLightNode = lightNode + + if (localPsi != null && localPsi !is PsiErrorElement) { + return localPsi.anySupertypeHasConstructorParentheses() + } else if (localLightNode != null && this is FirLightSourceElement) { + return localLightNode.anySupertypeHasConstructorParentheses(tree) + } + + return false + } + + private fun PsiElement.anySupertypeHasConstructorParentheses(): Boolean { + return children.isNotEmpty() && children[0] !is PsiErrorElement && children[0].children.any { it is KtSuperTypeCallEntry } + } + + private fun LighterASTNode.anySupertypeHasConstructorParentheses(tree: FlyweightCapableTreeStructure): Boolean { + val superTypes = getChildren(tree).find { it.tokenType == KtNodeTypes.SUPER_TYPE_LIST } + ?: return false + + return superTypes.getChildren(tree).any { it.tokenType == KtNodeTypes.SUPER_TYPE_CALL_ENTRY } + } + + private fun LighterASTNode.getChildren(tree: FlyweightCapableTreeStructure): List { + val children = Ref>() + val count = tree.getChildren(this, children) + return if (count > 0) children.get().filterNotNull() else emptyList() + } + + private fun DiagnosticReporter.report(source: FirSourceElement?) { + source?.let { report(FirErrors.SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR.on(it)) } + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 2b253cce496..17a9c293347 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -66,6 +66,7 @@ object FirErrors { val CYCLIC_CONSTRUCTOR_DELEGATION_CALL by warning0() val PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED by warning0() + val SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR by warning0() // Exposed visibility group val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3() diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt index d2077b092e7..31311893c36 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt @@ -1350,7 +1350,12 @@ class DeclarationsConverter( var delegateNumber = 0 delegationSpecifiers.forEachChildren { when (it.tokenType) { - SUPER_TYPE_ENTRY -> superTypeRefs += convertType(it) + SUPER_TYPE_ENTRY -> { + superTypeRefs += convertType(it) + if (delegateConstructorSource == null) { + delegateConstructorSource = it.toFirSourceElement() + } + } SUPER_TYPE_CALL_ENTRY -> convertConstructorInvocation(it).apply { delegatedSuperTypeRef = first superTypeRefs += first diff --git a/compiler/testData/diagnostics/tests/secondaryConstructors/headerSupertypeInitialization.fir.kt b/compiler/testData/diagnostics/tests/secondaryConstructors/headerSupertypeInitialization.fir.kt index eb42f37c72a..9168d52c96c 100644 --- a/compiler/testData/diagnostics/tests/secondaryConstructors/headerSupertypeInitialization.fir.kt +++ b/compiler/testData/diagnostics/tests/secondaryConstructors/headerSupertypeInitialization.fir.kt @@ -1,5 +1,5 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER open class B(x: Int) -class A : B(1) { +class A : B(1) { constructor(): super(1) -} +}