diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 12c597f55f3..3d5f73a6da1 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -278,6 +278,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.MISSING_CONSTRUCTOR_KEYWORD) { firDiagnostic -> + MissingConstructorKeywordImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.INVISIBLE_REFERENCE) { firDiagnostic -> InvisibleReferenceImpl( firSymbolBuilder.buildSymbol(firDiagnostic.a), diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 40bcfc7c6e1..dc2144bbaa3 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -237,6 +237,10 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { val diagnosticName: String } + interface MissingConstructorKeyword : KtFirDiagnostic { + override val diagnosticClass get() = MissingConstructorKeyword::class + } + interface InvisibleReference : KtFirDiagnostic { override val diagnosticClass get() = InvisibleReference::class val reference: KtSymbol diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 30757a60842..c8c19d508d8 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -270,6 +270,11 @@ internal class ErrorSuppressionImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.ErrorSuppression +internal class MissingConstructorKeywordImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.MissingConstructorKeyword + internal class InvisibleReferenceImpl( override val reference: KtSymbol, override val visible: Visibility, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index d6759c2c849..ac14b321d41 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -108,6 +108,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val ERROR_SUPPRESSION by warning { parameter("diagnosticName") } + val MISSING_CONSTRUCTOR_KEYWORD by error() } val UNRESOLVED by object : DiagnosticGroup("Unresolved") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index c9500ddcc23..4e81d19ac08 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -153,6 +153,7 @@ object FirErrors { val INVISIBLE_SETTER by error3(SourceElementPositioningStrategies.SELECTOR_BY_QUALIFIED) val INNER_ON_TOP_LEVEL_SCRIPT_CLASS by deprecationError0(ProhibitScriptTopLevelInnerClasses) val ERROR_SUPPRESSION by warning1() + val MISSING_CONSTRUCTOR_KEYWORD by error0() // Unresolved val INVISIBLE_REFERENCE by error3, Visibility, ClassId?>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt index 2d8231cedab..628b224702b 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt @@ -40,6 +40,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = setOf( "VAL_OR_VAR_ON_CATCH_PARAMETER", "VAL_OR_VAR_ON_SECONDARY_CONSTRUCTOR_PARAMETER", "INVISIBLE_SETTER", + "MISSING_CONSTRUCTOR_KEYWORD", "INVISIBLE_REFERENCE", "UNRESOLVED_REFERENCE", "UNRESOLVED_LABEL", diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPrimaryConstructorSuperTypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPrimaryConstructorSuperTypeChecker.kt index 33562f1aba8..32165f37129 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPrimaryConstructorSuperTypeChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPrimaryConstructorSuperTypeChecker.kt @@ -8,20 +8,20 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.descriptors.isEnumEntry +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors -import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.declarations.FirClass -import org.jetbrains.kotlin.fir.declarations.FirErrorPrimaryConstructor import org.jetbrains.kotlin.fir.declarations.FirRegularClass -import org.jetbrains.kotlin.fir.declarations.utils.isInterface import org.jetbrains.kotlin.fir.declarations.primaryConstructorIfAny -import org.jetbrains.kotlin.fir.symbols.SymbolInternals +import org.jetbrains.kotlin.fir.declarations.utils.isErrorPrimaryConstructor +import org.jetbrains.kotlin.fir.declarations.utils.isInterface import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol -import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.impl.FirImplicitAnyTypeRef +import org.jetbrains.kotlin.fir.types.toRegularClassSymbol import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull @@ -43,8 +43,7 @@ object FirPrimaryConstructorSuperTypeChecker : FirClassChecker() { val primaryConstructorSymbol = declaration.primaryConstructorIfAny(context.session) - @OptIn(SymbolInternals::class) - if (primaryConstructorSymbol == null || primaryConstructorSymbol.fir is FirErrorPrimaryConstructor) { + if (primaryConstructorSymbol == null || primaryConstructorSymbol.isErrorPrimaryConstructor) { checkSupertypeInitializedWithoutPrimaryConstructor(declaration, reporter, context) } else { checkSuperTypeNotInitialized(primaryConstructorSymbol, declaration, context, reporter) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollectorVisitor.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollectorVisitor.kt index 9696551da1b..2f9b3d420ce 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollectorVisitor.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollectorVisitor.kt @@ -122,6 +122,9 @@ abstract class AbstractDiagnosticCollectorVisitor( } } + override fun visitErrorPrimaryConstructor(errorPrimaryConstructor: FirErrorPrimaryConstructor, data: Nothing?) = + visitConstructor(errorPrimaryConstructor, data) + override fun visitAnonymousFunctionExpression(anonymousFunctionExpression: FirAnonymousFunctionExpression, data: Nothing?) { visitAnonymousFunction(anonymousFunctionExpression.anonymousFunction, data) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index b9f8ef5e2cf..9ea9d96c249 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -355,6 +355,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_INTERFACES_M import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_LAMBDA_EXPRESSION_ARGUMENTS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.METHOD_OF_ANY_IMPLEMENTED_IN_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_CONSTRUCTOR_KEYWORD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_STDLIB_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MIXING_FUNCTIONAL_KINDS_IN_SUPERTYPES @@ -765,6 +766,7 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { "This code uses error suppression for ''{0}''. While it might compile and work, the compiler behavior is UNSPECIFIED and WON''T BE PRESERVED. Please report your use case to the Kotlin issue tracker instead: https://kotl.in/issue", TO_STRING ) + map.put(MISSING_CONSTRUCTOR_KEYWORD, "Use the 'constructor' keyword after the modifiers of the primary constructor.") map.put(UNRESOLVED_REFERENCE, "Unresolved reference ''{0}''.", NULLABLE_STRING) map.put(UNRESOLVED_IMPORT, "Unresolved reference ''{0}''.", NULLABLE_STRING) // & map.put(UNRESOLVED_LABEL, "Unresolved label.") diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index 57a5a1f8ccd..10a18848893 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -153,6 +153,7 @@ private fun ConeDiagnostic.toKtDiagnostic( FirErrors.AMBIGUOUS_ANNOTATION_ARGUMENT.createOn(source, listOfNotNull(symbolFromCompilerPhase, symbolFromAnnotationArgumentsPhase)) is ConeAmbiguousFunctionTypeKinds -> FirErrors.AMBIGUOUS_FUNCTION_TYPE_KIND.createOn(source, kinds) is ConeUnsupportedClassLiteralsWithEmptyLhs -> FirErrors.UNSUPPORTED_CLASS_LITERALS_WITH_EMPTY_LHS.createOn(source) + is ConeMissingConstructorKeyword -> FirErrors.MISSING_CONSTRUCTOR_KEYWORD.createOn(source) else -> throw IllegalArgumentException("Unsupported diagnostic type: ${this.javaClass}") } diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt index 900b05235e2..b091cbab311 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/LightTreeRawFirDeclarationBuilder.kt @@ -904,14 +904,17 @@ class LightTreeRawFirDeclarationBuilder( } val constructorSymbol = FirConstructorSymbol(callableIdForClassConstructor()) - var modifiers = Modifier() + var modifiersIfPresent: Modifier? = null val valueParameters = mutableListOf() + var hasConstructorKeyword = false primaryConstructor?.forEachChildren { when (it.tokenType) { - MODIFIER_LIST -> modifiers = convertModifierList(it) + MODIFIER_LIST -> modifiersIfPresent = convertModifierList(it) + CONSTRUCTOR_KEYWORD -> hasConstructorKeyword = true VALUE_PARAMETER_LIST -> valueParameters += convertValueParameters(it, constructorSymbol, ValueParameterDeclaration.PRIMARY_CONSTRUCTOR) } } + val modifiers = modifiersIfPresent ?: Modifier() val defaultVisibility = classWrapper.defaultConstructorVisibility() val firDelegatedCall = runUnless(containingClassIsExpectClass) { @@ -968,12 +971,10 @@ class LightTreeRawFirDeclarationBuilder( isFromEnumClass = classWrapper.isEnum() } - val builder = if (isErrorConstructor) { - FirErrorConstructorBuilder().apply { - diagnostic = ConeNoConstructorError - } - } else { - FirPrimaryConstructorBuilder() + val builder = when { + modifiersIfPresent != null && !hasConstructorKeyword -> createErrorConstructorBuilder(ConeMissingConstructorKeyword) + isErrorConstructor -> createErrorConstructorBuilder(ConeNoConstructorError) + else -> FirPrimaryConstructorBuilder() } builder.apply { source = primaryConstructor?.toFirSourceElement() diff --git a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/PsiRawFirBuilder.kt b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/PsiRawFirBuilder.kt index 82f762c9098..69e04887ec4 100644 --- a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/PsiRawFirBuilder.kt +++ b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/PsiRawFirBuilder.kt @@ -1059,12 +1059,11 @@ open class PsiRawFirBuilder( isFromSealedClass = owner.hasModifier(SEALED_KEYWORD) && explicitVisibility !== Visibilities.Private isFromEnumClass = owner.hasModifier(ENUM_KEYWORD) } - val builder = if (isErrorConstructor) { - FirErrorConstructorBuilder().apply { - diagnostic = ConeNoConstructorError - } - } else { - FirPrimaryConstructorBuilder() + val hasConstructorKeyword = this@toFirConstructor?.getConstructorKeyword() != null + val builder = when { + this?.modifierList != null && !hasConstructorKeyword -> createErrorConstructorBuilder(ConeMissingConstructorKeyword) + isErrorConstructor -> createErrorConstructorBuilder(ConeNoConstructorError) + else -> FirPrimaryConstructorBuilder() } builder.apply { source = constructorSource diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirDeclarationUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirDeclarationUtil.kt index 15e4cd24fee..d0fc11fa3fd 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirDeclarationUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/FirDeclarationUtil.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.fir.declarations.utils import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.coneTypeSafe @@ -68,3 +69,5 @@ val FirNamedFunctionSymbol.isMethodOfAny: Boolean else -> false } } + +val FirConstructorSymbol.isErrorPrimaryConstructor get() = fir is FirErrorPrimaryConstructor diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/diagnostics/ConeSimpleDiagnostic.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/diagnostics/ConeSimpleDiagnostic.kt index a101985ecb3..079308f0a5d 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/diagnostics/ConeSimpleDiagnostic.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/diagnostics/ConeSimpleDiagnostic.kt @@ -83,6 +83,10 @@ object ConeNoConstructorError : ConeDiagnostic { override val reason: String get() = "This type does not have a constructor" } +object ConeMissingConstructorKeyword : ConeDiagnostic { + override val reason: String get() = "Use the 'constructor' keyword after the modifiers of the primary constructor." +} + enum class DiagnosticKind { ExpressionExpected, NotLoopLabel, diff --git a/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.fir.kt b/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.fir.kt index babb16e5b07..27f6b932bc8 100644 --- a/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.fir.kt +++ b/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.fir.kt @@ -1,9 +1,9 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER annotation class Ann(val x: Int = 1) -class A private (val x: Int) { -inner class B @Ann(2) (val y: Int) +class A private (val x: Int) { + inner class B @Ann(2) (val y: Int) -fun foo() { - class C private @Ann(3) (args: Int) + fun foo() { + class C private @Ann(3) (args: Int) } } diff --git a/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.kt b/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.kt index a6771d2e4f3..29f16115f4c 100644 --- a/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.kt +++ b/compiler/testData/diagnostics/tests/modifiers/primaryConstructorMissingKeyword.kt @@ -1,9 +1,9 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER annotation class Ann(val x: Int = 1) class A private (val x: Int) { -inner class B @Ann(2) (val y: Int) + inner class B @Ann(2) (val y: Int) -fun foo() { - class C private @Ann(3) (args: Int) + fun foo() { + class C private @Ann(3) (args: Int) } }