From c9e1dbeb7d34730f0386d01ae2f779600d523fed Mon Sep 17 00:00:00 2001 From: daizihan Date: Tue, 12 Aug 2025 11:50:29 +0800 Subject: [PATCH] Support optional prop in interface Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICSIN7?from=project-issue Signed-off-by: daizihan --- ets2panda/checker/ETSchecker.h | 2 + ets2panda/checker/ets/object.cpp | 24 ++++++++- ets2panda/compiler/core/ETSemitter.cpp | 4 -- .../ets/interfacePropertyDeclarations.cpp | 49 ++++++++++++++++--- .../ets/interfacePropertyDeclarations.h | 4 +- .../ets/default_interface_prop_bad.ets | 31 ++++++++++++ .../runtime/ets/default_interface_prop.ets | 35 +++++++++++++ .../runtime/ets/default_interface_prop1.ets | 37 ++++++++++++++ 8 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 ets2panda/test/ast/compiler/ets/default_interface_prop_bad.ets create mode 100644 ets2panda/test/runtime/ets/default_interface_prop.ets create mode 100644 ets2panda/test/runtime/ets/default_interface_prop1.ets diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 47fc4abbb2..23de383815 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -249,6 +249,8 @@ public: void MaybeReportErrorsForOverridingValidation(ArenaVector &abstractsToBeImplemented, ETSObjectType *classType, const lexer::SourcePosition &pos, bool reportError); + void AddAccessorFlagsForOptionalPropInterface(ETSObjectType *classType, ETSObjectType *interfaceType, + ir::MethodDefinition *ifaceMethod); void ValidateOverriding(ETSObjectType *classType, const lexer::SourcePosition &pos); void CheckInterfaceFunctions(ETSObjectType *classType); void CollectImplementedMethodsFromInterfaces(ETSObjectType *classType, diff --git a/ets2panda/checker/ets/object.cpp b/ets2panda/checker/ets/object.cpp index c6ca10da39..4ea9be180d 100644 --- a/ets2panda/checker/ets/object.cpp +++ b/ets2panda/checker/ets/object.cpp @@ -1007,7 +1007,7 @@ static void CallRedeclarationCheckForCorrectSignature(ir::MethodDefinition *meth { ir::ScriptFunction *func = method->Function(); ES2PANDA_ASSERT(func != nullptr); - if (!func->IsAbstract()) { + if (!func->IsAbstract() && !func->IsSetter() && !func->IsSetter()) { auto *sigFunc = funcType->FindSignature(func); checker->CheckFunctionRedeclarationInInterface(classType, similarSignatures, sigFunc); } @@ -1027,6 +1027,7 @@ void ETSChecker::CheckInterfaceFunctions(ETSObjectType *classType) } ir::MethodDefinition *node = prop->Declaration()->Node()->AsMethodDefinition(); + AddAccessorFlagsForOptionalPropInterface(classType, interface, node); if (prop->TsType()->IsTypeError()) { continue; } @@ -1239,6 +1240,27 @@ void ETSChecker::MaybeReportErrorsForOverridingValidation(ArenaVectorIsAbstract() || ifaceMethod->Function()->Body() == nullptr || classType == interfaceType) { + return; + } + + for (auto *field : classType->Fields()) { + if (field->Declaration()->Node()->AsClassProperty()->IsStatic()) { + continue; + } + + if (field->Name() == ifaceMethod->Id()->Name()) { + field->Declaration()->Node()->AddModifier(ir::ModifierFlags::GETTER_SETTER); + break; + } + } +} + void ETSChecker::ValidateOverriding(ETSObjectType *classType, const lexer::SourcePosition &pos) { if (GetCachedComputedAbstracts()->find(classType) != GetCachedComputedAbstracts()->end()) { diff --git a/ets2panda/compiler/core/ETSemitter.cpp b/ets2panda/compiler/core/ETSemitter.cpp index 1151febda8..89626de91a 100644 --- a/ets2panda/compiler/core/ETSemitter.cpp +++ b/ets2panda/compiler/core/ETSemitter.cpp @@ -439,10 +439,6 @@ void ETSEmitter::GenInterfaceMethodDefinition(const ir::MethodDefinition *method func.metadata->SetAttribute(Signatures::EXTERNAL); } - if (scriptFunc->Body() != nullptr) { - return; - } - func.metadata->SetAccessFlags(func.metadata->GetAccessFlags() | ACC_ABSTRACT); Program()->AddToFunctionTable(std::move(func)); } diff --git a/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp b/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp index 542d287cdf..47c979723d 100644 --- a/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp +++ b/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp @@ -22,12 +22,14 @@ #include "ir/astNode.h" #include "ir/expression.h" #include "ir/expressions/identifier.h" +#include "ir/expressions/literals/undefinedLiteral.h" #include "ir/opaqueTypeNode.h" #include "ir/statements/blockStatement.h" #include "ir/ts/tsInterfaceBody.h" #include "ir/base/classProperty.h" #include "ir/ets/etsUnionType.h" #include "ir/ets/etsNullishTypes.h" +#include "ir/visitor/AstVisitor.h" namespace ark::es2panda::compiler { @@ -103,10 +105,29 @@ ir::FunctionSignature InterfacePropertyDeclarationsPhase::GenerateGetterOrSetter return ir::FunctionSignature(nullptr, std::move(params), isSetter ? nullptr : field->TypeAnnotation()); } +ir::AstNode *InterfacePropertyDeclarationsPhase::GenerateGetterOrSetterBodyForOptional(public_lib::Context *ctx, + bool isSetter, bool isOptional) +{ + if (!isOptional) { + return nullptr; + } + + ArenaVector returnStatement(ctx->Allocator()->Adapter()); + if (isSetter) { + auto *parser = ctx->parser->AsETSParser(); + returnStatement.emplace_back(parser->CreateFormattedStatement("throw new InvalidStoreAccessError()")); + } else { + auto *undef = ctx->AllocNode(); + auto *rtStmt = ctx->AllocNode(undef); + returnStatement.emplace_back(rtStmt); + } + return ctx->AllocNode(ctx->Allocator(), std::move(returnStatement)); +} + ir::MethodDefinition *InterfacePropertyDeclarationsPhase::GenerateGetterOrSetter(public_lib::Context *ctx, varbinder::ETSBinder *varbinder, ir::ClassProperty *const field, - bool isSetter) + bool isSetter, bool isOptional) { auto classScope = NearestScope(field); auto *paramScope = ctx->Allocator()->New(ctx->Allocator(), classScope); @@ -117,19 +138,29 @@ ir::MethodDefinition *InterfacePropertyDeclarationsPhase::GenerateGetterOrSetter paramScope->BindFunctionScope(functionScope); auto flags = ir::ModifierFlags::PUBLIC; - flags |= ir::ModifierFlags::ABSTRACT; + if (!isOptional) { + flags |= ir::ModifierFlags::ABSTRACT; + } ir::FunctionSignature signature = GenerateGetterOrSetterSignature(ctx, varbinder, field, isSetter, paramScope); auto *func = ctx->AllocNode( ctx->Allocator(), ir::ScriptFunction::ScriptFunctionData { - nullptr, std::move(signature), // CC-OFF(G.FMT.02) project code style - // CC-OFFNXT(G.FMT.02) project code style + GenerateGetterOrSetterBodyForOptional(ctx, isSetter, isOptional), + std::move(signature), // CC-OFF(G.FMT.02) project code style + // CC-OFFNXT(G.FMT.02) project code style isSetter ? ir::ScriptFunctionFlags::SETTER : ir::ScriptFunctionFlags::GETTER, flags, classScope->Node()->AsTSInterfaceDeclaration()->Language()}); + // Since optional prop has default body, need to set scope. + if (isOptional) { + auto funcCtx = varbinder::LexicalScope::Enter(varbinder, functionScope); + InitScopesPhaseETS::RunExternalNode(func, varbinder); + } else { + func->SetScope(functionScope); + } + func->SetRange(field->Range()); - func->SetScope(functionScope); auto const &name = field->Key()->AsIdentifier()->Name(); auto methodIdent = ctx->AllocNode(name, ctx->Allocator()); @@ -226,7 +257,9 @@ ir::Expression *InterfacePropertyDeclarationsPhase::UpdateInterfaceProperties(pu continue; } auto *originProp = prop->Clone(ctx->allocator, nullptr); - auto getter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), false); + bool isOptional = prop->AsClassProperty()->IsOptionalDeclaration(); + ir::MethodDefinition *getter = + GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), false, isOptional); getter->SetOriginalNode(originProp); auto methodScope = scope->AsClassScope()->InstanceMethodScope(); @@ -245,7 +278,7 @@ ir::Expression *InterfacePropertyDeclarationsPhase::UpdateInterfaceProperties(pu AddOverload(method, getter, var); if (!prop->AsClassProperty()->IsReadonly()) { - auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true); + auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true, isOptional); AddOverload(method, setter, var); } continue; @@ -255,7 +288,7 @@ ir::Expression *InterfacePropertyDeclarationsPhase::UpdateInterfaceProperties(pu newPropertyList.emplace_back(getter); if (!prop->AsClassProperty()->IsReadonly()) { - auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true); + auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true, isOptional); AddOverload(getter, setter, variable); } scope->AsClassScope()->InstanceFieldScope()->EraseBinding(name); diff --git a/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.h b/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.h index d1f69e94ec..7d0e87f580 100644 --- a/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.h +++ b/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.h @@ -128,7 +128,7 @@ private: varbinder::FunctionParamScope *paramScope); ir::MethodDefinition *GenerateGetterOrSetter(public_lib::Context *ctx, varbinder::ETSBinder *varbinder, - ir::ClassProperty *const field, bool isSetter); + ir::ClassProperty *const field, bool isSetter, bool isOptional); void CollectPropertiesAndSuperInterfaces(ir::TSInterfaceBody *const interface); @@ -141,6 +141,8 @@ private: void UpdateClassProperties(public_lib::Context *ctx, ir::ClassDefinition *const klass); + ir::AstNode *GenerateGetterOrSetterBodyForOptional(public_lib::Context *ctx, bool isSetter, bool isOptional); + private: OptionalInterfacePropertyCollector propCollector_ {}; }; diff --git a/ets2panda/test/ast/compiler/ets/default_interface_prop_bad.ets b/ets2panda/test/ast/compiler/ets/default_interface_prop_bad.ets new file mode 100644 index 0000000000..0ec000f1b3 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/default_interface_prop_bad.ets @@ -0,0 +1,31 @@ +/* + * 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. + */ + +interface Inter { + a ?: number +} + +class Klass implements Inter { + a ?: string +} + +/* @@? 21:5 Error TypeError: a(): undefined|String in Klass cannot override a(): Double|undefined in Inter because overriding return type is not compatible with the other return type. */ +/* @@? 21:5 Error TypeError: Method a(): undefined|String in Klass not overriding any method */ +/* @@? 21:5 Error TypeError: a(): undefined|String in Klass cannot override a(): Double|undefined in Inter because overriding return type is not compatible with the other return type. */ +/* @@? 21:5 Error TypeError: Method a(): undefined|String in Klass not overriding any method */ +/* @@? 21:5 Error TypeError: Method a(a: undefined|String): void in Klass not overriding any method */ +/* @@? 21:5 Error TypeError: a(): undefined|String in Klass cannot override a(): Double|undefined in Inter because overriding return type is not compatible with the other return type. */ +/* @@? 21:5 Error TypeError: Method a(): undefined|String in Klass not overriding any method */ +/* @@? 21:5 Error TypeError: Method a(a: undefined|String): void in Klass not overriding any method */ diff --git a/ets2panda/test/runtime/ets/default_interface_prop.ets b/ets2panda/test/runtime/ets/default_interface_prop.ets new file mode 100644 index 0000000000..4d7e323534 --- /dev/null +++ b/ets2panda/test/runtime/ets/default_interface_prop.ets @@ -0,0 +1,35 @@ +/* + * 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. + */ + +interface Inter { + a ?: number +} + +class Klass implements Inter { +} + +function main() { + let a = new Klass(); + arktest.assertEQ(a.a, undefined) + try { + a.a = 10 + } catch (e) { + if (e instanceof InvalidStoreAccessError) { + arktest.assertTrue(true); + } else { + arktest.assertTrue(false); + } + } +} diff --git a/ets2panda/test/runtime/ets/default_interface_prop1.ets b/ets2panda/test/runtime/ets/default_interface_prop1.ets new file mode 100644 index 0000000000..f5e96f44eb --- /dev/null +++ b/ets2panda/test/runtime/ets/default_interface_prop1.ets @@ -0,0 +1,37 @@ +/* + * 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. + */ + + +interface Inter { + a ?: number + b ?: string +} + +class Klass implements Inter { + a ?: number + b ?: string + constructor() { + this.a = 10; + } +} + +function main() { + let a = new Klass(); + arktest.assertEQ(a.a, 10) + a.a = 11; + arktest.assertEQ(a.a, 11) + a.b = "Hello"; + arktest.assertEQ(a.b, "Hello") +} -- Gitee