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 77527adfc7e..51f36fe7f62 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 @@ -1406,6 +1406,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET) { firDiagnostic -> + AnnotationOnIllegalMultiFieldValueClassTypedTargetImpl( + firDiagnostic.a, + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.NONE_APPLICABLE) { firDiagnostic -> NoneApplicableImpl( firDiagnostic.a.map { firBasedSymbol -> 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 a13ae620aeb..21cc7f95668 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 @@ -1012,6 +1012,11 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = ValueClassCannotBeCloneable::class } + abstract class AnnotationOnIllegalMultiFieldValueClassTypedTarget : KtFirDiagnostic() { + override val diagnosticClass get() = AnnotationOnIllegalMultiFieldValueClassTypedTarget::class + abstract val name: String + } + abstract class NoneApplicable : KtFirDiagnostic() { override val diagnosticClass get() = NoneApplicable::class abstract val candidates: List 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 6b05c8bf18c..4ac58ca865b 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 @@ -1214,6 +1214,12 @@ internal class ValueClassCannotBeCloneableImpl( override val token: KtLifetimeToken, ) : KtFirDiagnostic.ValueClassCannotBeCloneable(), KtAbstractFirDiagnostic +internal class AnnotationOnIllegalMultiFieldValueClassTypedTargetImpl( + override val name: String, + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.AnnotationOnIllegalMultiFieldValueClassTypedTarget(), KtAbstractFirDiagnostic + internal class NoneApplicableImpl( override val candidates: List, override val firDiagnostic: KtPsiDiagnostic, diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index af6238c1104..b5b973c69a3 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -33612,6 +33612,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/valueClasses"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("annotations.kt") + public void testAnnotations() throws Exception { + runTest("compiler/testData/diagnostics/tests/valueClasses/annotations.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithUniversal()); + } + @Test @TestMetadata("basicValueClassDeclaration.kt") public void testBasicValueClassDeclaration() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 00bc8101df0..2d7b6b5c48f 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -33708,6 +33708,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/valueClasses"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("annotations.kt") + public void testAnnotations() throws Exception { + runTest("compiler/testData/diagnostics/tests/valueClasses/annotations.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithUniversal()); + } + @Test @TestMetadata("basicValueClassDeclaration.kt") public void testBasicValueClassDeclaration() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index bd77e6cb80e..80db07a69e0 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -33612,6 +33612,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/valueClasses"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("annotations.kt") + public void testAnnotations() throws Exception { + runTest("compiler/testData/diagnostics/tests/valueClasses/annotations.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithUniversal()); + } + @Test @TestMetadata("basicValueClassDeclaration.kt") public void testBasicValueClassDeclaration() throws Exception { 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 efde962632d..88a79151836 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 @@ -434,6 +434,9 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val TYPE_ARGUMENT_ON_TYPED_VALUE_CLASS_EQUALS by error() val INNER_CLASS_INSIDE_VALUE_CLASS by error(PositioningStrategy.INNER_MODIFIER) val VALUE_CLASS_CANNOT_BE_CLONEABLE by error(PositioningStrategy.INLINE_OR_VALUE_MODIFIER) + val ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET by error { + parameter("name") + } } val APPLICABILITY by object : DiagnosticGroup("Applicability") { diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmFieldApplicabilityChecker.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmFieldApplicabilityChecker.kt index 685b749b89f..d2759a3e88f 100644 --- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmFieldApplicabilityChecker.kt +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmFieldApplicabilityChecker.kt @@ -5,21 +5,22 @@ package org.jetbrains.kotlin.fir.analysis.jvm.checkers.declaration -import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.JvmFieldApplicabilityProblem.* +import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.classKind 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.declaration.FirPropertyChecker +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.needsMultiFieldValueClassFlattening import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol -import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors -import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.containingClassLookupTag import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirProperty @@ -65,7 +66,8 @@ object FirJvmFieldApplicabilityChecker : FirPropertyChecker() { } containingClassSymbol == null && isInsideJvmMultifileClassFile(context) -> TOP_LEVEL_PROPERTY_OF_MULTIFILE_FACADE - declaration.returnTypeRef.isInlineClassThatRequiresMangling(session) -> RETURN_TYPE_IS_INLINE_CLASS + declaration.returnTypeRef.isInlineClassThatRequiresMangling(session) -> RETURN_TYPE_IS_VALUE_CLASS + declaration.returnTypeRef.needsMultiFieldValueClassFlattening(session) -> RETURN_TYPE_IS_VALUE_CLASS else -> return } diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmNameChecker.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmNameChecker.kt index 73deae09b2c..fe84099b3ab 100644 --- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmNameChecker.kt +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmNameChecker.kt @@ -48,7 +48,7 @@ object FirJvmNameChecker : FirBasicDeclarationChecker() { if ( declaration.isOverride || declaration.isOpen || - containingClass?.isInlineThatRequiresMangling() == true + containingClass?.isValueClassThatRequiresMangling() == true ) { reporter.reportOn(jvmName.source, FirJvmErrors.INAPPLICABLE_JVM_NAME, context) } @@ -66,7 +66,8 @@ object FirJvmNameChecker : FirBasicDeclarationChecker() { return containingClass != null || !function.symbol.callableId.isLocal } - private fun FirRegularClass.isInlineThatRequiresMangling(): Boolean { + private fun FirRegularClass.isValueClassThatRequiresMangling(): Boolean { + // value classes have inline modifiers in FIR return isInline && name != StandardClassIds.Result.shortClassName } } 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 717c05c6735..57dde324ba9 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 @@ -327,6 +327,7 @@ object FirErrors { val TYPE_ARGUMENT_ON_TYPED_VALUE_CLASS_EQUALS by error0() val INNER_CLASS_INSIDE_VALUE_CLASS by error0(SourceElementPositioningStrategies.INNER_MODIFIER) val VALUE_CLASS_CANNOT_BE_CLONEABLE by error0(SourceElementPositioningStrategies.INLINE_OR_VALUE_MODIFIER) + val ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET by error1() // Applicability val NONE_APPLICABLE by error1>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 7828e567dff..030e81d009f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.primaryConstructorSymbol -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.primaryConstructorSymbol import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.getChild import org.jetbrains.kotlin.fir.containingClassForLocalAttr @@ -98,7 +97,33 @@ fun FirClassSymbol<*>.isSupertypeOf(other: FirClassSymbol<*>, session: FirSessio return isSupertypeOf(other, mutableSetOf()) } -fun ConeKotlinType.isInlineClass(session: FirSession): Boolean = toRegularClassSymbol(session)?.isInline == true +fun ConeKotlinType.isValueClass(session: FirSession): Boolean { + // Value classes have inline modifier in FIR + return toRegularClassSymbol(session)?.isInline == true +} + +fun ConeKotlinType.isSingleFieldValueClass(session: FirSession): Boolean = with(session.typeContext) { + isRecursiveSingleFieldValueClassType(session) || typeConstructor().isInlineClass() +} + +fun ConeKotlinType.isRecursiveSingleFieldValueClassType(session: FirSession) = + isRecursiveValueClassType(hashSetOf(), session, onlyInline = true) + +fun ConeKotlinType.isRecursiveValueClassType(session: FirSession) = + isRecursiveValueClassType(hashSetOf(), session, onlyInline = false) + +private fun ConeKotlinType.isRecursiveValueClassType(visited: HashSet, session: FirSession, onlyInline: Boolean): Boolean { + + val asRegularClass = this.toRegularClassSymbol(session)?.takeIf { it.isInlineOrValueClass() } ?: return false + val primaryConstructor = asRegularClass.declarationSymbols + .firstOrNull { it is FirConstructorSymbol && it.isPrimary } as FirConstructorSymbol? + ?: return false + + if (primaryConstructor.valueParameterSymbols.size > 1 && onlyInline) return false + return !visited.add(this) || primaryConstructor.valueParameterSymbols.any { + it.resolvedReturnTypeRef.coneType.isRecursiveValueClassType(visited, session, onlyInline) + }.also { visited.remove(this) } +} /** * Returns the FirRegularClass associated with this @@ -678,6 +703,6 @@ private fun findDefaultValue(source: KtLightSourceElement): KtLightSourceElement } fun ConeKotlinType.getInlineClassUnderlyingType(session: FirSession): ConeKotlinType { - require(this.isInlineClass(session)) + require(this.isSingleFieldValueClass(session)) return toRegularClassSymbol(session)!!.primaryConstructorSymbol()!!.valueParameterSymbols[0].resolvedReturnTypeRef.coneType } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt index a54fda3e5a1..5957d41ff8e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationChecker.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.* import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.hasValOrVar @@ -18,6 +19,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.languageVersionSettings @@ -70,6 +72,44 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() { } } + private fun checkMultiFieldValueClassAnnotationRestrictions( + declaration: FirDeclaration, + annotation: FirAnnotation, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + val (hint, type) = when (annotation.useSiteTarget) { + FIELD, PROPERTY_DELEGATE_FIELD -> "fields" to ((declaration as? FirProperty)?.backingField?.returnTypeRef ?: return) + FILE, PROPERTY, PROPERTY_SETTER -> return + PROPERTY_GETTER -> "getters" to ((declaration as? FirPropertyAccessor)?.returnTypeRef ?: return) + RECEIVER -> "receivers" to ((declaration as? FirCallableDeclaration)?.receiverTypeRef ?: return) + CONSTRUCTOR_PARAMETER, SETTER_PARAMETER -> "parameters" to (declaration as? FirValueParameter ?: return).returnTypeRef + null -> when { + declaration is FirProperty && !declaration.isLocal -> { + val allowedAnnotationTargets = annotation.getAllowedAnnotationTargets(context.session) + when { + declaration.fromPrimaryConstructor == true && allowedAnnotationTargets.contains(KotlinTarget.VALUE_PARAMETER) -> return // handled in FirValueParameter case + allowedAnnotationTargets.contains(KotlinTarget.PROPERTY) -> return + allowedAnnotationTargets.contains(KotlinTarget.FIELD) -> "fields" to declaration.returnTypeRef + else -> return + } + } + declaration is FirField -> "fields" to declaration.returnTypeRef + declaration is FirValueParameter -> "parameters" to declaration.returnTypeRef + declaration is FirVariable -> "variables" to declaration.returnTypeRef + declaration is FirPropertyAccessor && declaration.isGetter && + declaration.receiverTypeRef == null && + declaration.contextReceivers.isEmpty() -> + "getters" to declaration.returnTypeRef + + else -> return + } + } + if (type.needsMfvcFlattening(context.session)) { + reporter.reportOn(annotation.source, FirErrors.ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET, hint, context) + } + } + private fun checkAnnotationTarget( declaration: FirDeclaration, annotation: FirAnnotation, @@ -96,6 +136,7 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() { } if (check(actualTargets.defaultTargets) || check(actualTargets.canBeSubstituted) || checkWithUseSiteTargets()) { + checkMultiFieldValueClassAnnotationRestrictions(declaration, annotation, context, reporter) return } @@ -128,28 +169,28 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() { ) { if (annotation.source?.kind == KtFakeSourceElementKind.FromUseSiteTarget) return when (target) { - AnnotationUseSiteTarget.PROPERTY, - AnnotationUseSiteTarget.PROPERTY_GETTER -> { + PROPERTY, + PROPERTY_GETTER -> { } - AnnotationUseSiteTarget.FIELD -> { + FIELD -> { if (annotated is FirProperty && annotated.delegateFieldSymbol != null && !annotated.hasBackingField) { reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_TARGET_PROPERTY_HAS_NO_BACKING_FIELD, context) } } - AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD -> { + PROPERTY_DELEGATE_FIELD -> { if (annotated is FirProperty && annotated.delegateFieldSymbol == null) { reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_TARGET_PROPERTY_HAS_NO_DELEGATE, context) } } - AnnotationUseSiteTarget.PROPERTY_SETTER, - AnnotationUseSiteTarget.SETTER_PARAMETER -> { + PROPERTY_SETTER, + SETTER_PARAMETER -> { if (annotated !is FirProperty || annotated.isLocal) { reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_TARGET_ON_PROPERTY, target.renderName, context) } else if (!annotated.isVar) { reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_TARGET_PROPERTY_IMMUTABLE, target.renderName, context) } } - AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER -> when { + CONSTRUCTOR_PARAMETER -> when { annotated is FirValueParameter -> { val container = context.containingDeclarations.lastOrNull() if (container is FirConstructor && container.isPrimary) { @@ -164,13 +205,13 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() { } else -> reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_PARAM_TARGET, context) } - AnnotationUseSiteTarget.FILE -> { + FILE -> { // NB: report once? if (annotated !is FirFile) { reporter.reportOn(annotation.source, FirErrors.INAPPLICABLE_FILE_TARGET, context) } } - AnnotationUseSiteTarget.RECEIVER -> { + RECEIVER -> { // NB: report once? // annotation with use-site target `receiver` can be only on type reference, but not on declaration reporter.reportOn( @@ -245,9 +286,9 @@ object FirAnnotationChecker : FirBasicDeclarationChecker() { } val propertyAnnotations = mapOf( - AnnotationUseSiteTarget.PROPERTY_GETTER to property.getter?.getAnnotationTypes(), - AnnotationUseSiteTarget.PROPERTY_SETTER to property.setter?.getAnnotationTypes(), - AnnotationUseSiteTarget.SETTER_PARAMETER to property.setter?.valueParameters?.single().getAnnotationTypes() + PROPERTY_GETTER to property.getter?.getAnnotationTypes(), + PROPERTY_SETTER to property.setter?.getAnnotationTypes(), + SETTER_PARAMETER to property.setter?.valueParameters?.single().getAnnotationTypes() ) val isError = context.session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitRepeatedUseSiteTargetAnnotations) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt index b68a5addc3a..36c647493f2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtRealSourceElementKind import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.isInlineClass +import org.jetbrains.kotlin.fir.analysis.checkers.isValueClass import org.jetbrains.kotlin.fir.analysis.checkers.valOrVarKeyword import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors @@ -66,7 +66,7 @@ object FirFunctionParameterChecker : FirFunctionChecker() { for (varargParameter in varargParameters) { val varargParameterType = varargParameter.returnTypeRef.coneType.arrayElementType() ?: continue if (AbstractTypeChecker.isSubtypeOf(context.session.typeContext, varargParameterType, nullableNothingType) || - (varargParameterType.isInlineClass(context.session) && !varargParameterType.isUnsignedTypeOrNullableUnsignedType) + (varargParameterType.isValueClass(context.session) && !varargParameterType.isUnsignedTypeOrNullableUnsignedType) // Note: comparing with FE1.0, we skip checking if the type is not primitive because primitive types are not inline. That // is any primitive values are already allowed by the inline check. ) { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInapplicableLateinitChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInapplicableLateinitChecker.kt index a5baa916a26..4c5caf40b59 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInapplicableLateinitChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInapplicableLateinitChecker.kt @@ -11,15 +11,14 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineClassDeclarationChecker.isRecursiveInlineClassType import org.jetbrains.kotlin.fir.analysis.checkers.getInlineClassUnderlyingType -import org.jetbrains.kotlin.fir.analysis.checkers.isInlineClass +import org.jetbrains.kotlin.fir.analysis.checkers.isRecursiveValueClassType +import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyGetter import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertySetter import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField -import org.jetbrains.kotlin.fir.declarations.utils.isInline import org.jetbrains.kotlin.fir.declarations.utils.isLateInit import org.jetbrains.kotlin.fir.types.* @@ -65,7 +64,7 @@ object FirInapplicableLateinitChecker : FirPropertyChecker() { reporter.reportError(declaration.source, "is not allowed on properties with a custom getter or setter", context) } - if (declaration.returnTypeRef.coneType.isInlineClass(context.session)) { + if (declaration.returnTypeRef.coneType.isSingleFieldValueClass(context.session)) { val declarationType = declaration.returnTypeRef.coneType val variables = if (declaration.isLocal) "local variables" else "properties" when { @@ -93,14 +92,14 @@ object FirInapplicableLateinitChecker : FirPropertyChecker() { fun isForbiddenTypeForLateinit(type: ConeKotlinType): Boolean { if (type.isPrimitiveOrNullablePrimitive) return true if (type.hasNullableUpperBound) return true - if (type.isInlineClass(session)) { + if (type.isSingleFieldValueClass(session)) { return isForbiddenTypeForLateinit(type.getInlineClassUnderlyingType(session)) } return false } // prevent infinite recursion - if (type.isRecursiveInlineClassType(session)) return false + if (type.isRecursiveValueClassType(session)) return false return isForbiddenTypeForLateinit(type.getInlineClassUnderlyingType(session)) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirValueClassDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirValueClassDeclarationChecker.kt index 927cee9c129..ef4bc2dd473 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirValueClassDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirValueClassDeclarationChecker.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.hasModifier +import org.jetbrains.kotlin.fir.analysis.checkers.isRecursiveValueClassType import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.* @@ -23,7 +24,6 @@ import org.jetbrains.kotlin.fir.resolve.defaultType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.isEquals import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes -import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtTokens @@ -197,7 +197,7 @@ object FirValueClassDeclarationChecker : FirRegularClassChecker() { ) } - primaryConstructorParameter.returnTypeRef.coneType.isRecursiveInlineClassType(context.session) -> { + primaryConstructorParameter.returnTypeRef.coneType.isRecursiveValueClassType(context.session) -> { reporter.reportOn( primaryConstructorParameter.returnTypeRef.source, FirErrors.VALUE_CLASS_CANNOT_BE_RECURSIVE, context @@ -276,21 +276,6 @@ object FirValueClassDeclarationChecker : FirRegularClassChecker() { arrayElementType.isGenericArrayOfTypeParameter() } - fun ConeKotlinType.isRecursiveInlineClassType(session: FirSession) = - isRecursiveInlineClassType(hashSetOf(), session) - - private fun ConeKotlinType.isRecursiveInlineClassType(visited: HashSet, session: FirSession): Boolean { - - val asRegularClass = this.toRegularClassSymbol(session)?.takeIf { it.isInlineOrValueClass() } ?: return false - val primaryConstructor = asRegularClass.declarationSymbols - .firstOrNull { it is FirConstructorSymbol && it.isPrimary } as FirConstructorSymbol? - ?: return false - - return !visited.add(this) || primaryConstructor.valueParameterSymbols.any { - it.resolvedReturnTypeRef.coneType.isRecursiveInlineClassType(visited, session) - }.also { visited.remove(this) } - } - private fun FirRegularClass.isSubtypeOfCloneable(session: FirSession): Boolean { if (classId.isCloneableId()) return true diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt index bd1a7b1e3f5..3a994c30fa7 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt @@ -18,11 +18,8 @@ import org.jetbrains.kotlin.fir.resolve.defaultType import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.* -import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.impl.FirImplicitUnitTypeRef -import org.jetbrains.kotlin.fir.types.isBoolean -import org.jetbrains.kotlin.fir.types.isNothing -import org.jetbrains.kotlin.fir.types.replaceArgumentsWithStarProjections import org.jetbrains.kotlin.util.OperatorNameConventions internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean { @@ -131,6 +128,10 @@ fun FirSimpleFunction.isTypedEqualsInValueClass(session: FirSession): Boolean = } } ?: false +fun FirTypeRef.needsMultiFieldValueClassFlattening(session: FirSession) = with(session.typeContext) { + coneType.typeConstructor().isMultiFieldValueClass() && !coneType.isNullable +} + val FirCallableSymbol<*>.hasExplicitReturnType: Boolean get() { val returnTypeRef = resolvedReturnTypeRef 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 c111d036aae..ef41f20f623 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 @@ -68,6 +68,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_ARGUME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_CLASS_CONSTRUCTOR_CALL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_CLASS_MEMBER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_IN_WHERE_CLAUSE_ERROR +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_ON_SUPERCLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_USED_AS_ANNOTATION_ARGUMENT @@ -1290,6 +1291,12 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { map.put(TYPE_ARGUMENT_ON_TYPED_VALUE_CLASS_EQUALS, "Type arguments for typed value class equals must be only star projections") map.put(INNER_CLASS_INSIDE_VALUE_CLASS, "Value class cannot have inner classes") map.put(VALUE_CLASS_CANNOT_BE_CLONEABLE, "Value class cannot be Cloneable") + map.put( + ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET, + "Annotations on ''{0}'' of multi-field value class type are not supported", + STRING + ) + // Inline map.put( diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt index a62079d3253..7db913e42d1 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmFieldApplicabilityChecker.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.resolve.jvm.checkers +import org.jetbrains.kotlin.JvmFieldApplicabilityProblem.* import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.fileClasses.isInsideJvmMultifileClassFile @@ -30,9 +31,9 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmFieldAnnotation -import org.jetbrains.kotlin.JvmFieldApplicabilityProblem.* import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm import org.jetbrains.kotlin.resolve.jvm.isInlineClassThatRequiresMangling +import org.jetbrains.kotlin.resolve.needsMfvcFlattening import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.MemberScope @@ -63,7 +64,8 @@ class JvmFieldApplicabilityChecker : DeclarationChecker { } DescriptorUtils.isTopLevelDeclaration(descriptor) && declaration.isInsideJvmMultifileClassFile() -> TOP_LEVEL_PROPERTY_OF_MULTIFILE_FACADE - descriptor.returnType?.isInlineClassThatRequiresMangling() == true -> RETURN_TYPE_IS_INLINE_CLASS + descriptor.returnType?.isInlineClassThatRequiresMangling() == true -> RETURN_TYPE_IS_VALUE_CLASS + descriptor.returnType?.needsMfvcFlattening() == true -> RETURN_TYPE_IS_VALUE_CLASS else -> return } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt index 042395e3cf8..b51b224e247 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.kotlin.resolve.isMultiFieldValueClass import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmOverloadsAnnotation import org.jetbrains.kotlin.resolve.jvm.annotations.findSynchronizedAnnotation import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmFieldAnnotation @@ -157,6 +158,8 @@ class JvmNameAnnotationChecker : DeclarationChecker { diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) } else if (descriptor.containingDeclaration.isInlineClassThatRequiresMangling()) { diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) + } else if (descriptor.containingDeclaration.isMultiFieldValueClass()) { + diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry)) } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index ae1d8d61076..836ea417c80 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -430,6 +430,9 @@ public interface Errors { DiagnosticFactory0 VALUE_CLASS_CANNOT_HAVE_CONTEXT_RECEIVERS = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 INEFFICIENT_EQUALS_OVERRIDING_IN_VALUE_CLASS = DiagnosticFactory1.create(WARNING, DECLARATION_NAME); + + DiagnosticFactory1 ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET = + DiagnosticFactory1.create(ERROR); // Result class diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 602d40adcbd..7256c1f1234 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -806,6 +806,7 @@ public class DefaultErrorMessages { MAP.put(INEFFICIENT_EQUALS_OVERRIDING_IN_VALUE_CLASS, "Overriding ''equals'' from ''Any'' in value class without operator ''equals(other: {0}): Boolean'' leads to boxing on every equality comparison", RENDER_TYPE); + MAP.put(ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET, "Annotations on {0} of multi-field value class type are not supported", STRING); MAP.put(RESULT_CLASS_IN_RETURN_TYPE, "'kotlin.Result' cannot be used as a return type"); MAP.put(RESULT_CLASS_WITH_NULLABLE_OPERATOR, "Expression of type ''kotlin.Result'' cannot be used as a left operand of ''{0}''", STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 657936f1660..7d163b02439 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -27,6 +27,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( KClassWithIncorrectTypeArgumentChecker, SuspendLimitationsChecker, ValueClassDeclarationChecker, + MultiFieldValueClassAnnotationsChecker, PropertiesWithBackingFieldsInsideValueClass(), InnerClassInsideValueClass(), AnnotationClassTargetAndRetentionChecker(), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MultiFieldValueClassAnnotationsChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MultiFieldValueClassAnnotationsChecker.kt new file mode 100644 index 00000000000..0557c3f57f5 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/MultiFieldValueClassAnnotationsChecker.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2022 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.resolve.checkers + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.resolve.calls.util.getType +import org.jetbrains.kotlin.resolve.needsMfvcFlattening +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.types.KotlinType + +object MultiFieldValueClassAnnotationsChecker : DeclarationChecker { + private fun report(context: DeclarationCheckerContext, name: String, type: KotlinType, annotationEntry: KtAnnotationEntry) { + if (!type.needsMfvcFlattening()) return + context.trace.report(Errors.ANNOTATION_ON_ILLEGAL_MULTI_FIELD_VALUE_CLASS_TYPED_TARGET.on(annotationEntry, name)) + } + + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + fun report(name: String, type: KotlinType, annotations: Annotations) { + for (annotationDescriptor in annotations) { + report(context, name, type, annotationDescriptor.source.getPsi() as KtAnnotationEntry) + } + } + + when (descriptor) { + is PropertyDescriptor -> { + descriptor.backingField?.let { report("fields", descriptor.type, it.annotations) } + val delegateType = (declaration as? KtProperty)?.delegateExpression?.getType(context.trace.bindingContext) + descriptor.delegateField?.let { + if (delegateType == null) return@let + report("delegate fields", delegateType, it.annotations) + } + descriptor.getter?.let { getterDescriptor -> + if (getterDescriptor.contextReceiverParameters.isNotEmpty() || getterDescriptor.extensionReceiverParameter != null) return@let + val type = getterDescriptor.returnType ?: return@let + report("getters", type, getterDescriptor.annotations) + } + descriptor.setter?.valueParameters?.single()?.let { report("parameters", it.type, it.annotations) } + descriptor.extensionReceiverParameter?.let { report("receivers", it.type, it.annotations) } + descriptor.contextReceiverParameters.forEach { report("receivers", it.type, it.annotations) } + } + is PropertyAccessorDescriptor -> Unit + is LocalVariableDescriptor -> { + report("variables", descriptor.type, descriptor.annotations) + } + is CallableDescriptor -> { + descriptor.extensionReceiverParameter?.let { report("receivers", it.type, it.annotations) } + descriptor.contextReceiverParameters.forEach { report("receivers", it.type, it.annotations) } + descriptor.valueParameters.forEach { report("parameters", it.type, it.annotations) } + } + } + } +} diff --git a/compiler/testData/diagnostics/tests/valueClasses/annotations.kt b/compiler/testData/diagnostics/tests/valueClasses/annotations.kt new file mode 100644 index 00000000000..d7711682432 --- /dev/null +++ b/compiler/testData/diagnostics/tests/valueClasses/annotations.kt @@ -0,0 +1,113 @@ +// !LANGUAGE: +ValueClasses +// WITH_STDLIB +// SKIP_TXT +// WORKS_WHEN_VALUE_CLASS +// FIR_IDENTICAL + +@Repeatable +annotation class Ann + +@[Ann Ann] +@JvmInline +value class A @Ann constructor( + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + val x: Int, + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + val y: Int, +) + +@[Ann Ann] +@JvmInline +value class B @Ann constructor( + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + val x: A, + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + val y: A?, +) { + @JvmName("otherName") + fun f() = Unit +} + +@[Ann Ann] +class C @Ann constructor( + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + @set:[Ann Ann] + @setparam:[Ann Ann] + var x: A, + @[Ann Ann] + @param:[Ann Ann] + @property:[Ann Ann] + @field:[Ann Ann] + @get:[Ann Ann] + @set:[Ann Ann] + @setparam:[Ann Ann] + var y: A?, +) { + @delegate:[Ann Ann] + val z by lazy { A(-100, -200) } + + @JvmField + val e = x + + init { + if (2 + 2 == 4) { + @[Ann Ann] + val x = 4 + @[Ann Ann] + val y = A(1, 2) + } + + + fun f() { + if (2 + 2 == 4) { + @[Ann Ann] + val x = 4 + @[Ann Ann] + val y = A(1, 2) + } + } + } +} + + +@[Ann Ann] +fun @receiver:[Ann Ann] A.t(@[Ann Ann] a: A, @[Ann Ann] b: B, @[Ann Ann] c: C) { + if (2 + 2 == 4) { + @[Ann Ann] + val x = 4 + @[Ann Ann] + val y = A(1, 2) + } + + fun f() { + if (2 + 2 == 4) { + @[Ann Ann] + val x1 = 4 + @[Ann Ann] + val y1 = A(1, 2) + } + } +} + +@[Ann Ann] +fun @receiver:[Ann Ann] C.t(@[Ann Ann] a: A, @[Ann Ann] b: B, @[Ann Ann] c: C) = 4 diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 92b8c600ea3..af225ec818b 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -33708,6 +33708,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/valueClasses"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); } + @Test + @TestMetadata("annotations.kt") + public void testAnnotations() throws Exception { + runTest("compiler/testData/diagnostics/tests/valueClasses/annotations.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithUniversal()); + } + @Test @TestMetadata("basicValueClassDeclaration.kt") public void testBasicValueClassDeclaration() throws Exception { diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/JvmFieldApplicabilityProblem.kt b/core/compiler.common.jvm/src/org/jetbrains/kotlin/JvmFieldApplicabilityProblem.kt index 7ea5913605a..1798c962975 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/JvmFieldApplicabilityProblem.kt +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/JvmFieldApplicabilityProblem.kt @@ -18,5 +18,5 @@ enum class JvmFieldApplicabilityProblem(val errorMessage: String) { NOT_PUBLIC_VAL_WITH_JVMFIELD("JvmField could be applied only if all interface companion properties are 'public final val' with '@JvmField' annotation"), TOP_LEVEL_PROPERTY_OF_MULTIFILE_FACADE("JvmField cannot be applied to top level property of a file annotated with ${JVM_MULTIFILE_CLASS_SHORT}"), DELEGATE("JvmField cannot be applied to delegated property"), - RETURN_TYPE_IS_INLINE_CLASS("JvmField cannot be applied to a property of an inline class type") + RETURN_TYPE_IS_VALUE_CLASS("JvmField cannot be applied to a property of a value class type"), } \ No newline at end of file diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt index faacfb692b6..40ca3fea147 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext.isNullableType val JVM_INLINE_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmInline") val JVM_INLINE_ANNOTATION_CLASS_ID = ClassId.topLevel(JVM_INLINE_ANNOTATION_FQ_NAME) @@ -42,6 +43,9 @@ fun KotlinType.unsubstitutedUnderlyingTypes(): List { fun KotlinType.isInlineClassType(): Boolean = constructor.declarationDescriptor?.isInlineClass() ?: false +fun KotlinType.needsMfvcFlattening(): Boolean = + constructor.declarationDescriptor?.run { isMultiFieldValueClass() && !isNullableType() } == true + fun KotlinType.substitutedUnderlyingType(): KotlinType? = unsubstitutedUnderlyingType()?.let { TypeSubstitutor.create(this).substitute(it, Variance.INVARIANT) } diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt index 0145c48a012..17aae6a5f9a 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt @@ -12,8 +12,7 @@ import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker -import org.jetbrains.kotlin.fir.analysis.checkers.isInlineClass -import org.jetbrains.kotlin.fir.analysis.checkers.outerClassSymbol +import org.jetbrains.kotlin.fir.analysis.checkers.isValueClass import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.* @@ -481,7 +480,7 @@ object FirSerializationPluginClassChecker : FirClassChecker() { context(CheckerContext) @Suppress("IncorrectFormatting") // KTIJ-22227 private val ConeKotlinType.isUnsupportedInlineType: Boolean - get() = isInlineClass(session) && !isPrimitiveOrNullablePrimitive + get() = isValueClass(session) && !isPrimitiveOrNullablePrimitive context(CheckerContext) @Suppress("IncorrectFormatting") // KTIJ-22227