From 1e2f2b53d10421b288dd3777a0f079576fc37d76 Mon Sep 17 00:00:00 2001 From: daizihan Date: Mon, 8 Sep 2025 20:05:50 +0800 Subject: [PATCH] Fix async return type infer Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICWZNR?from=project-issue Signed-off-by: daizihan --- ets2panda/checker/ETSAnalyzer.cpp | 15 +++++++-- ets2panda/checker/ETSAnalyzerHelpers.cpp | 13 +++++++- ets2panda/checker/ETSchecker.h | 3 +- ets2panda/checker/ets/function.cpp | 31 +++--------------- ets2panda/test/runtime/ets/async_infer_1.ets | 27 ++++++++++++++++ ets2panda/test/runtime/ets/async_infer_2.ets | 33 ++++++++++++++++++++ 6 files changed, 90 insertions(+), 32 deletions(-) create mode 100644 ets2panda/test/runtime/ets/async_infer_1.ets create mode 100644 ets2panda/test/runtime/ets/async_infer_2.ets diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 53f045d8b8..387cc50193 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -487,7 +487,7 @@ checker::Type *ETSAnalyzer::Check(ir::ETSFunctionType *node) const } auto *returnType = node->IsExtensionFunction() && node->ReturnType()->IsTSThisType() ? signatureInfo->params.front()->TsType() - : checker->ComposeReturnType(node->ReturnType(), node->IsAsync()); + : checker->ComposeReturnType(node->ReturnType()); auto *const signature = checker->CreateSignature(signatureInfo, returnType, node->Flags(), node->IsExtensionFunction()); @@ -1770,7 +1770,6 @@ static checker::Type *GetCallExpressionReturnType(ETSChecker *checker, ir::CallE signature->Function()->Scope()); } checker::ScopeContext scopeCtx(checker, signature->Function()->Body()->Scope()); - checker->CollectReturnStatements(signature->Function()); return signature->ReturnType(); // NOTE(vpukhov): #14902 substituted signature is not updated } @@ -3709,6 +3708,18 @@ checker::Type *ETSAnalyzer::Check(ir::ReturnStatement *st) const return ReturnTypeForStatement(st); } + // Need to unroll the promise return type to avoid multiple incorrect return type inference later. + // i.e. The return type was Promise, but the argument type was actually boolean, then after this check, + // return type will be Promise | Boolean which was unexpected. + if (containingFunc->IsAsyncFunc() && containingFunc->ReturnTypeAnnotation() == nullptr && + containingFunc->Signature()->ReturnType() != nullptr && + checker->IsPromiseType(containingFunc->Signature()->ReturnType())) { + auto *retType = containingFunc->Signature()->ReturnType()->AsETSObjectType(); + containingFunc->Signature()->SetReturnType(retType->IsETSAsyncFuncReturnType() + ? retType->AsETSAsyncFuncReturnType()->GetPromiseTypeArg() + : retType->TypeArguments()[0]); + } + checker->AddStatus(CheckerStatus::MEET_RETURN); if (containingFunc->IsConstructor()) { diff --git a/ets2panda/checker/ETSAnalyzerHelpers.cpp b/ets2panda/checker/ETSAnalyzerHelpers.cpp index 7df2adc15c..da004de951 100644 --- a/ets2panda/checker/ETSAnalyzerHelpers.cpp +++ b/ets2panda/checker/ETSAnalyzerHelpers.cpp @@ -224,7 +224,8 @@ void DoBodyTypeChecking(ETSChecker *checker, ir::MethodDefinition *node, ir::Scr scriptFunc->Body()->Check(checker); if (scriptFunc->ReturnTypeAnnotation() == nullptr) { - if (scriptFunc->IsAsyncFunc()) { + // Note (daizihan): This will be entered multiple times and will continuously wrap promise, which is unexpected. + if (scriptFunc->IsAsyncFunc() && !checker->IsPromiseType(scriptFunc->Signature()->ReturnType())) { auto returnType = checker->CreateETSAsyncFuncReturnTypeFromBaseType(scriptFunc->Signature()->ReturnType()); ES2PANDA_ASSERT(returnType != nullptr); scriptFunc->Signature()->SetReturnType(returnType->PromiseType()); @@ -650,6 +651,11 @@ checker::Type *InferReturnType(ETSChecker *checker, ir::ScriptFunction *containi return funcReturnType; } + // Keep raw type for async function util all return types have been inferred. + if (containingFunc->IsAsyncFunc() && checker->IsPromiseType(funcReturnType)) { + funcReturnType = funcReturnType->AsETSObjectType()->TypeArguments()[0]; + } + /* when st_argment is ArrowFunctionExpression, need infer type for st_argment example code: @@ -716,6 +722,11 @@ checker::Type *ProcessReturnStatements(ETSChecker *checker, ir::ScriptFunction * checker::Type *argumentType = checker->GetNonConstantType(stArgument->Check(checker)); + // Keep raw type for async function util all return types have been inferred. + if (containingFunc->IsAsyncFunc() && checker->IsPromiseType(argumentType)) { + argumentType = argumentType->AsETSObjectType()->TypeArguments()[0]; + } + // previous return statement(s) don't have any value ES2PANDA_ASSERT(argumentType != nullptr); if (funcReturnType->IsETSVoidType() && !argumentType->IsETSVoidType()) { diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 8ffefc5306..a15e99227c 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -551,7 +551,7 @@ public: Signature *ComposeSignature(ir::ScriptFunction *func, SignatureInfo *signatureInfo, Type *returnType, varbinder::Variable *nameVar); - Type *ComposeReturnType(ir::TypeNode *typeAnnotation, bool isAsync); + Type *ComposeReturnType(ir::TypeNode *typeAnnotation); SignatureInfo *ComposeSignatureInfo(ir::TSTypeParameterDeclaration *typeParams, ArenaVector const ¶ms); void ValidateMainSignature(ir::ScriptFunction *func); @@ -895,7 +895,6 @@ public: ETSObjectType *GetCachedFunctionalInterface(ir::ETSFunctionType *type); void CacheFunctionalInterface(ir::ETSFunctionType *type, ETSObjectType *ifaceType); - void CollectReturnStatements(ir::AstNode *parent); ir::ETSParameterExpression *AddParam(util::StringView name, ir::TypeNode *type); evaluate::ScopedDebugInfoPlugin *GetDebugInfoPlugin(); diff --git a/ets2panda/checker/ets/function.cpp b/ets2panda/checker/ets/function.cpp index 7034998575..b8cb4270a6 100644 --- a/ets2panda/checker/ets/function.cpp +++ b/ets2panda/checker/ets/function.cpp @@ -1702,12 +1702,12 @@ Signature *ETSChecker::ComposeSignature(ir::ScriptFunction *func, SignatureInfo return signature; } -Type *ETSChecker::ComposeReturnType(ir::TypeNode *typeAnnotation, bool isAsync) +Type *ETSChecker::ComposeReturnType(ir::TypeNode *typeAnnotation) { if (typeAnnotation != nullptr) { return typeAnnotation->GetType(this); } - return isAsync ? CreatePromiseOf(GlobalVoidType()) : GlobalVoidType(); + return GlobalVoidType(); } static varbinder::LocalVariable *SetupSignatureParameter(ir::ETSParameterExpression *param, Type *type) @@ -1846,9 +1846,8 @@ void ETSChecker::BuildFunctionSignature(ir::ScriptFunction *func, bool isConstru thisVar->SetTsType(Context().ContainingClass()); } auto *signatureInfo = ComposeSignatureInfo(func->TypeParams(), func->Params()); - auto *returnType = func->GetPreferredReturnType() != nullptr - ? func->GetPreferredReturnType() - : ComposeReturnType(func->ReturnTypeAnnotation(), func->IsAsyncFunc()); + auto *returnType = func->GetPreferredReturnType() != nullptr ? func->GetPreferredReturnType() + : ComposeReturnType(func->ReturnTypeAnnotation()); auto *signature = ComposeSignature(func, signatureInfo, returnType, nameVar); if (signature == nullptr) { // #23134 ES2PANDA_ASSERT(IsAnyError()); @@ -2611,28 +2610,6 @@ void ETSChecker::CacheFunctionalInterface(ir::ETSFunctionType *type, ETSObjectTy functionalInterfaceCache_.emplace(hash, ifaceType); } -void ETSChecker::CollectReturnStatements(ir::AstNode *parent) // NOTE: remove with #28178 -{ - parent->Iterate([this](ir::AstNode *childNode) -> void { - if (childNode->IsScriptFunction()) { - return; - } - - auto scope = Scope(); - if (childNode->IsBlockStatement()) { - scope = childNode->AsBlockStatement()->Scope(); - } - checker::ScopeContext scopeCtx(this, scope); - - if (childNode->IsReturnStatement()) { - ir::ReturnStatement *returnStmt = childNode->AsReturnStatement(); - returnStmt->Check(this); - } - - CollectReturnStatements(childNode); - }); -} - std::vector &ETSChecker::PendingConstraintCheckRecords() { return pendingConstraintCheckRecords_; diff --git a/ets2panda/test/runtime/ets/async_infer_1.ets b/ets2panda/test/runtime/ets/async_infer_1.ets new file mode 100644 index 0000000000..044aac2077 --- /dev/null +++ b/ets2panda/test/runtime/ets/async_infer_1.ets @@ -0,0 +1,27 @@ +/* + * 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. + */ + +async function soo() { + const result = await foo(); + arktest.assertTrue(result); +} + +async function foo() { + return true; +} + +function main() { + soo(); +} diff --git a/ets2panda/test/runtime/ets/async_infer_2.ets b/ets2panda/test/runtime/ets/async_infer_2.ets new file mode 100644 index 0000000000..b612aec202 --- /dev/null +++ b/ets2panda/test/runtime/ets/async_infer_2.ets @@ -0,0 +1,33 @@ +/* + * 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. + */ + + +let s = 0 + +function main() { + let v: Promise = foo() + arktest.assertTrue(foo() instanceof Promise) +} + +async function bar() { + return true +} + +async function foo() { // infer Promise + if (s == 0) + return bar(); + else + return new Boolean(false); +} -- Gitee