diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index c4d2946155463dcfde7a2ba8e4219d59b333469d..61e0db4a37ea9f2d22839b20290301d7a6c7adb4 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -1177,6 +1177,12 @@ static bool IsInvalidMethodAssignment(const ir::AssignmentExpression *const expr return false; } +static bool IsLazyImportObject(Type *type) +{ + return type->MaybeBaseTypeOfGradualType()->IsETSObjectType() && + type->MaybeBaseTypeOfGradualType()->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::LAZY_IMPORT_OBJECT); +} + checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *const expr) const { if (expr->TsType() != nullptr) { @@ -1211,7 +1217,10 @@ checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *const expr) const } if (expr->target_ != nullptr && !expr->IsIgnoreConstAssign()) { - checker->ValidateUnaryOperatorOperand(expr->target_); + auto originalVar = !expr->Left()->IsMemberExpression() + ? expr->Target() + : expr->Left()->AsMemberExpression()->PropVarOfPotentialOverridingField(); + checker->ValidateUnaryOperatorOperand(originalVar); } auto [rightType, relationNode] = CheckAssignmentExprOperatorType(expr, leftType); @@ -1222,9 +1231,7 @@ checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *const expr) const CastPossibleTupleOnRHS(checker, expr); checker::Type *smartType = rightType; - auto isLazyImportObject = - leftType->MaybeBaseTypeOfGradualType()->IsETSObjectType() && - leftType->MaybeBaseTypeOfGradualType()->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::LAZY_IMPORT_OBJECT); + auto isLazyImportObject = IsLazyImportObject(leftType); if (!leftType->IsTypeError() && !isLazyImportObject) { if (const auto ctx = checker::AssignmentContext(checker->Relation(), relationNode, rightType, leftType, expr->Right()->Start(), diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 62c5776c6b25bac3bda30ddbb34a35413a27a800..d102748bfc04f1128fcba33b21a44cf797537721 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -81,6 +81,8 @@ using MaybeDiagnosticInfo = std::optional>; using AstNodePtr = ir::AstNode *; using TypePtr = Type *; +using PropMap = ArenaUnorderedMap; +using ClassPropTypeInfo = std::pair; class ETSChecker final : public Checker { public: @@ -103,7 +105,8 @@ public: dynamicCallNames_ { {DynamicCallNamesMap(Allocator()->Adapter()), DynamicCallNamesMap(Allocator()->Adapter())}}, overloadSigContainer_(Allocator()->Adapter()), - readdedChecker_(Allocator()->Adapter()) + readdedChecker_(Allocator()->Adapter()), + classOwnedFields_(Allocator()->Adapter()) { } @@ -244,6 +247,8 @@ public: ETSObjectType *classType, const lexer::SourcePosition &pos, bool reportError); void ValidateOverriding(ETSObjectType *classType, const lexer::SourcePosition &pos); + void HandleFieldsOverriding(ETSObjectType *classType); + bool ValidateFieldsOverriding(varbinder::LocalVariable *baseVar, varbinder::LocalVariable *overridingVar); void CheckInterfaceFunctions(ETSObjectType *classType); void CollectImplementedMethodsFromInterfaces(ETSObjectType *classType, std::vector *implementedSignatures, @@ -728,9 +733,11 @@ public: ir::ClassProperty *ClassPropToImplementationProp(ir::ClassProperty *classProp, varbinder::ClassScope *scope); ir::Expression *GenerateImplicitInstantiateArg(const std::string &className); + ir::MemberExpression *GenerateGetterSetterMemberExpression(const ClassPropTypeInfo &info); void GenerateGetterSetterBody(ArenaVector &stmts, ArenaVector ¶ms, - ir::ClassProperty *field, varbinder::FunctionParamScope *paramScope, bool isSetter); - static ir::MethodDefinition *GenerateDefaultGetterSetter(ir::ClassProperty *property, ir::ClassProperty *field, + const ClassPropTypeInfo &info, varbinder::FunctionParamScope *paramScope, + bool isSetter); + static ir::MethodDefinition *GenerateDefaultGetterSetter(ir::ClassProperty *property, const ClassPropTypeInfo &info, varbinder::ClassScope *scope, bool isSetter, ETSChecker *checker); void GenerateGetterSetterPropertyAndMethod(ir::ClassProperty *originalProp, ETSObjectType *classType); @@ -956,6 +963,7 @@ public: } elementStack_.clear(); overloadSigContainer_.clear(); + classOwnedFields_.clear(); } // This helper finds the intersection of two callSignatures sets @@ -1112,6 +1120,7 @@ private: std::unordered_set elementStack_; ArenaVector overloadSigContainer_; ArenaSet readdedChecker_; + ArenaUnorderedMap classOwnedFields_; }; } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 6292556bb7b4a81cfed802bc9d081ac20481c1d8..dbe2a421ef1cd41e6de9563f1193944370da7c5b 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -2711,36 +2711,43 @@ ir::ClassProperty *ETSChecker::ClassPropToImplementationProp(ir::ClassProperty * return classProp; } -// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic -void ETSChecker::GenerateGetterSetterBody(ArenaVector &stmts, ArenaVector ¶ms, - ir::ClassProperty *const field, varbinder::FunctionParamScope *paramScope, - bool isSetter) +ir::MemberExpression *ETSChecker::GenerateGetterSetterMemberExpression(const ClassPropTypeInfo &info) { + const auto [field, instantiatedType] = info; auto *classDef = field->Parent()->AsClassDefinition(); ir::Expression *baseExpression; if ((field->Modifiers() & ir::ModifierFlags::SUPER_OWNER) != 0U) { // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - baseExpression = ProgramAllocator()->New(); + baseExpression = Allocator()->New(); + baseExpression->SetTsType(Context().ContainingClass()->AsETSObjectType()->SuperType()); } else { // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - baseExpression = ProgramAllocator()->New(); + baseExpression = Allocator()->New(); + baseExpression->SetTsType(classDef->TsType()); } - baseExpression->SetParent(classDef); - baseExpression->SetTsType(classDef->TsType()); auto *memberExpression = // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - ProgramAllocNode(baseExpression, - // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - field->Key()->AsIdentifier()->Clone(ProgramAllocator(), nullptr), - ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); - memberExpression->SetTsType(field->TsType()); + AllocNode(baseExpression, field->Key()->AsIdentifier()->Clone(Allocator(), nullptr), + ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); + + memberExpression->SetTsType(instantiatedType != nullptr ? instantiatedType : field->TsType()); memberExpression->SetPropVar(field->Key()->Variable()->AsLocalVariable()); memberExpression->SetRange(classDef->Range()); if (memberExpression->ObjType() == nullptr && classDef->TsType() != nullptr) { memberExpression->SetObjectType(classDef->TsType()->AsETSObjectType()); } + return memberExpression; +} + +void ETSChecker::GenerateGetterSetterBody(ArenaVector &stmts, ArenaVector ¶ms, + const ClassPropTypeInfo &info, varbinder::FunctionParamScope *paramScope, + bool isSetter) +{ + const auto [field, instantiatedType] = info; + // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) + auto memberExpression = GenerateGetterSetterMemberExpression(info); if (!isSetter) { // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) @@ -2749,8 +2756,10 @@ void ETSChecker::GenerateGetterSetterBody(ArenaVector &stmts, A } // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - auto *paramIdent = field->Key()->AsIdentifier()->Clone(ProgramAllocator(), nullptr); - if (field->TypeAnnotation() != nullptr) { + auto *paramIdent = field->Key()->AsIdentifier()->Clone(Allocator(), nullptr); + if (instantiatedType != nullptr) { + paramIdent->SetTsType(instantiatedType); + } else if (field->TypeAnnotation() != nullptr) { // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) auto *const typeAnnotation = field->TypeAnnotation()->Clone(ProgramAllocator(), paramIdent); paramIdent->SetTsTypeAnnotation(typeAnnotation); @@ -2803,9 +2812,10 @@ static ir::BlockStatement *GenGetterSetterBodyHelper(ETSChecker *checker, ArenaV // Need to avoid codecheck static std::tuple GenGetterSetterScriptFunc( - ir::ClassProperty *const property, ir::ClassProperty *const field, varbinder::ClassScope *classScope, bool isSetter, + ir::ClassProperty *const property, const ClassPropTypeInfo &info, varbinder::ClassScope *classScope, bool isSetter, ETSChecker *checker) { + const auto [field, instantiatedType] = info; auto *paramScope = checker->ProgramAllocator()->New(checker->ProgramAllocator(), classScope); auto *functionScope = @@ -2818,14 +2828,15 @@ static std::tuple params(checker->ProgramAllocator()->Adapter()); ArenaVector stmts(checker->ProgramAllocator()->Adapter()); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - checker->GenerateGetterSetterBody(stmts, params, field, paramScope, isSetter); + checker->GenerateGetterSetterBody(stmts, params, info, paramScope, isSetter); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) auto funcFlags = isSetter ? ir::ScriptFunctionFlags::SETTER : ir::ScriptFunctionFlags::GETTER | ir::ScriptFunctionFlags::HAS_RETURN; - auto *returnTypeAnn = isSetter || field->TypeAnnotation() == nullptr + auto *returnTypeAnn = isSetter || field->TypeAnnotation() == nullptr || instantiatedType != nullptr ? nullptr // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - : field->TypeAnnotation()->Clone(checker->ProgramAllocator(), nullptr); + : field->TypeAnnotation()->Clone(checker->Allocator(), nullptr); + ReInitScopesForTypeAnnotation(checker, returnTypeAnn, paramScope); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) ir::ModifierFlags modifierFlag = @@ -2844,13 +2855,14 @@ static std::tupleSetRange(field->Range()); func->SetScope(functionScope); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) @@ -2860,12 +2872,12 @@ ir::MethodDefinition *ETSChecker::GenerateDefaultGetterSetter(ir::ClassProperty funcExpr->SetRange(func->Range()); func->AddFlag(ir::ScriptFunctionFlags::METHOD); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - auto *method = checker->ProgramAllocNode( + auto *method = checker->AllocNode( isSetter ? ir::MethodDefinitionKind::SET : ir::MethodDefinitionKind::GET, methodIdent, funcExpr, modifierFlag, - checker->ProgramAllocator(), false); - auto *decl = checker->ProgramAllocator()->New( - checker->ProgramAllocator(), property->Key()->AsIdentifier()->Name(), method); - auto *var = checker->ProgramAllocator()->New(decl, varbinder::VariableFlags::VAR); + checker->Allocator(), false); + auto *decl = checker->Allocator()->New(checker->Allocator(), + property->Key()->AsIdentifier()->Name(), method); + auto *var = checker->Allocator()->New(decl, varbinder::VariableFlags::VAR); var->AddFlag(varbinder::VariableFlags::METHOD); methodIdent->SetVariable(var); @@ -2889,9 +2901,10 @@ ir::MethodDefinition *ETSChecker::GenerateDefaultGetterSetter(ir::ClassProperty return method; } -ir::ClassProperty *GetImplementationClassProp(ETSChecker *checker, ir::ClassProperty *interfaceProp, - ir::ClassProperty *originalProp, ETSObjectType *classType) +ClassPropTypeInfo GetImplementationClassProp(ETSChecker *checker, ir::ClassProperty *interfaceProp, + ir::ClassProperty *originalProp, ETSObjectType *classType) { + Type *instantiatedType = nullptr; bool isSuperOwner = ((originalProp->Modifiers() & ir::ModifierFlags::SUPER_OWNER) != 0U); if (!isSuperOwner) { auto *const classDef = classType->GetDeclNode()->AsClassDefinition(); @@ -2901,7 +2914,7 @@ ir::ClassProperty *GetImplementationClassProp(ETSChecker *checker, ir::ClassProp interfaceProp->Clone(checker->ProgramAllocator(), originalProp->Parent()), scope); classType->AddProperty(classProp->Key()->Variable()->AsLocalVariable()); classDef->EmplaceBody(classProp); - return classProp; + return {classProp, instantiatedType}; } auto *const classProp = classType @@ -2911,7 +2924,13 @@ ir::ClassProperty *GetImplementationClassProp(ETSChecker *checker, ir::ClassProp ->Node() ->AsClassProperty(); classProp->AddModifier(ir::ModifierFlags::SUPER_OWNER); - return classProp; + if (classProp->TsType()->IsETSTypeParameter()) { + instantiatedType = classType + ->GetProperty(interfaceProp->Key()->AsIdentifier()->Name(), + PropertySearchFlags::SEARCH_ALL | PropertySearchFlags::SEARCH_IN_BASE) + ->TsType(); + } + return {classProp, instantiatedType}; } void ETSChecker::SetupGetterSetterFlags(ir::ClassProperty *originalProp, ETSObjectType *classType, @@ -2965,19 +2984,20 @@ void ETSChecker::GenerateGetterSetterPropertyAndMethod(ir::ClassProperty *origin compiler::InitScopesPhaseETS::RunExternalNode(interfaceProp->Value(), VarBinder()); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - auto *const classProp = GetImplementationClassProp(this, interfaceProp, originalProp, classType); + auto [classProp, instantiatedType] = GetImplementationClassProp(this, interfaceProp, originalProp, classType); // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - ir::MethodDefinition *getter = GenerateDefaultGetterSetter(interfaceProp, classProp, scope, false, this); + ir::MethodDefinition *getter = + GenerateDefaultGetterSetter(interfaceProp, {classProp, instantiatedType}, scope, false, this); classDef->EmplaceBody(getter); const auto &name = getter->Key()->AsIdentifier()->Name(); - ir::MethodDefinition *setter = - !classProp->IsConst() - // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) - ? GenerateDefaultGetterSetter(interfaceProp, classProp, Scope()->AsClassScope(), true, this) - : nullptr; + ir::MethodDefinition *setter = !classProp->IsConst() + // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) + ? GenerateDefaultGetterSetter(interfaceProp, {classProp, instantiatedType}, + Scope()->AsClassScope(), true, this) + : nullptr; auto *const methodScope = scope->InstanceMethodScope(); auto *const decl = ProgramAllocator()->New(ProgramAllocator(), name, getter); diff --git a/ets2panda/checker/ets/object.cpp b/ets2panda/checker/ets/object.cpp index 7cfe16d58eb609267a1f9c2a2877defcbd7daa76..890fb404245ba2bf8ad8b28ef0efceb107849583 100644 --- a/ets2panda/checker/ets/object.cpp +++ b/ets2panda/checker/ets/object.cpp @@ -21,6 +21,7 @@ #include "checker/types/ets/etsPartialTypeParameter.h" #include "checker/types/gradualType.h" #include "compiler/lowering/phase.h" +#include "ir/astDump.h" #include "ir/base/classDefinition.h" #include "ir/base/classElement.h" #include "ir/base/classProperty.h" @@ -1283,6 +1284,7 @@ void ETSChecker::CheckClassDefinition(ir::ClassDefinition *classDef) AddStatus(checker::CheckerStatus::IN_STATIC_CONTEXT); } + HandleFieldsOverriding(classType); // NOTE(gogabr): temporary, until we have proper bridges, see #16485 // Don't check overriding for synthetic functional classes. if ((static_cast(classDef)->Modifiers() & ir::ModifierFlags::FUNCTIONAL) == 0) { @@ -1311,6 +1313,77 @@ void ETSChecker::CheckClassDefinition(ir::ClassDefinition *classDef) CheckTypeParameterVariance(classDef); } +void ETSChecker::HandleFieldsOverriding(ETSObjectType *classType) +{ + if (classType->FieldsOverridingChecked()) { + return; + } + + PropMap ownedFields(Allocator()->Adapter()); + if (classType->SuperType() != nullptr) { + HandleFieldsOverriding(classType->SuperType()); + ownedFields = classOwnedFields_.at(classType->SuperType()); + } + + for (auto [fieldName, variable] : classType->InstanceFields()) { + if (ownedFields.find(fieldName) == ownedFields.end()) { + ownedFields.insert({fieldName, variable}); + } else { + ES2PANDA_ASSERT((variable->Declaration()->Node()->IsClassProperty())); + ES2PANDA_ASSERT(ownedFields.at(fieldName)->Declaration()->Node()->IsClassProperty()); + if (ValidateFieldsOverriding(ownedFields.at(fieldName), variable)) { + classType->RemoveProperty(variable); + classType->GetDeclNode()->Scope()->AsClassScope()->InstanceFieldScope()->EraseBinding(fieldName); + classType->AddOverridingField(fieldName, variable); + } + } + } + + classOwnedFields_.emplace(classType, std::move(ownedFields)); + classType->SetFieldsOverridingChecked(); +} + +bool ETSChecker::ValidateFieldsOverriding(varbinder::LocalVariable *baseVar, varbinder::LocalVariable *overridingVar) +{ + bool hasError = false; + GetTypeOfVariable(baseVar); + GetTypeOfVariable(overridingVar); + auto baseFieldType = baseVar->TsType(); + auto fieldType = overridingVar->TsType(); + ES2PANDA_ASSERT(baseFieldType != nullptr); + ES2PANDA_ASSERT(fieldType != nullptr); + auto fieldName = overridingVar->Name(); + auto baseClassDefName = baseVar->Declaration()->Node()->Parent()->AsClassDefinition()->Ident()->Name(); + auto classDef = overridingVar->Declaration()->Node()->Parent()->AsClassDefinition(); + auto classDefName = classDef->Ident()->Name(); + auto superType = classDef->TsType()->AsETSObjectType()->SuperType(); + auto pos = overridingVar->Declaration()->Node()->Start(); + if (!Relation()->IsIdenticalTo(baseFieldType, fieldType)) { + LogError(diagnostic::FIELD_OVERRIDING_MISMATCHED_TYPE, {fieldName, classDefName, baseClassDefName}, pos); + hasError = true; + } + + if (!baseVar->Declaration()->IsReadonlyDecl() && overridingVar->Declaration()->IsReadonlyDecl()) { + LogError(diagnostic::INHERITED_CLASS_TYPE_MISMATCH, {superType->Name(), "field", fieldName}, + classDef->Super()->Start()); + hasError = true; + } + + auto baseFieldAccess = baseVar->Declaration()->Node()->Modifiers(); + auto fieldAccess = overridingVar->Declaration()->Node()->Modifiers(); + auto dumpFlag = ir::AstDumper::ModifierToString; + if (((baseFieldAccess & ir::ModifierFlags::PRIVATE) != 0) && ((fieldAccess & ir::ModifierFlags::PRIVATE) != 0)) { + LogError(diagnostic::PRIVATE_FIELD_OVERRIDING, {classDefName, baseClassDefName, fieldName}, pos); + } else if (((baseFieldAccess & fieldAccess)) == 0 && !(((baseFieldAccess & ir::ModifierFlags::PROTECTED) != 0) && + ((fieldAccess & ir::ModifierFlags::PUBLIC) != 0))) { + LogError(diagnostic::FIELD_OVERRIDING_MISMATCHED_ACCESS, + {fieldName, dumpFlag(fieldAccess), classDefName, dumpFlag(baseFieldAccess), baseClassDefName}, pos); + hasError = true; + } + + return !hasError; +} + void ETSChecker::CheckClassElement(ir::ClassDefinition *classDef) { for (auto *it : classDef->Body()) { @@ -2338,7 +2411,11 @@ void ETSChecker::CheckValidInheritance(ETSObjectType *classType, ir::ClassDefini return; } - const auto &allProps = classType->GetAllProperties(); + const auto &props = classType->GetAllProperties(); + const auto &inheritedProps = classType->InheritedProperties(); + std::vector allProps(props.size() + inheritedProps.size()); + std::copy(props.begin(), props.end(), allProps.begin()); + std::copy(inheritedProps.begin(), inheritedProps.end(), allProps.begin() + props.size()); for (auto *it : allProps) { const auto searchFlag = PropertySearchFlags::SEARCH_ALL | PropertySearchFlags::SEARCH_IN_BASE | @@ -2373,6 +2450,10 @@ void ETSChecker::CheckValidInheritance(ETSObjectType *classType, ir::ClassDefini void ETSChecker::CheckProperties(ETSObjectType *classType, ir::ClassDefinition *classDef, varbinder::LocalVariable *it, varbinder::LocalVariable *found, ETSObjectType *interfaceFound) { + if (it == found) { + return; + } + if (found->TsType() == nullptr) { GetTypeOfVariable(found); } diff --git a/ets2panda/checker/types/ets/etsObjectType.cpp b/ets2panda/checker/types/ets/etsObjectType.cpp index e7d2fb4229839b42e0e1cd6d17c135906d3be3b4..b118cc50418a37103aaa8de2a35ed17a60fa9871 100644 --- a/ets2panda/checker/types/ets/etsObjectType.cpp +++ b/ets2panda/checker/types/ets/etsObjectType.cpp @@ -60,7 +60,18 @@ void ETSObjectType::CacheSupertypeTransitive(ETSObjectType *type) } } -varbinder::LocalVariable *ETSObjectType::SearchFieldsDecls(util::StringView name, PropertySearchFlags flags) const +void ETSObjectType::IterateSuperOnly(const PropertyTraverser &cb) const +{ + for (auto *prop : GetInstanceProperties()) { + cb(prop); + } + + if (superType_ != nullptr) { + superType_->IterateSuperOnly(cb); + } +} + +varbinder::LocalVariable *ETSObjectType::SearchFieldsDecls(const util::StringView name, PropertySearchFlags flags) const { varbinder::LocalVariable *res {}; if ((flags & PropertySearchFlags::SEARCH_INSTANCE_FIELD) != 0) { @@ -464,6 +475,27 @@ std::vector ETSObjectType::Overloads() const return methods; } +std::vector ETSObjectType::GetInstanceProperties() const +{ + std::vector instanceProperties; + for (const auto &[_, prop] : InstanceFields()) { + (void)_; + instanceProperties.push_back(prop); + } + + for (const auto &[_, prop] : InstanceMethods()) { + (void)_; + instanceProperties.push_back(prop); + } + + for (const auto &[_, prop] : InstanceDecls()) { + (void)_; + instanceProperties.push_back(prop); + } + + return instanceProperties; +} + std::vector ETSObjectType::Methods() const { std::vector methods; @@ -536,6 +568,32 @@ std::vector ETSObjectType::ForeignProperties() return foreignProps; } +std::vector ETSObjectType::InheritedProperties() const +{ + std::vector inheritedProps; + std::unordered_set ownProps; + + EnsurePropertiesInstantiated(); + ownProps.reserve(properties_.size()); + + for (const auto *prop : GetInstanceProperties()) { + ownProps.insert(prop); + } + + std::unordered_set allProps {}; + IterateSuperOnly([&allProps](const varbinder::LocalVariable *var) { + allProps.emplace(const_cast(var)); + }); + + for (const auto &var : allProps) { + if (ownProps.find(var) == ownProps.end()) { + inheritedProps.push_back(var); + } + } + + return inheritedProps; +} + void ETSObjectType::ToString(std::stringstream &ss, bool precise) const { if (IsPartial()) { diff --git a/ets2panda/checker/types/ets/etsObjectType.h b/ets2panda/checker/types/ets/etsObjectType.h index 2f81c4be7c98481b663be75eb3c0a94a6bb28383..1019391cb94a50da32e48b674bf7ada9a57dfa6e 100644 --- a/ets2panda/checker/types/ets/etsObjectType.h +++ b/ets2panda/checker/types/ets/etsObjectType.h @@ -335,8 +335,30 @@ public: return name_.EndsWith(PARTIAL_CLASS_SUFFIX); } + const PropertyMap &OverridingFields() const + { + return overridingFields_; + } + + void AddOverridingField(util::StringView propName, varbinder::LocalVariable *propVar) + { + overridingFields_.emplace(propName, propVar); + } + + bool FieldsOverridingChecked() + { + return fieldsOverridingChecked_; + } + + void SetFieldsOverridingChecked() + { + fieldsOverridingChecked_ = true; + } + std::vector ForeignProperties() const; - varbinder::LocalVariable *GetProperty(util::StringView name, PropertySearchFlags flags) const; + std::vector InheritedProperties() const; + varbinder::LocalVariable *GetProperty(const util::StringView name, PropertySearchFlags flags) const; + std::vector GetInstanceProperties() const; std::vector GetAllProperties() const; void ForEachAllOwnProperties(const PropertyTraverser &cb) const; void ForEachAllNonOwnProperties(const PropertyTraverser &cb) const; @@ -362,6 +384,7 @@ public: bool CheckIdenticalFlags(ETSObjectType *other) const; ETSObjectType *CreateETSObjectType(ir::AstNode *declNode, ETSObjectFlags flags); void Iterate(const PropertyTraverser &cb) const; + void IterateSuperOnly(const PropertyTraverser &cb) const; void ToString(std::stringstream &ss, bool precise) const override; void Identical(TypeRelation *relation, Type *other) override; bool AssignmentSource(TypeRelation *relation, Type *target) override; @@ -424,7 +447,8 @@ private: transitiveSupertypes_(allocator->Adapter()), relation_(std::get(info)), constructSignatures_(allocator->Adapter()), - properties_ {(void(IS), PropertyMap {allocator->Adapter()})...} + properties_ {(void(IS), PropertyMap {allocator->Adapter()})...}, + overridingFields_(allocator_->Adapter()) { } @@ -481,6 +505,8 @@ private: mutable bool propertiesInstantiated_ = false; mutable ArenaVector constructSignatures_; mutable PropertyHolder properties_; + PropertyMap overridingFields_; + bool fieldsOverridingChecked_ {false}; }; } // namespace ark::es2panda::checker diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index 8992e4463266032d54ade146aee52b4d6f28f6a7..5f86c39610f2434a7ff980ca6dfcfca48e88ac1f 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -66,7 +66,28 @@ void ETSCompiler::Compile(const ir::ClassProperty *st) const if (st->IsStatic()) { etsg->StoreStaticOwnProperty(st, st->TsType(), st->Key()->AsIdentifier()->Name()); } else { - etsg->StoreProperty(st, st->TsType(), etsg->GetThisReg(), st->Key()->AsIdentifier()->Name()); + const varbinder::LocalVariable *propVar = nullptr; + auto *objType = st->Parent()->AsClassDefinition()->TsType()->AsETSObjectType(); + bool found = false; + objType->IterateSuperOnly([&propVar, &found, st](const varbinder::LocalVariable *localVar) -> void { + if (found) { + return; + } + + if (localVar->Name() == st->Key()->AsIdentifier()->Name()) { + propVar = localVar; + found = true; + } + }); + + if (found && propVar->TsType() != nullptr && propVar->TsType()->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) { + auto argReg = etsg->AllocReg(); + etsg->StoreAccumulator(st, argReg); + auto sig = propVar->TsType()->AsETSFunctionType()->FindSetter(); + etsg->CallVirtual(st, sig, etsg->GetThisReg(), argReg); + } else { + etsg->StoreProperty(st, st->TsType(), etsg->GetThisReg(), st->Key()->AsIdentifier()->Name()); + } } } diff --git a/ets2panda/compiler/core/ETSemitter.cpp b/ets2panda/compiler/core/ETSemitter.cpp index baf96ce455f3357b646fbe237b4ec89295069762..58361fa800c604c1e8a7d102088f2ae3b463faaf 100644 --- a/ets2panda/compiler/core/ETSemitter.cpp +++ b/ets2panda/compiler/core/ETSemitter.cpp @@ -619,6 +619,12 @@ void ETSEmitter::GenClassRecord(const ir::ClassDefinition *classDef, bool extern continue; } + const auto &overridingFields = baseType->OverridingFields(); + if (!prop->IsStatic() && + overridingFields.find(prop->AsClassProperty()->Id()->Name()) != overridingFields.end()) { + continue; + } + GenClassField(prop->AsClassProperty(), classRecord, external); } diff --git a/ets2panda/compiler/core/ETSfunction.cpp b/ets2panda/compiler/core/ETSfunction.cpp index 672f29e5f6d859490a0ed258dfe6b640d5853390..277d503b8912336e0c4937ebc29b16e1efe4399d 100644 --- a/ets2panda/compiler/core/ETSfunction.cpp +++ b/ets2panda/compiler/core/ETSfunction.cpp @@ -65,13 +65,14 @@ void ETSFunction::CallImplicitCtor(ETSGen *etsg) void ETSFunction::CompileSourceBlock(ETSGen *etsg, const ir::BlockStatement *block) { auto *scriptFunc = etsg->RootNode()->AsScriptFunction(); + bool isSuperCallEmitted = false; if (scriptFunc->IsEnum()) { // NOTE: add enum methods } else if (scriptFunc->IsStaticBlock()) { CompileAsStaticBlock(etsg); } else if (scriptFunc->IsConstructor()) { - CompileAsConstructor(etsg, scriptFunc); + CompileAsConstructor(etsg, scriptFunc, isSuperCallEmitted); } const auto &statements = block->Statements(); @@ -84,7 +85,13 @@ void ETSFunction::CompileSourceBlock(ETSGen *etsg, const ir::BlockStatement *blo etsg->SetFirstStmt(statements.front()); - etsg->CompileStatements(statements); + if (isSuperCallEmitted) { + for (size_t i = 1; i < statements.size(); i++) { + statements[i]->Compile(etsg); + } + } else { + etsg->CompileStatements(statements); + } if (!statements.back()->IsReturnStatement()) { ExtendWithDefaultReturn(etsg, statements.back(), scriptFunc); @@ -138,10 +145,33 @@ void ETSFunction::CompileAsStaticBlock(ETSGen *etsg) } } -void ETSFunction::CompileAsConstructor(ETSGen *etsg, const ir::ScriptFunction *scriptFunc) +void ETSFunction::CompileAsConstructor(ETSGen *etsg, const ir::ScriptFunction *scriptFunc, bool &isSuperCallEmitted) { + // CC-OFFNXT(G.FMT.14-CPP) project code style + const auto getSuperCall = [](const ir::BlockStatement *blockStmt) -> ir::AstNode * { + if (blockStmt->Statements().empty()) { + return nullptr; + } + + // super call must be first statement in constructor + auto firstStmt = blockStmt->Statements()[0]; + if (!firstStmt->IsExpressionStatement()) { + return nullptr; + } + + auto expr = firstStmt->AsExpressionStatement()->GetExpression(); + if (expr->IsCallExpression() && expr->AsCallExpression()->Callee()->IsSuperExpression()) { + return expr; + } + + return nullptr; + }; + if (scriptFunc->IsImplicitSuperCallNeeded()) { CallImplicitCtor(etsg); + } else if (auto superCall = getSuperCall(scriptFunc->Body()->AsBlockStatement()); superCall != nullptr) { + superCall->Compile(etsg); + isSuperCallEmitted = true; } const auto *classDef = etsg->ContainingObjectType()->GetDeclNode()->AsClassDefinition(); diff --git a/ets2panda/compiler/core/ETSfunction.h b/ets2panda/compiler/core/ETSfunction.h index 7251ed9f34d9d857698b1a3fdb9501eba3c9f774..e59304798dac2b8955d3bd3f753447f03460288d 100644 --- a/ets2panda/compiler/core/ETSfunction.h +++ b/ets2panda/compiler/core/ETSfunction.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - 2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -37,7 +37,7 @@ private: const ir::TSEnumMember *enumMember, int32_t index); static void CompileSourceBlock(ETSGen *etsg, const ir::BlockStatement *block); static void CompileAsStaticBlock(ETSGen *etsg); - static void CompileAsConstructor(ETSGen *etsg, const ir::ScriptFunction *scriptFunc); + static void CompileAsConstructor(ETSGen *etsg, const ir::ScriptFunction *scriptFunc, bool &isSuperCallEmitted); static void CompileFunction(ETSGen *etsg); static void CallImplicitCtor(ETSGen *etsg); static void ExtendWithDefaultReturn(ETSGen *etsg, const ir::AstNode *node, const ir::ScriptFunction *scriptFunc); diff --git a/ets2panda/ir/expressions/memberExpression.cpp b/ets2panda/ir/expressions/memberExpression.cpp index ae65ae697c7eeb94628ab04a06dbdcbfee54dbff..952a491d1544b33c9c06b0c3c87651f92d7c54c0 100644 --- a/ets2panda/ir/expressions/memberExpression.cpp +++ b/ets2panda/ir/expressions/memberExpression.cpp @@ -150,6 +150,15 @@ checker::Type *MemberExpression::Check(checker::TSChecker *checker) return checker->GetAnalyzer()->Check(this); } +varbinder::LocalVariable *MemberExpression::PropVarOfPotentialOverridingField() +{ + if (objType_->OverridingFields().find(property_->AsIdentifier()->Name()) != objType_->OverridingFields().end()) { + return objType_->OverridingFields().at(property_->AsIdentifier()->Name()); + } + + return PropVar(); +} + std::pair MemberExpression::ResolveObjectMember( checker::ETSChecker *checker) const { diff --git a/ets2panda/ir/expressions/memberExpression.h b/ets2panda/ir/expressions/memberExpression.h index 4c72e652feabf12e3d22dd1f36bbfe9ed09b86f7..c3fc6caad601c1806ca2803c3d016198849a8248 100644 --- a/ets2panda/ir/expressions/memberExpression.h +++ b/ets2panda/ir/expressions/memberExpression.h @@ -132,6 +132,8 @@ public: return Property()->Variable() != nullptr ? Property()->Variable()->AsLocalVariable() : nullptr; } + [[nodiscard]] varbinder::LocalVariable *PropVarOfPotentialOverridingField(); + [[nodiscard]] bool IsComputed() const noexcept { return computed_; diff --git a/ets2panda/test/ast/parser/ets/getter_setter_access_modifiers_2.ets b/ets2panda/test/ast/parser/ets/getter_setter_access_modifiers_2.ets index ba846261acb7657783e6b5a2f7dd6235c0baff0e..d4c2b753486de5626d6486ab59012512d8779258 100644 --- a/ets2panda/test/ast/parser/ets/getter_setter_access_modifiers_2.ets +++ b/ets2panda/test/ast/parser/ets/getter_setter_access_modifiers_2.ets @@ -14,8 +14,8 @@ */ class Core { - private _size: int; - private _width: int; + protected _size: int; + protected _width: int; final get size(): int { return this._size; @@ -27,8 +27,8 @@ class Core { } class Hex extends Core { - private _size: int; - private _width: int; + protected _size: int; + protected _width: int; override set size(s: int) { this._size = s; diff --git a/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt b/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt index 3e6c99e7bee3bbf28cf1d1c662c8b4fcde7cd010..767ad86d328299df7555e9c24f78e0968332d2dc 100644 --- a/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt +++ b/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt @@ -1033,3 +1033,4 @@ } } } +TypeError: Field 'a' in derived class 'B' cannot override that in base class 'A' with different types. [invalidInheritance3.ets:21:5] diff --git a/ets2panda/test/parser/ets/getter_setter_access_modifiers-expected.txt b/ets2panda/test/parser/ets/getter_setter_access_modifiers-expected.txt index 64641f90efc5dd24658a2bb6c4c446fe580adc9d..fbb85cf171da6c6814016f1560a736941a54de26 100644 --- a/ets2panda/test/parser/ets/getter_setter_access_modifiers-expected.txt +++ b/ets2panda/test/parser/ets/getter_setter_access_modifiers-expected.txt @@ -33,17 +33,17 @@ "loc": { "start": { "line": 17, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 17, - "column": 17, + "column": 19, "program": "getter_setter_access_modifiers.ets" } } }, - "accessibility": "private", + "accessibility": "protected", "static": false, "readonly": false, "declare": false, @@ -54,12 +54,12 @@ "loc": { "start": { "line": 17, - "column": 19, + "column": 21, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 17, - "column": 22, + "column": 24, "program": "getter_setter_access_modifiers.ets" } } @@ -69,12 +69,12 @@ "loc": { "start": { "line": 17, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 17, - "column": 22, + "column": 24, "program": "getter_setter_access_modifiers.ets" } } @@ -88,17 +88,17 @@ "loc": { "start": { "line": 18, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 18, - "column": 19, + "column": 21, "program": "getter_setter_access_modifiers.ets" } } }, - "accessibility": "private", + "accessibility": "protected", "static": false, "readonly": false, "declare": false, @@ -109,12 +109,12 @@ "loc": { "start": { "line": 18, - "column": 21, + "column": 23, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 18, - "column": 24, + "column": 26, "program": "getter_setter_access_modifiers.ets" } } @@ -124,12 +124,12 @@ "loc": { "start": { "line": 18, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 18, - "column": 24, + "column": 26, "program": "getter_setter_access_modifiers.ets" } } @@ -832,17 +832,17 @@ "loc": { "start": { "line": 27, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 27, - "column": 17, + "column": 19, "program": "getter_setter_access_modifiers.ets" } } }, - "accessibility": "private", + "accessibility": "protected", "static": false, "readonly": false, "declare": false, @@ -853,12 +853,12 @@ "loc": { "start": { "line": 27, - "column": 19, + "column": 21, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 27, - "column": 22, + "column": 24, "program": "getter_setter_access_modifiers.ets" } } @@ -868,12 +868,12 @@ "loc": { "start": { "line": 27, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 27, - "column": 22, + "column": 24, "program": "getter_setter_access_modifiers.ets" } } @@ -887,17 +887,17 @@ "loc": { "start": { "line": 28, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 28, - "column": 19, + "column": 21, "program": "getter_setter_access_modifiers.ets" } } }, - "accessibility": "private", + "accessibility": "protected", "static": false, "readonly": false, "declare": false, @@ -908,12 +908,12 @@ "loc": { "start": { "line": 28, - "column": 21, + "column": 23, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 28, - "column": 24, + "column": 26, "program": "getter_setter_access_modifiers.ets" } } @@ -923,12 +923,12 @@ "loc": { "start": { "line": 28, - "column": 13, + "column": 15, "program": "getter_setter_access_modifiers.ets" }, "end": { "line": 28, - "column": 24, + "column": 26, "program": "getter_setter_access_modifiers.ets" } } diff --git a/ets2panda/test/parser/ets/getter_setter_access_modifiers.ets b/ets2panda/test/parser/ets/getter_setter_access_modifiers.ets index 2fce20db8684902ea22c0731a9e77357a393c54b..2c2118bb41c7253bab77bcf08af00f3753196994 100644 --- a/ets2panda/test/parser/ets/getter_setter_access_modifiers.ets +++ b/ets2panda/test/parser/ets/getter_setter_access_modifiers.ets @@ -14,8 +14,8 @@ */ abstract class AbstractClass { - private _age: int; - private _value: int; + protected _age: int; + protected _value: int; abstract get age(): int; abstract set age(a: int); @@ -24,8 +24,8 @@ abstract class AbstractClass { } class ChildClass extends AbstractClass { - private _age: int; - private _value: int; + protected _age: int; + protected _value: int; override get age(): int { diff --git a/ets2panda/test/runtime/ets/CastReference3.ets b/ets2panda/test/runtime/ets/CastReference3.ets index e4c0300b0cd970eeb772cb945c06d95a993a7377..ee4ed419ae08aa864dad1e9198385fbeb0d9ec36 100644 --- a/ets2panda/test/runtime/ets/CastReference3.ets +++ b/ets2panda/test/runtime/ets/CastReference3.ets @@ -37,7 +37,7 @@ function main() { // Accumulator type 'C[]' is always a subtype of 'FixedArray'. Checkcast is redundant here. // It may be a sign of possible error here. let Bs: FixedArray = As as FixedArray; - arktest.assertEQ(Bs[0].name, c'B') + arktest.assertEQ(Bs[0].name, c'C') } { diff --git a/ets2panda/test/runtime/ets/ClassMemberAccess.ets b/ets2panda/test/runtime/ets/ClassMemberAccess.ets index 81a46d44059dae69f740ad4d933daf15511dedd3..87e6fad2e7c006ba78a380b7d048042820e83ded 100644 --- a/ets2panda/test/runtime/ets/ClassMemberAccess.ets +++ b/ets2panda/test/runtime/ets/ClassMemberAccess.ets @@ -80,15 +80,15 @@ function main() : void { arktest.assertEQ(B.get_static_name(), c'B') arktest.assertEQ(b.name, c'B') arktest.assertEQ(b.get_name(), c'B') - arktest.assertEQ(b.super_name(), c'A') - arktest.assertEQ(b.get_name_a(), c'A') + arktest.assertEQ(b.super_name(), c'B') + arktest.assertEQ(b.get_name_a(), c'B') } { let b_as_a: A = new B(); arktest.assertEQ(b_as_a.name, c'B') arktest.assertEQ(b_as_a.get_name(), c'B') - arktest.assertEQ(b_as_a.get_name_a(), c'A') + arktest.assertEQ(b_as_a.get_name_a(), c'B') } { @@ -97,36 +97,36 @@ function main() : void { arktest.assertEQ(C.get_static_name(), c'C') arktest.assertEQ(c.name, c'C') arktest.assertEQ(c.get_name(), c'C') - arktest.assertEQ(c.super_name(), c'B') - arktest.assertEQ(c.get_name_a(), c'A') + arktest.assertEQ(c.super_name(), c'C') + arktest.assertEQ(c.get_name_a(), c'C') } { let c_as_a: A = new C(); arktest.assertEQ(c_as_a.name, c'C') arktest.assertEQ(c_as_a.get_name(), c'C') - arktest.assertEQ(c_as_a.get_name_a(), c'A') + arktest.assertEQ(c_as_a.get_name_a(), c'C') } { let c_as_b: B = new C(); arktest.assertEQ(c_as_b.name, c'C') arktest.assertEQ(c_as_b.get_name(), c'C') - arktest.assertEQ(c_as_b.super_name(), c'B') - arktest.assertEQ(c_as_b.get_name_a(), c'A') + arktest.assertEQ(c_as_b.super_name(), c'C') + arktest.assertEQ(c_as_b.get_name_a(), c'C') } { let c = new C(); - arktest.assertEQ((c as A).name, c'A') + arktest.assertEQ((c as A).name, c'C') arktest.assertEQ((c as A).get_name(), c'C') - arktest.assertEQ((c as A).get_name_a(), c'A') + arktest.assertEQ((c as A).get_name_a(), c'C') } { - arktest.assertEQ((new C() as B).name, c'B') - arktest.assertTrue((new C() as B).get_name() ==c'C') - arktest.assertEQ((new C() as B).super_name(), c'B') - arktest.assertEQ((new C() as B).get_name_a(), c'A') + arktest.assertEQ((new C() as B).name, c'C') + arktest.assertTrue((new C() as B).get_name() == c'C') + arktest.assertEQ((new C() as B).super_name(), c'C') + arktest.assertEQ((new C() as B).get_name_a(), c'C') } } diff --git a/ets2panda/test/runtime/ets/class-abstract-inheritance.ets b/ets2panda/test/runtime/ets/class-abstract-inheritance.ets index 75b47720d19a8d68f26352cdf3d4c312dbe3cbdc..3101321ea56eb1848f435eb72273e6489830e885 100644 --- a/ets2panda/test/runtime/ets/class-abstract-inheritance.ets +++ b/ets2panda/test/runtime/ets/class-abstract-inheritance.ets @@ -60,7 +60,7 @@ function main(): void { arktest.assertEQ(b.get_super_str(), "A") arktest.assertEQ(c.str, "C") arktest.assertEQ(c.get_str(), "C") - arktest.assertEQ(c.get_super_str(), "A") + arktest.assertEQ(c.get_super_str(), "C") arktest.assertEQ((b as B).str, "A") arktest.assertEQ((b as A).str, "A") @@ -68,11 +68,11 @@ function main(): void { arktest.assertEQ((b as B).get_super_str(), "A") arktest.assertEQ((c as C).str, "C") - arktest.assertEQ((c as B).str, "A") - arktest.assertEQ((c as A).str, "A") + arktest.assertEQ((c as B).str, "C") + arktest.assertEQ((c as A).str, "C") arktest.assertEQ((c as C).get_str(), "C") - arktest.assertEQ((c as C).get_super_str(), "A") + arktest.assertEQ((c as C).get_super_str(), "C") arktest.assertEQ((c as B).get_str(), "C") - arktest.assertEQ((c as B).get_super_str(), "A") + arktest.assertEQ((c as B).get_super_str(), "C") } diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index 8de38bb1efa0aa9ead9fd8b0d3f0eaaa9d20c88e..cb3a673bb84757ba152f73f3e4587995a0df4e08 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -1567,3 +1567,15 @@ semantic: - name: INVALID_LAMBDA_PARAMETER id: 395 message: "Invalid lambda parameter. Expected: 'identifier(: type)?', 'identifier?(: type)?' or '...identifier(: type)?'." + +- name: FIELD_OVERRIDING_MISMATCHED_TYPE + id: 396 + message: "Field '{}' in derived class '{}' cannot override that in base class '{}' with different types." + +- name: PRIVATE_FIELD_OVERRIDING + id: 397 + message: "Derived class '{}' and base class '{}' have separate declarations of a private property '{}'." + +- name: FIELD_OVERRIDING_MISMATCHED_ACCESS + id: 398 + message: "Field '{}' is {} in derived class '{}' but {} in base class '{}'."