diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index e9c4dee5ec57e0abaae65fc96db6e672de60da28..4e19ba4611eedc19115328d7cac1ab19c24033a0 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -228,6 +228,7 @@ libes2panda_sources = [ "compiler/lowering/ets/exportAnonymousConst.cpp", "compiler/lowering/ets/expressionLambdaLowering.cpp", "compiler/lowering/ets/extensionAccessorLowering.cpp", + "compiler/lowering/ets/fieldOverridingLowering.cpp", "compiler/lowering/ets/genericBridgesLowering.cpp", "compiler/lowering/ets/gradualTypeNarrowing.cpp", "compiler/lowering/ets/insertOptionalParametersAnnotation.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 1f0676370bc241208d064691145f51d9051aded0..b3cca1dbbeb391a17a8812e9b8a02615286c4a0d 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -320,6 +320,7 @@ set(ES2PANDA_LIB_SRC compiler/lowering/ets/stringConstructorLowering.cpp compiler/lowering/ets/typeFromLowering.cpp compiler/lowering/ets/enumLowering.cpp + compiler/lowering/ets/fieldOverridingLowering.cpp compiler/lowering/ets/enumPostCheckLowering.cpp compiler/lowering/ets/enumPropertiesInAnnotationsLowering.cpp compiler/lowering/ets/setJumpTarget.cpp diff --git a/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e579b18e8c5bf9bab47f2376b452c0c889827e85 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (c) 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fieldOverridingLowering.h" +#include "ir/expressions/memberExpression.h" +#include "utils/arena_containers.h" +#include "checker/ETSchecker.h" +#include "compiler/lowering/util.h" +#include "util/helpers.h" +#include "utils/logger.h" + +/* +BEFORE LOWERING: +class Base { + field: number = 1; +} + +AFTER LOWERING: +class Base { + field: number; + constructor() { + this.field = 1; + } +} + +BEFORE LOWERING: +class Derived extends Base { + field: number = 2; +} + +AFTER LOWERING: +class Derived extends Base { + // field: number = 2; // Field removed from derived class + constructor() { + super(); + this.field = 2; + } +} +*/ + +namespace ark::es2panda::compiler { + +bool operator<(const FieldOverriding::OverriddenFieldInfo &a, const FieldOverriding::OverriddenFieldInfo &b) +{ + return std::tuple {a.derivedClassDef, a.derivedField, a.baseClassDef, a.baseField} < + std::tuple {b.derivedClassDef, b.derivedField, b.baseClassDef, b.baseField}; +} + +static ir::AstNode *GetClassForProp(ir::AstNode *node) +{ + if (node == nullptr) { + return nullptr; + } + for (ir::AstNode *p = node->Parent(); p != nullptr; p = p->Parent()) { + if (p->IsClassDefinition()) { + return p; + } + } + return nullptr; +} + +static std::string ToString(ir::AstNode *node) +{ + ir::AstNode *classDef = GetClassForProp(node); + if (classDef == nullptr) { + return ""; + } + auto classId = classDef->FindChild([](ir::AstNode *n) { return n->IsIdentifier(); }); + auto id = + node->IsIdentifier() ? node->AsIdentifier() : node->FindChild([](ir::AstNode *n) { return n->IsIdentifier(); }); + return id != nullptr && classId != nullptr + ? classId->AsIdentifier()->ToString() + "::" + id->AsIdentifier()->ToString() + : ""; +} + +void TraverseParentClassProperties(ir::AstNode *node, std::function cb) +{ + if (!node->IsClassProperty()) { + return; + } + + ir::AstNode *classDef = nullptr; + for (auto parent = node->Parent(); parent != nullptr; parent = parent->Parent()) { + if (parent->IsClassDefinition()) { + classDef = parent; + } + } + + if (classDef == nullptr) { + return; + } + + ir::AstNode *parentClass = + classDef->IsClassDefinition() && classDef->AsClassDefinition()->Super() != nullptr + ? classDef->AsClassDefinition()->Super()->TsType()->AsETSObjectType()->GetDeclNode()->AsClassDefinition() + : nullptr; // NOTE(muhammet): These checks are pobably unnecessary + while (parentClass != nullptr) { + if (parentClass->IsClassDefinition()) { + const ArenaVector body = parentClass->AsClassDefinition()->Body(); + std::for_each(body.begin(), body.end(), [&cb](ir::AstNode *fieldNode) { + if (fieldNode->IsClassProperty()) { + cb(fieldNode->AsClassProperty()); + } + }); + } + parentClass = parentClass->IsClassDefinition() && parentClass->AsClassDefinition()->Super() != nullptr + ? parentClass->AsClassDefinition() + ->Super() + ->TsType() + ->AsETSObjectType() + ->GetDeclNode() + ->AsClassDefinition() + : nullptr; + } +} + +static ir::AstNode *GetBaseProp(public_lib::Context *ctx, ir::AstNode *node) +{ + if (!node->IsClassProperty()) { + return nullptr; + } + + ir::AstNode *res = nullptr; + + // Get parent class fields + ir::ClassProperty *derivedProp = node->AsClassProperty(); + + // Go through all the parents and check if any of them have a field with the same name and type + TraverseParentClassProperties(derivedProp, [&derivedProp, &ctx, &res](ir::ClassProperty *baseProp) { + bool typesEqual = ctx->GetChecker()->IsTypeIdenticalTo(baseProp->TsType(), derivedProp->TsType()); + bool namesEqual = baseProp->Id()->Name() == derivedProp->Id()->Name(); + bool bothNonStatic = baseProp->IsStatic() == derivedProp->IsStatic() && !baseProp->IsStatic(); + bool isOverriding = typesEqual && namesEqual && bothNonStatic; + bool typeMismatch = bothNonStatic && namesEqual && !typesEqual; + bool accessModifiersCompatible = + (baseProp->IsPublic() && derivedProp->IsPublic()) || + (baseProp->IsProtected() && (derivedProp->IsProtected() || derivedProp->IsPublic())); + // NOTE(muhammet): Couldn't handle case with non readonly derived field + bool readonlyCompatible = baseProp->IsReadonly() == derivedProp->IsReadonly() || + (!baseProp->IsReadonly() && derivedProp->IsReadonly()); + + if (typeMismatch && accessModifiersCompatible) { + // NOTE(muhammet): Open a bug for this, gotta be a CTE according to spec + LOG_INFO(ES2PANDA, false) << "Overriding field has a different type, overriding field: " + << ToString(derivedProp) << " overriden field " << ToString(baseProp); + } else if (isOverriding && accessModifiersCompatible && readonlyCompatible) { + res = baseProp; + } + }); + + return res; +} + +static ir::Statement *CreateAssignmentStatementForField(public_lib::Context *ctx, ir::AstNode *baseField, + ir::AstNode *derivedField) +{ + if (derivedField->AsClassProperty()->Value() == nullptr || baseField->IsStatic()) { + return nullptr; + } + auto val = derivedField->AsClassProperty()->Value()->Clone(ctx->allocator, nullptr)->AsExpression(); + auto *thisExpr = util::NodeAllocator::Alloc(ctx->allocator); + thisExpr->AsThisExpression()->SetVariable(baseField->Variable()); + auto *fieldIdentifier = util::NodeAllocator::Alloc( + ctx->allocator, baseField->AsClassProperty()->Id()->Name(), ctx->allocator); + fieldIdentifier->SetVariable(baseField->Variable()); + auto *leftHandSide = util::NodeAllocator::Alloc( + ctx->allocator, thisExpr, fieldIdentifier, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); + auto *rightHandSide = val; + rightHandSide->SetVariable(val->Variable()); + auto *initializer = util::NodeAllocator::Alloc( + ctx->allocator, leftHandSide, rightHandSide, lexer::TokenType::PUNCTUATOR_SUBSTITUTION); + if (derivedField->IsReadonly()) { + initializer->SetIgnoreConstAssign(); + } + auto initStatement = util::NodeAllocator::Alloc(ctx->allocator, initializer); + return initStatement; +} + +// Map overriding/derived fields to overridden/base fields +static std::map GetFieldMap(public_lib::Context *ctx, + ir::ClassDefinition *derivedClassDef) +{ + std::map fields; + + // Iterate over class properties + for (auto derivedProp : derivedClassDef->Body()) { + auto baseProp = GetBaseProp(ctx, derivedProp); + if (baseProp != nullptr) { + // counterparts will be removed from the class + fields[derivedProp] = baseProp; + } + } + + return fields; +} + +// If there are any explicit calls to super add assignments after that +static ArenaVector::iterator AssigmentInsertionPosition(ir::BlockStatement *blockStatement) +{ + if (blockStatement->StatementsForUpdates().empty()) { + return blockStatement->StatementsForUpdates().begin(); + } + + auto it = std::find_if(blockStatement->StatementsForUpdates().begin(), blockStatement->StatementsForUpdates().end(), + [](ir::Statement *statement) { + return statement->FindChild([](ir::AstNode *node) { + return node->IsCallExpression() && node->AsCallExpression()->IsETSConstructorCall(); + }) != nullptr; + }); + return it != blockStatement->StatementsForUpdates().end() ? it + 1 : blockStatement->StatementsForUpdates().begin(); +} + +ir::AstNode *FieldOverriding::HandleDerivedField(public_lib::Context *ctx, OverriddenFieldInfo ovField) +{ + // Edit ctor + auto ctor = ovField.derivedClassDef + ->FindChild([](ir::AstNode *node) { + return node->IsMethodDefinition() && node->AsMethodDefinition()->IsConstructor(); + }) + ->AsMethodDefinition(); + ir::Statement *initStatement = CreateAssignmentStatementForField(ctx, ovField.baseField, ovField.derivedField); + if (initStatement != nullptr) { + auto blockStatement = ctor->Function()->Body()->AsBlockStatement(); + auto pos = AssigmentInsertionPosition(blockStatement); + blockStatement->StatementsForUpdates().emplace(pos, initStatement); + initStatement->SetParent(blockStatement); + initStatements_.emplace_back(initStatement); + } + + // Edit class body and remove fields + auto found = std::find_if(ovField.derivedClassDef->Body().begin(), ovField.derivedClassDef->Body().end(), + [ovField](ir::AstNode *node) { return node == ovField.derivedField; }); + if (found != ovField.derivedClassDef->Body().end()) { + LOG_INFO(ES2PANDA, false) << "Removing field '" + ToString(*found) + "'"; + ovField.derivedClassDef->BodyForUpdate().erase(found); + // NOTE(muhammet): Cant recheck the class due to checker errors but it works when the module is rechecked, + // couldnt find a fix for this + ir::AstNode *module = nullptr; + for (module = ovField.derivedClassDef->Parent(); module != nullptr; module = module->Parent()) { + if (module->IsETSModule()) { + break; + } + } + ES2PANDA_ASSERT(module != nullptr); + Recheck(GetPhaseManager(), ctx->GetChecker()->AsETSChecker()->VarBinder()->AsETSBinder(), + ctx->GetChecker()->AsETSChecker(), module); + } + + return ovField.derivedClassDef; +} + +ir::AstNode *FieldOverriding::HandleBaseField(public_lib::Context *ctx, OverriddenFieldInfo ovField) +{ + if (ovField.baseField->IsReadonly()) { + return ovField.baseClassDef; + } + // Edit ctor + auto ctor = ovField.baseClassDef + ->FindChild([](ir::AstNode *node) { + return node->IsMethodDefinition() && node->AsMethodDefinition()->IsConstructor(); + }) + ->AsMethodDefinition(); + ir::Statement *initStatement = CreateAssignmentStatementForField(ctx, ovField.baseField, ovField.baseField); + if (initStatement != nullptr) { + auto blockStatement = ctor->Function()->Body()->AsBlockStatement(); + auto pos = AssigmentInsertionPosition(blockStatement); + blockStatement->StatementsForUpdates().emplace(pos, initStatement); + initStatement->SetParent(blockStatement); + initStatements_.emplace_back(initStatement); + } + return ovField.baseClassDef; +} + +// NOTE(muhammet): Might be redundant +void FieldOverriding::RebindIdentifiers([[maybe_unused]] public_lib::Context *ctx, parser::Program *program) +{ + // Replace all references to derived field with base field + auto cb = [this](ir::AstNode *node) { + if (node->IsIdentifier() && node->AsIdentifier()->Variable() != nullptr && + node->AsIdentifier()->Variable()->Declaration() != nullptr) { + auto varNode = node->AsIdentifier()->Variable()->Declaration()->Node(); + OverriddenFieldInfo found {nullptr, nullptr, nullptr, nullptr}; + if (std::any_of(fieldsList_.begin(), fieldsList_.end(), + [varNode, &found](const OverriddenFieldInfo &ovField) { + found = ovField; + return varNode == ovField.derivedField; + })) { + LOG_INFO(ES2PANDA, false) << "Replacing " << ToString(found.derivedField) << " with " + << ToString(found.baseField) << " at " << ToString(node); + node->AsIdentifier()->Variable()->Declaration()->BindNode(found.baseField); + } + } + return node; + }; + program->Ast()->TransformChildrenRecursively(cb, this->Name()); +} + +bool FieldOverriding::PerformForModule(public_lib::Context *ctx, parser::Program *program) +{ + program->Ast()->IterateRecursively([&ctx, this](ir::AstNode *node) { + if (!node->IsClassDefinition()) { + return; + } + auto fields = GetFieldMap(ctx, node->AsClassDefinition()); + if (fields.empty()) { + return; + } + + for (auto entry : fields) { + auto derivedField = entry.first; + auto baseField = entry.second; + auto baseClassDef = GetClassForProp(baseField)->AsClassDefinition(); + auto derivedClassDef = GetClassForProp(derivedField)->AsClassDefinition(); + fieldsList_.insert(OverriddenFieldInfo {derivedClassDef, derivedField, baseClassDef, baseField}); + } + }); + + for (auto ovField : fieldsList_) { + LOG_INFO(ES2PANDA, false) << "Field '" + std::string {ovField.derivedClassDef->Ident()->Name()} + + "::" + std::string {ovField.derivedField->AsClassProperty()->Id()->Name()} + + "' overrides '" + std::string {ovField.baseClassDef->Ident()->Name()} + + "::" + std::string {ovField.baseField->AsClassProperty()->Id()->Name()} + "'"; + + [[maybe_unused]] auto baseClassDef = HandleBaseField(ctx, ovField)->AsClassDefinition(); + + [[maybe_unused]] auto derivedClassDef = HandleDerivedField(ctx, ovField)->AsClassDefinition(); + + ovField.baseClassDef->SetTransformedNode(Name(), ovField.baseClassDef); + ovField.derivedClassDef->SetTransformedNode(Name(), ovField.derivedClassDef); + } + + for (auto node : initStatements_) { + CheckLoweredNode(ctx->GetChecker()->AsETSChecker()->VarBinder()->AsETSBinder(), + ctx->GetChecker()->AsETSChecker(), node); + } + + // RebindIdentifiers(ctx, program); + + return true; +} + +} // namespace ark::es2panda::compiler \ No newline at end of file diff --git a/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h new file mode 100644 index 0000000000000000000000000000000000000000..614b9c648226d1328681a2cb680372e22a74d48f --- /dev/null +++ b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ES2PANDA_COMPILER_LOWERING_FIELD_OVERRIDING_H +#define ES2PANDA_COMPILER_LOWERING_FIELD_OVERRIDING_H + +#include "compiler/lowering/phase.h" + +namespace ark::es2panda::compiler { + +class FieldOverriding : public PhaseForBodies { +public: + struct OverriddenFieldInfo { + ir::ClassDefinition *derivedClassDef; + ir::AstNode *derivedField; + ir::ClassDefinition *baseClassDef; + ir::AstNode *baseField; + }; + + std::string_view Name() const override + { + return "FieldOverriding"; + } + bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; + ir::AstNode *OverrideFields(public_lib::Context *ctx, ir::ClassDefinition *derivedClassDef, + std::map fields, bool isBase = false); + + ir::AstNode *HandleDerivedField(public_lib::Context *ctx, OverriddenFieldInfo ovField); + ir::AstNode *HandleBaseField(public_lib::Context *ctx, OverriddenFieldInfo ovField); + +private: + void RebindIdentifiers(public_lib::Context *ctx, parser::Program *program); + std::set fieldsList_; + std::vector initStatements_; +}; + +} // namespace ark::es2panda::compiler + +#endif // ES2PANDA_COMPILER_LOWERING_FIELD_OVERRIDING_H \ No newline at end of file diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index 800aef488d33d922cf9d87e932c528dfa66a0057..ebb004809397656fb641fb240c34952b988819f6 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -72,6 +72,7 @@ #include "compiler/lowering/ets/unboxLowering.h" #include "compiler/lowering/ets/unionLowering.h" #include "compiler/lowering/ets/typeFromLowering.h" +#include "compiler/lowering/ets/fieldOverridingLowering.h" #include "compiler/lowering/plugin_phase.h" #include "compiler/lowering/resolveIdentifiers.h" #include "compiler/lowering/scopesInit/scopesInitPhase.h" @@ -133,6 +134,7 @@ std::vector GetETSPhaseList() // new ConvertPrimitiveCastMethodCall, new DynamicImport, new GradualTypeNarrowing, + new FieldOverriding, new AnnotationCopyPostLowering, new AsyncMethodLowering, new DeclareOverloadLowering, 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..462b007742be5958827de0dc1a4ead1ada2af5af 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.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'B') - arktest.assertEQ((new C() as B).get_name_a(), c'A') + 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") }