in compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirValueClassDeclarationChecker.kt [62:271]
override fun check(declaration: FirRegularClass) {
if (!declaration.symbol.isInlineOrValueClass()) {
return
}
if (declaration.isInner || declaration.isLocal) {
reporter.reportOn(declaration.source, FirErrors.VALUE_CLASS_NOT_TOP_LEVEL)
}
if (declaration.modality != Modality.FINAL) {
reporter.reportOn(declaration.source, FirErrors.VALUE_CLASS_NOT_FINAL)
}
if (declaration.contextParameters.isNotEmpty() && LanguageFeature.ContextReceivers.isEnabled()) {
reporter.reportOn(declaration.source, FirErrors.VALUE_CLASS_CANNOT_HAVE_CONTEXT_RECEIVERS)
}
for (supertypeEntry in declaration.superTypeRefs) {
if (supertypeEntry is FirImplicitAnyTypeRef || supertypeEntry is FirErrorTypeRef) continue
if (supertypeEntry.toRegularClassSymbol(context.session)?.isInterface == true) continue
reporter.reportOn(supertypeEntry.source, FirErrors.VALUE_CLASS_CANNOT_EXTEND_CLASSES)
}
if (declaration.isSubtypeOfCloneable(context.session)) {
reporter.reportOn(declaration.source, FirErrors.VALUE_CLASS_CANNOT_BE_CLONEABLE)
}
var primaryConstructor: FirConstructorSymbol? = null
var primaryConstructorParametersByName = mapOf<Name, FirValueParameterSymbol>()
val primaryConstructorPropertiesByName = hashMapOf<Name, FirPropertySymbol>()
var primaryConstructorParametersSymbolsSet = setOf<FirValueParameterSymbol>()
val isCustomEqualsSupported = LanguageFeature.CustomEqualsInValueClasses.isEnabled()
declaration.constructors(context.session).forEach { innerDeclaration ->
when {
innerDeclaration.isPrimary -> {
primaryConstructor = innerDeclaration
primaryConstructorParametersByName = innerDeclaration.valueParameterSymbols.associateBy { it.name }
primaryConstructorParametersSymbolsSet = primaryConstructorParametersByName.values.toSet()
}
innerDeclaration.hasBody && !context.languageVersionSettings.supportsFeature(
LanguageFeature.ValueClassesSecondaryConstructorWithBody
) -> {
reporter.reportOn(
innerDeclaration.bodySource!!, FirErrors.SECONDARY_CONSTRUCTOR_WITH_BODY_INSIDE_VALUE_CLASS
)
}
}
}
declaration.processAllDeclarations(context.session) { innerDeclaration ->
when (innerDeclaration) {
is FirRegularClassSymbol -> {
if (innerDeclaration.isInner) {
reporter.reportOn(innerDeclaration.source, FirErrors.INNER_CLASS_INSIDE_VALUE_CLASS)
}
}
is FirPropertySymbol -> {
if (innerDeclaration.isRelatedToParameter(primaryConstructorParametersByName[innerDeclaration.name])) {
primaryConstructorPropertiesByName[innerDeclaration.name] = innerDeclaration
} else {
when {
innerDeclaration.delegate != null ->
reporter.reportOn(
innerDeclaration.delegate!!.source,
FirErrors.DELEGATED_PROPERTY_INSIDE_VALUE_CLASS
)
innerDeclaration.hasBackingField &&
innerDeclaration.source?.kind !is KtFakeSourceElementKind -> {
reporter.reportOn(
innerDeclaration.source, FirErrors.PROPERTY_WITH_BACKING_FIELD_INSIDE_VALUE_CLASS
)
}
}
}
}
else -> {}
}
}
// Separate handling of delegate fields
@OptIn(DirectDeclarationsAccess::class)
declaration.declarations.forEach { innerDeclaration ->
if (innerDeclaration !is FirField || !innerDeclaration.isSynthetic) return@forEach
val symbol = innerDeclaration.initializer?.toResolvedCallableSymbol(context.session)
if (symbol != null && symbol in primaryConstructorParametersSymbolsSet) {
return@forEach
}
val delegatedTypeRefSource = (innerDeclaration.returnTypeRef as FirResolvedTypeRef).delegatedTypeRef?.source
reporter.reportOn(
delegatedTypeRefSource,
FirErrors.VALUE_CLASS_CANNOT_IMPLEMENT_INTERFACE_BY_DELEGATION
)
}
val reservedNames = boxAndUnboxNames + if (isCustomEqualsSupported) emptySet() else equalsAndHashCodeNames
val classScope = declaration.unsubstitutedScope()
for (reservedName in reservedNames) {
classScope.processFunctionsByName(Name.identifier(reservedName)) {
val functionSymbol = it.unwrapFakeOverrides()
if (functionSymbol.isAbstract) return@processFunctionsByName
val containingClassSymbol = functionSymbol.getContainingClassSymbol() ?: return@processFunctionsByName
if (containingClassSymbol == declaration.symbol) {
if (functionSymbol.source?.kind is KtRealSourceElementKind) {
reporter.reportOn(
functionSymbol.source,
FirErrors.RESERVED_MEMBER_INSIDE_VALUE_CLASS,
reservedName
)
}
} else if (containingClassSymbol.classKind == ClassKind.INTERFACE) {
reporter.reportOn(
declaration.source,
FirErrors.RESERVED_MEMBER_FROM_INTERFACE_INSIDE_VALUE_CLASS,
containingClassSymbol.name.asString(),
reservedName
)
}
}
}
if (primaryConstructor?.source?.kind !is KtRealSourceElementKind) {
reporter.reportOn(declaration.source, FirErrors.ABSENCE_OF_PRIMARY_CONSTRUCTOR_FOR_VALUE_CLASS)
return
}
if (LanguageFeature.ValueClasses.isEnabled()) {
if (primaryConstructorParametersByName.isEmpty()) {
reporter.reportOn(primaryConstructor.source, FirErrors.VALUE_CLASS_EMPTY_CONSTRUCTOR)
return
}
} else if (primaryConstructorParametersByName.size != 1) {
reporter.reportOn(primaryConstructor.source, FirErrors.INLINE_CLASS_CONSTRUCTOR_WRONG_PARAMETERS_SIZE)
return
}
for ((name, primaryConstructorParameter) in primaryConstructorParametersByName) {
val parameterTypeRef = primaryConstructorParameter.resolvedReturnTypeRef
when {
primaryConstructorParameter.isNotFinalReadOnly(primaryConstructorPropertiesByName[name]) ->
reporter.reportOn(
primaryConstructorParameter.source,
FirErrors.VALUE_CLASS_CONSTRUCTOR_NOT_FINAL_READ_ONLY_PARAMETER
)
parameterTypeRef.isInapplicableParameterType(context.session) -> {
reporter.reportOn(
parameterTypeRef.source,
FirErrors.VALUE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE,
parameterTypeRef.coneType
)
}
parameterTypeRef.coneType.isRecursiveValueClassType(context.session) -> {
reporter.reportOn(
parameterTypeRef.source, FirErrors.VALUE_CLASS_CANNOT_BE_RECURSIVE
)
}
declaration.multiFieldValueClassRepresentation != null -> {
val defaultValue = primaryConstructorParameter.resolvedDefaultValue
if (defaultValue != null) {
// TODO, KT-50113: Fix when inline arguments are supported.
reporter.reportOn(
defaultValue.source,
FirErrors.MULTI_FIELD_VALUE_CLASS_PRIMARY_CONSTRUCTOR_DEFAULT_PARAMETER
)
}
}
}
}
if (isCustomEqualsSupported) {
val (equalsFromAnyOverriding, typedEquals) = run {
var equalsFromAnyOverriding: FirNamedFunctionSymbol? = null
var typedEquals: FirNamedFunctionSymbol? = null
declaration.processAllDeclarations(context.session) {
if (it !is FirNamedFunctionSymbol) {
return@processAllDeclarations
}
if (it.isEquals(context.session)) equalsFromAnyOverriding = it
if (it.isTypedEqualsInValueClass(context.session)) typedEquals = it
}
equalsFromAnyOverriding to typedEquals
}
if (typedEquals != null) {
if (typedEquals.typeParameterSymbols.isNotEmpty()) {
reporter.reportOn(
typedEquals.source,
FirErrors.TYPE_PARAMETERS_NOT_ALLOWED
)
}
val singleParameterReturnTypeRef = typedEquals.valueParameterSymbols.single().resolvedReturnTypeRef
if (singleParameterReturnTypeRef.coneType.typeArguments.any { !it.isStarProjection }) {
reporter.reportOn(singleParameterReturnTypeRef.source, FirErrors.TYPE_ARGUMENT_ON_TYPED_VALUE_CLASS_EQUALS)
}
}
if (equalsFromAnyOverriding != null && typedEquals == null) {
reporter.reportOn(
equalsFromAnyOverriding.source,
FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_VALUE_CLASS,
declaration.defaultType().replaceArgumentsWithStarProjections()
)
}
}
}