From 42cd04d12807260c39faad1a2fdfbf725eb78537 Mon Sep 17 00:00:00 2001 From: Martin Sajti Date: Wed, 27 Aug 2025 16:51:03 +0200 Subject: [PATCH] Generate rest-invoke for optional param lambdas Invoke methods with rest argument are missing from generated lambda classes with optional parameters. This patch implements the necessary function generation. Also a recently added bug related to signature selector for the invoke call is fixed. Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICVUSH Internal issue: #28522 Test: build, runtime test Change-Id: I1ce05ab983c0dfa61bef6d3c20c61d4fafe4e0c9 Signed-off-by: Martin Sajti --- ets2panda/checker/types/signature.h | 5 ++ ets2panda/compiler/core/ETSemitter.cpp | 5 +- .../lowering/ets/enumPostCheckLowering.cpp | 2 +- .../compiler/lowering/ets/lambdaLowering.cpp | 80 +++++++++++++++---- ets2panda/parser/program/DeclarationCache.cpp | 2 +- .../runtime/ets/lambda_opt_param_rest_1.ets | 50 ++++++++++++ .../runtime/ets/lambda_opt_param_rest_2.ets | 29 +++++++ 7 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 ets2panda/test/runtime/ets/lambda_opt_param_rest_1.ets create mode 100644 ets2panda/test/runtime/ets/lambda_opt_param_rest_2.ets diff --git a/ets2panda/checker/types/signature.h b/ets2panda/checker/types/signature.h index 4051395469..29a958010e 100644 --- a/ets2panda/checker/types/signature.h +++ b/ets2panda/checker/types/signature.h @@ -274,6 +274,11 @@ public: return signatureInfo_->restVar != nullptr; } + void SetRestVar(varbinder::LocalVariable *restVar) + { + signatureInfo_->restVar = restVar; + } + [[nodiscard]] bool IsFinal() const noexcept { return HasSignatureFlag(SignatureFlags::FINAL); diff --git a/ets2panda/compiler/core/ETSemitter.cpp b/ets2panda/compiler/core/ETSemitter.cpp index 69cac80ef6..ccd40cfac2 100644 --- a/ets2panda/compiler/core/ETSemitter.cpp +++ b/ets2panda/compiler/core/ETSemitter.cpp @@ -268,9 +268,10 @@ ETSEmitter::ETSEmitter(const public_lib::Context *context) { } -ETSEmitter::~ETSEmitter() { +ETSEmitter::~ETSEmitter() +{ // for PImpl -}; +} std::string const &ETSEmitter::AddDependence(std::string const &str) { diff --git a/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp b/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp index e3527f1118..b14271c0df 100644 --- a/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp +++ b/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp @@ -434,7 +434,7 @@ ir::AstNode *EnumPostCheckLoweringPhase::BuildEnumCasting(ir::AstNode *const nod return node; } return GenerateEnumCasting(node->AsTSAsExpression(), castFlag); -}; +} bool EnumPostCheckLoweringPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program) { diff --git a/ets2panda/compiler/lowering/ets/lambdaLowering.cpp b/ets2panda/compiler/lowering/ets/lambdaLowering.cpp index 1d45f1c371..de8a31fe3c 100644 --- a/ets2panda/compiler/lowering/ets/lambdaLowering.cpp +++ b/ets2panda/compiler/lowering/ets/lambdaLowering.cpp @@ -56,6 +56,7 @@ struct LambdaClassInvokeInfo { util::StringView restParameterIdentifier = ""; util::StringView restArgumentIdentifier = ""; ArenaVector *argNames = nullptr; + bool genRestInvokeSigForOptParams = false; }; static std::pair FindEnclosingClassAndFunction(ir::AstNode *ast) @@ -923,6 +924,30 @@ static ir::BlockStatement *CreateLambdaClassInvokeBody(public_lib::Context *ctx, return util::NodeAllocator::ForceSetParent(allocator, allocator, std::move(bodyStmts)); } +static checker::Signature *GenerateSignatureWithRestParam(public_lib::Context *ctx, checker::Signature *originalSig) +{ + auto *checker = ctx->GetChecker()->AsETSChecker(); + auto *allocator = ctx->allocator; + + auto *newParamId = Gensym(allocator); + auto *restType = checker->CreateETSArrayType(checker->GlobalETSAnyType()); + auto *newSpreadElement = util::NodeAllocator::ForceSetParent( + allocator, ir::AstNodeType::REST_ELEMENT, allocator, newParamId); + auto *newParamNode = + util::NodeAllocator::ForceSetParent(allocator, newSpreadElement, false, allocator); + auto *newParamDecl = allocator->New(newParamId->Name()); + newParamDecl->BindNode(newParamNode); + auto *newParamVar = allocator->New(newParamDecl, varbinder::VariableFlags::NONE); + newParamNode->SetVariable(newParamVar); + newParamNode->SetTsType(restType); + newParamVar->SetTsType(restType); + + auto *newLambdaSig = originalSig->Copy(allocator, checker->Relation(), checker->GetGlobalTypesHolder()); + newLambdaSig->SetRestVar(newParamVar); + + return newLambdaSig; +} + static void CreateLambdaClassInvokeMethod(public_lib::Context *ctx, LambdaInfo const *info, LambdaClassInvokeInfo *lciInfo, util::StringView methodName, bool wrapToObject) @@ -946,6 +971,12 @@ static void CreateLambdaClassInvokeMethod(public_lib::Context *ctx, LambdaInfo c if (lciInfo->lambdaSignature->HasRestParameter()) { CreateInvokeMethodRestParameter(ctx, lciInfo, ¶ms); lciInfo->restArgumentIdentifier = GenName(allocator).View(); + } else if (lciInfo->genRestInvokeSigForOptParams) { + auto *const originalLambdaSig = lciInfo->lambdaSignature; + lciInfo->lambdaSignature = GenerateSignatureWithRestParam(ctx, lciInfo->lambdaSignature); + CreateInvokeMethodRestParameter(ctx, lciInfo, ¶ms); + lciInfo->restArgumentIdentifier = GenName(allocator).View(); + lciInfo->lambdaSignature = originalLambdaSig; } auto *returnType2 = allocator->New( @@ -1155,6 +1186,25 @@ static void SetModifiersForFunctionReference(ir::ClassDefinition *classDefinitio } } +static void GenerateRestInvokeForOptional(public_lib::Context *ctx, LambdaInfo const *info, + LambdaClassInvokeInfo &lciInfo) +{ + // For the arity == signature->ArgCount() case, it's already implemented in the corresponding LambdaN + // class in stdlib + if (lciInfo.arity == lciInfo.lambdaSignature->ArgCount() || lciInfo.lambdaSignature->HasRestParameter()) { + return; + } + + auto *checker = ctx->GetChecker()->AsETSChecker(); + + auto restInvokeMethodName = + util::UString {checker->FunctionalInterfaceInvokeName(lciInfo.arity, true), ctx->allocator}.View(); + + lciInfo.genRestInvokeSigForOptParams = true; + CreateLambdaClassInvokeMethod(ctx, info, &lciInfo, restInvokeMethodName, true); + lciInfo.genRestInvokeSigForOptParams = false; +} + static ir::ClassDeclaration *CreateLambdaClass(public_lib::Context *ctx, checker::ETSFunctionType *fntype, ir::MethodDefinition *callee, LambdaInfo const *info) { @@ -1166,20 +1216,20 @@ static ir::ClassDeclaration *CreateLambdaClass(public_lib::Context *ctx, checker CloneTypeParamsForClass(ctx, oldTypeParams, info->enclosingFunction, ctx->parserProgram->GlobalClassScope()); auto &substitution = subst0; // NOTE(gogabr): needed to capture in a lambda later. - auto fnInterface = fntype->Substitute(checker->Relation(), &substitution)->ArrowToFunctionalInterface(checker); - auto lambdaProviderClass = FunctionTypeToLambdaProviderType(checker, fntype->ArrowSignature()); + auto *fnInterface = fntype->Substitute(checker->Relation(), &substitution)->ArrowToFunctionalInterface(checker); + auto *lambdaProviderClass = FunctionTypeToLambdaProviderType(checker, fntype->ArrowSignature()); auto lexScope = varbinder::LexicalScope::Enter(varBinder, ctx->parserProgram->GlobalClassScope()); - auto classDeclaration = + auto *classDeclaration = CreateEmptyLambdaClassDeclaration(ctx, info, newTypeParams, fnInterface, lambdaProviderClass); - auto classDefinition = classDeclaration->Definition(); + auto *classDefinition = classDeclaration->Definition(); SetModifiersForFunctionReference(classDefinition, callee, info); CreateLambdaClassFields(ctx, classDefinition, info, &substitution); CreateLambdaClassConstructor(ctx, classDefinition, info, &substitution); - auto signature = fntype->ArrowSignature(); + auto *signature = fntype->ArrowSignature(); LambdaClassInvokeInfo lciInfo; lciInfo.callee = callee; @@ -1195,7 +1245,7 @@ static ir::ClassDeclaration *CreateLambdaClass(public_lib::Context *ctx, checker ctx->allocator} .View(); CreateLambdaClassInvokeMethod(ctx, info, &lciInfo, invokeMethodName, true); - // NOTE(vpukhov): for optional methods, the required invokeRk k={min, max-1} is not emitted + GenerateRestInvokeForOptional(ctx, info, lciInfo); } } else { lciInfo.arity = signature->ArgCount(); @@ -1549,14 +1599,16 @@ static ir::AstNode *InsertInvokeCall(public_lib::Context *ctx, ir::CallExpressio auto *oldCallee = call->Callee(); auto *oldType = checker->GetApparentType(oldCallee->TsType()); ES2PANDA_ASSERT(oldType != nullptr); - size_t arity = call->Arguments().size(); + const size_t arity = call->Arguments().size(); auto *ifaceType = oldType->AsETSFunctionType()->ArrowToFunctionalInterfaceDesiredArity(checker, arity); ES2PANDA_ASSERT(ifaceType != nullptr); - bool hasRestParam = - (oldType->IsETSFunctionType() && oldType->AsETSFunctionType()->ArrowSignature()->HasRestParameter()) || - call->Signature()->HasRestParameter(); - util::StringView invokeMethodName = - util::UString {checker->FunctionalInterfaceInvokeName(arity, hasRestParam), allocator}.View(); + + /* Pull out substituted call signature */ + // NOTE (smartin): the signature that is set here is not the same as the one set above. In the AST, a + // different signature will be present, than in the bytecode. Review this behaviour, and make it consistent. + checker::Signature *callSig = ifaceType->GetFunctionalInterfaceInvokeType()->CallSignatures()[0]; + ES2PANDA_ASSERT(callSig != nullptr); + util::StringView invokeMethodName = callSig->Function()->Id()->Name(); auto *prop = ifaceType->GetProperty(invokeMethodName, checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD | checker::PropertySearchFlags::SEARCH_IN_INTERFACES); @@ -1572,10 +1624,6 @@ static ir::AstNode *InsertInvokeCall(public_lib::Context *ctx, ir::CallExpressio newCallee->SetTsType(prop->TsType()); newCallee->SetObjectType(ifaceType); - /* Pull out substituted call signature */ - checker::Signature *callSig = ifaceType->GetFunctionalInterfaceInvokeType()->CallSignatures()[0]; - ES2PANDA_ASSERT(callSig != nullptr); - call->SetCallee(newCallee); call->SetSignature(callSig); diff --git a/ets2panda/parser/program/DeclarationCache.cpp b/ets2panda/parser/program/DeclarationCache.cpp index cb103fb184..edc1868bc7 100644 --- a/ets2panda/parser/program/DeclarationCache.cpp +++ b/ets2panda/parser/program/DeclarationCache.cpp @@ -25,7 +25,7 @@ UniqueSpinMutex::~UniqueSpinMutex() { // Atomic with relaxed order reason: read of field ES2PANDA_ASSERT(spin_.load(std::memory_order_relaxed) == LOCK_OFF); -}; +} // CC-OFFNXT(G.NAM.03-CPP) project code style void UniqueSpinMutex::lock() diff --git a/ets2panda/test/runtime/ets/lambda_opt_param_rest_1.ets b/ets2panda/test/runtime/ets/lambda_opt_param_rest_1.ets new file mode 100644 index 0000000000..e3d517daa2 --- /dev/null +++ b/ets2panda/test/runtime/ets/lambda_opt_param_rest_1.ets @@ -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. + */ + +function foo1(f: (a1: string, ...args: string[]) => string) { + let fa = f("a") + arktest.assertEQ(fa, "a-undefined-undefined") + let fab = f("a", "b") + arktest.assertEQ(fab, "a-b-undefined") + let fabc = f("a", "b", "c") + arktest.assertEQ(fabc, "a-b-c") + let fabcd = f("a", "b", "c", "d") + arktest.assertEQ(fabcd, "a-b-c-d") +} + +function foo2(f: (a1: string, ...args: string[]) => string) { + let fa = f("a") + arktest.assertEQ(fa, "a-undefined") + let fab = f("a", "b") + arktest.assertEQ(fab, "a-b") + let fabc = f("a", "b", "c") + arktest.assertEQ(fabc, "a-b") +} + +function main(): void { + let f1 = ((a: string, b?: string, c?: string, ...arg1: string[]): string => { + let res: string = a + "-" + b + "-" + c; + if(arg1.length != 0) { + res += "-" + arg1[0] + } + return res + }) as Function + foo1(f1 as (a1: string, ...args: string[]) => string) + + let f2 = ((a: string, b?: string): string => { + return a + "-" + b; + }) as Function + foo2(f2 as (a1: string, ...args: string[]) => string) +} diff --git a/ets2panda/test/runtime/ets/lambda_opt_param_rest_2.ets b/ets2panda/test/runtime/ets/lambda_opt_param_rest_2.ets new file mode 100644 index 0000000000..5a2bb427ca --- /dev/null +++ b/ets2panda/test/runtime/ets/lambda_opt_param_rest_2.ets @@ -0,0 +1,29 @@ +/* + * 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. + */ + +function foo(f: (a1: string, ...args: string[]) => void) { + f("a") +} + +function test() { + try { + let f = ((a: string, b?: string) => {}) as Function + foo(f as (a1: string, ...args: string[]) => void) + } catch (e) { + arktest.assertTrue(false); + } +} + +test() -- Gitee