diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 3aa4109c2eb8eb2d86c89482000a32026599fdd0..c2e26737071af1f012463062cbeb1dca004cc5fb 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -1751,7 +1751,7 @@ checker::Type *ETSAnalyzer::ResolveMemberExpressionByBaseType(ETSChecker *checke } if (baseType->IsETSTupleType()) { - return expr->SetAndAdjustType(checker, checker->GlobalETSObjectType()); + return expr->SetAndAdjustType(checker, baseType->AsETSTupleType()->GetWrapperType()); } if (baseType->IsETSFunctionType()) { @@ -3022,6 +3022,8 @@ checker::Type *ETSAnalyzer::Check(ir::ForOfStatement *const st) const elemType = checker->GlobalCharBuiltinType(); } else if (exprType->IsETSArrayType() || exprType->IsETSResizableArrayType()) { elemType = checker->GetElementTypeOfArray(exprType); + } else if (exprType->IsETSTupleType()) { + elemType = checker->GetGlobalTypesHolder()->GlobalETSUnionUndefinedNullObject(); } else if (exprType->IsETSObjectType() || exprType->IsETSUnionType() || exprType->IsETSTypeParameter()) { elemType = st->CheckIteratorMethod(checker); } diff --git a/ets2panda/compiler/lowering/ets/objectIterator.cpp b/ets2panda/compiler/lowering/ets/objectIterator.cpp index c3136f256e52160a0174d63b0f35d44e74cd005e..753493743cc80847bea4eb35a27f348f42fc57b9 100644 --- a/ets2panda/compiler/lowering/ets/objectIterator.cpp +++ b/ets2panda/compiler/lowering/ets/objectIterator.cpp @@ -38,6 +38,7 @@ #include "compiler/lowering/scopesInit/scopesInitPhase.h" #include "checker/ETSchecker.h" #include "util/options.h" +#include "checker/types/ets/etsTupleType.h" namespace ark::es2panda::compiler { @@ -102,7 +103,7 @@ checker::Type *FindInstantiatedTypeParamFromIterator(checker::ETSObjectType *ito return nullptr; } -static ir::OpaqueTypeNode *FindIterValueType(checker::ETSObjectType *type, ArenaAllocator *allocator) +static checker::Type *FindIterValueType(const checker::ETSObjectType *type) { auto *itor = type->GetProperty(compiler::Signatures::ITERATOR_METHOD, checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD | @@ -117,8 +118,44 @@ static ir::OpaqueTypeNode *FindIterValueType(checker::ETSObjectType *type, Arena } } ES2PANDA_ASSERT(itorReturnType); - auto *valueType = FindInstantiatedTypeParamFromIterator(itorReturnType); - return allocator->New(valueType, allocator); + return FindInstantiatedTypeParamFromIterator(itorReturnType); +} + +static checker::Type *GetIterationType(public_lib::Context *ctx, const checker::Type *exprType, ir::AstNode *node) +{ + // find $_iterator->ReturnType->Iterator->number + // we cannot simply use next().value! , because value itself maybe undefined or null + if (exprType->IsETSTupleType()) { + return FindIterValueType(exprType->AsETSTupleType()->GetWrapperType()); + } + + if (exprType->IsETSObjectType()) { + return FindIterValueType(exprType->AsETSObjectType()); + } + + if (!exprType->IsETSUnionType()) { + return nullptr; + } + + ES2PANDA_ASSERT(exprType->IsETSUnionType()); + auto checker = ctx->GetChecker()->AsETSChecker(); + auto unionType = exprType->AsETSUnionType(); + checker::Type *iteratorTypes = nullptr; + for (auto type : unionType->ConstituentTypes()) { + if (iteratorTypes == nullptr) { + iteratorTypes = GetIterationType(ctx, type, node); + continue; + } + if (!checker->IsTypeIdenticalTo(GetIterationType(ctx, type, node), iteratorTypes)) { + if (std::any_of(unionType->ConstituentTypes().begin(), unionType->ConstituentTypes().end(), + [](auto helperType) { return helperType->IsETSTupleType(); })) { + return checker->TypeError(type->Variable(), diagnostic::ERROR_ARKTS_INCOMPATIBLE_UNION_IN_FOROF, + {unionType}, node->Start()); + } + return nullptr; + } + } + return iteratorTypes; } ir::Statement *ObjectIteratorLowering::ProcessObjectIterator(public_lib::Context *ctx, @@ -136,14 +173,11 @@ ir::Statement *ObjectIteratorLowering::ProcessObjectIterator(public_lib::Context ir::Identifier *const nextIdent = Gensym(allocator); ir::Identifier *loopVariableIdent = nullptr; - // find $_iterator->ReturnType->Iterator->number - // we cannot simply use next().value! , because value itself maybe undefined or null - ir::AstNode *typeNode; - auto exprType = forOfStatement->Right()->TsType(); - if (!exprType->IsETSObjectType()) { + auto iterationType = GetIterationType(ctx, forOfStatement->Right()->TsType(), forOfStatement->Right()); + if (iterationType == nullptr || iterationType->IsTypeError()) { return forOfStatement; } - typeNode = FindIterValueType(exprType->AsETSObjectType(), allocator); + ir::AstNode *typeNode = allocator->New(iterationType, allocator); // Replace the for-of loop with the while loop using the provided iterator interface std::string whileStatement = "let @@I1 = (@@E2)." + std::string {compiler::Signatures::ITERATOR_METHOD} + "(); "; @@ -189,8 +223,8 @@ ir::Statement *ObjectIteratorLowering::ProcessObjectIterator(public_lib::Context bool ObjectIteratorLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) { auto hasIterator = [](checker::Type const *const exprType) -> bool { - return exprType != nullptr && - ((exprType->IsETSObjectType() && !exprType->IsETSStringType()) || exprType->IsETSTypeParameter()); + return (exprType->IsETSObjectType() && !exprType->IsETSStringType()) || exprType->IsETSTupleType() || + exprType->IsETSTypeParameter(); }; program->Ast()->TransformChildrenRecursively( @@ -199,8 +233,9 @@ bool ObjectIteratorLowering::PerformForModule(public_lib::Context *ctx, parser:: // clang-format on if (ast->IsForOfStatement()) { if (auto const *const exprType = ast->AsForOfStatement()->Right()->TsType(); - hasIterator(exprType) || (exprType != nullptr && exprType->IsETSUnionType() && - exprType->AsETSUnionType()->AllOfConstituentTypes(hasIterator))) { + exprType != nullptr && + (hasIterator(exprType) || + (exprType->IsETSUnionType() && exprType->AsETSUnionType()->AllOfConstituentTypes(hasIterator)))) { return ProcessObjectIterator(ctx, ast->AsForOfStatement()); } } diff --git a/ets2panda/compiler/lowering/ets/unionLowering.cpp b/ets2panda/compiler/lowering/ets/unionLowering.cpp index 61cacd86a4682c78ed169b096ee0bc69b5ce8015..1dae0831af128cd8951a942666abb3ac74841271 100644 --- a/ets2panda/compiler/lowering/ets/unionLowering.cpp +++ b/ets2panda/compiler/lowering/ets/unionLowering.cpp @@ -36,7 +36,12 @@ std::string GetAccessClassName(const checker::ETSUnionType *unionType) { std::stringstream ss; ss << PREFIX; - unionType->ToString(ss, false); + for (auto it = unionType->ConstituentTypes().begin(); it != unionType->ConstituentTypes().end(); it++) { + (*it)->ToAssemblerType(ss); + if (std::next(it) != unionType->ConstituentTypes().end()) { + ss << "|"; + } + } std::string res(ss.str()); std::replace(res.begin(), res.end(), '.', '-'); std::replace(res.begin(), res.end(), '|', '_'); diff --git a/ets2panda/ir/expressions/memberExpression.cpp b/ets2panda/ir/expressions/memberExpression.cpp index 2ea5a0d8472cd3c0e10310e1f093841c896e51ff..e10ed7aa3f6ab731f2903a60e7d8e605f2374cc7 100644 --- a/ets2panda/ir/expressions/memberExpression.cpp +++ b/ets2panda/ir/expressions/memberExpression.cpp @@ -223,7 +223,8 @@ checker::Type *MemberExpression::TraverseUnionMember(checker::ETSChecker *checke }; for (auto *const type : unionType->ConstituentTypes()) { - auto *const apparent = checker->GetApparentType(type); + auto *apparent = type->IsETSTupleType() ? checker->GetApparentType(type)->AsETSTupleType()->GetWrapperType() + : checker->GetApparentType(type); if (apparent->IsETSObjectType()) { SetObjectType(apparent->AsETSObjectType()); addPropType(ResolveObjectMember(checker).first); diff --git a/ets2panda/ir/statements/forOfStatement.cpp b/ets2panda/ir/statements/forOfStatement.cpp index 953d2822686fed5db2a26f1b66b7ea3dd97210e8..a0f9e587657350bcb424a487415e2822d2c9ddfd 100644 --- a/ets2panda/ir/statements/forOfStatement.cpp +++ b/ets2panda/ir/statements/forOfStatement.cpp @@ -33,11 +33,12 @@ checker::Type *ForOfStatement::CreateUnionIteratorTypes(checker::ETSChecker *che } else if (it->IsETSArrayType()) { types.emplace_back(it->AsETSArrayType()->ElementType()->Clone(checker)); types.back()->RemoveTypeFlag(checker::TypeFlag::CONSTANT); + } else if (it->IsETSTupleType()) { + types.push_back(checker->GetGlobalTypesHolder()->GlobalETSUnionUndefinedNullObject()); } else { return nullptr; } } - return checker->CreateETSUnionType(std::move(types)); } diff --git a/ets2panda/test/ast/compiler/ets/iterableTupleInUnion_n.ets b/ets2panda/test/ast/compiler/ets/iterableTupleInUnion_n.ets new file mode 100644 index 0000000000000000000000000000000000000000..1b606adc403195ad597fa8bccb55e18826ec1d15 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/iterableTupleInUnion_n.ets @@ -0,0 +1,39 @@ +/* + * 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(a: boolean): [number, string] | [number, string, int] | Int[]{ + if (a){ + let tuple2 :[number, string] = [1.0 ,"str"]; + return tuple2; + } + let tuple3 :[number, string, int] = [1.0 ,"str",1]; + return tuple3; +} + +function main(): void { + for(let a of foo(true)){ + if(a instanceof Number) { + assertEQ(a, 1.0); + } + if(a instanceof string) { + assertEQ(a, "str"); + } + if(a instanceof Int){ + assertTrue(false); + } + } +} + +/* @@? 26:18 Error TypeError: Union [double, String]|[double, String, int]|Array contains incompatible types for iteration. */ diff --git a/ets2panda/test/runtime/ets/iterableTuple.ets b/ets2panda/test/runtime/ets/iterableTuple.ets new file mode 100644 index 0000000000000000000000000000000000000000..28dd836addbba0ffde826744eb8fb09d56b42ba6 --- /dev/null +++ b/ets2panda/test/runtime/ets/iterableTuple.ets @@ -0,0 +1,57 @@ +/* + * 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(a:T): [number, A | null, T, ()=>void, B, string] { + return [1.0,null,a,()=>{}, new B(),"str"]; +} + +class A{} +class B{} + +function main(): void { + let tuple:[number, A | null, ()=>void, string] = [1.0,null,()=>{},"str"] + assertTrue(tuple.$_iterator() instanceof Iterator) + + let iterator = 0 + for(let a of foo(new Long(1000))) { + if(a instanceof Number) { + assertEQ(a, 1.0); + assertEQ(iterator++,0); + continue; + } + if(a instanceof A | null) { + assertEQ(a,null); + assertEQ(iterator++,1); + continue; + } + if(a instanceof Long) { + assertEQ(a, 1000); + assertEQ(iterator++,2); + continue; + } + if(a instanceof ()=>void) { + assertEQ(iterator++,3); + continue; + } + if(a instanceof B) { + assertEQ(iterator++,4); + continue; + } + if(a instanceof string) { + assertEQ(a, "str"); + assertEQ(iterator++,5); + } + } + assertEQ(iterator,6); +} diff --git a/ets2panda/test/runtime/ets/iterableTupleInUnion.ets b/ets2panda/test/runtime/ets/iterableTupleInUnion.ets new file mode 100644 index 0000000000000000000000000000000000000000..e274164c27392fb9cb327620e09530ac65f75a63 --- /dev/null +++ b/ets2panda/test/runtime/ets/iterableTupleInUnion.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. + */ +function foo(a: boolean): [number, string] | [number, string, int] { + if (a){ + let tuple2 :[number, string] = [1.0 ,"str"]; + return tuple2; + } + let tuple3 :[number, string, int] = [1.0 ,"str",1]; + return tuple3; +} + + +function main(): void { + for(let a of foo(true)){ + if(a instanceof Number) { + assertEQ(a, 1.0); + } + if(a instanceof string) { + assertEQ(a, "str"); + } + if(a instanceof Int){ + assertTrue(false); + } + } +} diff --git a/ets2panda/util/diagnostic/semantic.yaml b/ets2panda/util/diagnostic/semantic.yaml index 568a3b73128a4625368f0bb392f21c0762ebe352..837f0a9270b5bd1b8d543814c1e196ca56cf50d2 100644 --- a/ets2panda/util/diagnostic/semantic.yaml +++ b/ets2panda/util/diagnostic/semantic.yaml @@ -1498,3 +1498,7 @@ semantic: - name: INTERFACE_EXTENDS_CLASS id: 373 message: "Interfaces cannot extend classes, only other interfaces." + +- name: ERROR_ARKTS_INCOMPATIBLE_UNION_IN_FOROF + id: 374 + message: "Union {} contains incompatible types for iteration."