diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index a849952ff192a8391e05bf87bf427a4b70f4fcbc..370038368dbc56a23ad69520d7c98c920abe2566 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 bc6c2c8fa9bff75772993905502650d55ebbb11c..0e573e0e14826d152d52b76627e405f4eb5c79b2 100644 --- a/ets2panda/checker/ets/object.cpp +++ b/ets2panda/checker/ets/object.cpp @@ -1012,7 +1012,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); } @@ -1032,6 +1032,7 @@ void ETSChecker::CheckInterfaceFunctions(ETSObjectType *classType) } ir::MethodDefinition *node = prop->Declaration()->Node()->AsMethodDefinition(); + AddAccessorFlagsForOptionalPropInterface(classType, interface, node); if (prop->TsType()->IsTypeError()) { continue; } @@ -1248,6 +1249,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/checker/ets/utilityTypeHandlers.cpp b/ets2panda/checker/ets/utilityTypeHandlers.cpp index b47ff09f704ff798f08c2f74b964a72b4f48ba63..bf7d6fb3441738b6612b52e65875599ff7bd71c8 100644 --- a/ets2panda/checker/ets/utilityTypeHandlers.cpp +++ b/ets2panda/checker/ets/utilityTypeHandlers.cpp @@ -706,6 +706,7 @@ ir::MethodDefinition *ETSChecker::CreateNullishAccessor(ir::MethodDefinition *co function->SetScope(functionScope); paramScope->BindNode(function); functionScope->BindNode(function); + VarBinder()->AsETSBinder()->AddCompilableFunction(function); if (function->IsGetter()) { auto *propTypeAnn = function->ReturnTypeAnnotation(); diff --git a/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp b/ets2panda/compiler/lowering/ets/interfacePropertyDeclarations.cpp index e3d748001cc78c774362bc78775c9807b715942d..2479efc0187905b73e082557fab7242c50426e14 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,30 @@ 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 +258,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 +279,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 +289,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 d1f69e94ecd559c5fa404243214024ef1d88bd63..7d0e87f580b9461a20f9f1e74605e95bdd7ad373 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 0000000000000000000000000000000000000000..0ec000f1b3bec6f5715c35271c45a1c3c5219f4e --- /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 0000000000000000000000000000000000000000..4d7e323534b036b93d14ad462cf37d50accf3e93 --- /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 0000000000000000000000000000000000000000..f5e96f44ebd0abae5d9d9f1d7090e3f188303074 --- /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") +}