diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index cc87b76f3415a275a203b6ca591e1364e7951a92..1dd2e8e6a2215c5d5b8cd6cf4a164eb4c67e3560 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -705,7 +705,8 @@ static bool ValidArrayExprSizeForTupleSize(ETSChecker *checker, Type *possibleTu possibleTupleType->AsETSTupleType()); } -static ArenaVector> GetElementTypes(ETSChecker *checker, ir::ArrayExpression *expr) +static ArenaVector> GetElementTypes(ETSChecker *checker, ir::ArrayExpression *expr, + bool setPreferredType = true) { ArenaVector> elementTypes(checker->ProgramAllocator()->Adapter()); @@ -727,7 +728,7 @@ static ArenaVector> GetElementTypes(ETSCheck continue; } - if (element->IsArrayExpression() || element->IsObjectExpression()) { + if (setPreferredType && (element->IsArrayExpression() || element->IsObjectExpression())) { auto *const targetPreferredType = exprPreferredType->IsETSTupleType() ? exprPreferredType->AsETSTupleType()->GetTypeAtIndex(idx) : checker->GetElementTypeOfArray(exprPreferredType); @@ -750,7 +751,8 @@ static Type *GetArrayElementType(ETSChecker *checker, Type *preferredType) } static bool CheckElement(ETSChecker *checker, Type *const preferredType, - ArenaVector> arrayExprElementTypes, std::size_t idx) + ArenaVector> arrayExprElementTypes, std::size_t idx, + bool isThrowError = true) { auto [elementType, currentElement] = arrayExprElementTypes[idx]; @@ -792,8 +794,10 @@ static bool CheckElement(ETSChecker *checker, Type *const preferredType, auto ctx = AssignmentContext(checker->Relation(), currentElement, elementType, targetType, currentElement->Start(), {}, TypeRelationFlag::NO_THROW); if (!ctx.IsAssignable()) { - checker->LogError(diagnostic::ARRAY_ELEMENT_INIT_TYPE_INCOMPAT, {idx, elementType, targetType}, - currentElement->Start()); + if (isThrowError) { + checker->LogError(diagnostic::ARRAY_ELEMENT_INIT_TYPE_INCOMPAT, {idx, elementType, targetType}, + currentElement->Start()); + } return false; } @@ -820,6 +824,10 @@ static Type *InferPreferredTypeFromElements(ETSChecker *checker, ir::ArrayExpres arrayExpressionElementTypes.emplace_back(elementType); } + if (arrayExpressionElementTypes.empty()) { + return nullptr; + } + // NOTE (smartin): fix union type normalization. Currently for primitive types like a 'char | char' type, it will be // normalized to 'Char'. However it shouldn't be boxed, and be kept as 'char'. For a quick fix, if all types are // primitive, then after making the union type, explicitly unbox it. @@ -849,21 +857,75 @@ static bool CheckArrayExpressionElements(ETSChecker *checker, ir::ArrayExpressio return allElementsAssignable; } +static Type *GetArrayExprUnionPreferredType(ETSChecker *checker, ir::Expression *expr, const ArenaVector &types) +{ + if (types.empty()) { + return nullptr; + } + + if (types.size() == 1) { + return types[0]; + } + + if (!expr->IsArrayExpression()) { + return nullptr; + } + + auto arrayExpr = expr->AsArrayExpression(); + auto inferredPreferredType = InferPreferredTypeFromElements(checker, arrayExpr); + if (inferredPreferredType == nullptr) { + return nullptr; + } + + bool isIdenticalType = false; + for (checker::Type *type : types) { + if (checker->Relation()->IsIdenticalTo(type, inferredPreferredType)) { + isIdenticalType = true; + break; + } + } + if (isIdenticalType) { + return inferredPreferredType; + } + + ArenaVector availableTypes(checker->Allocator()->Adapter()); + const ArenaVector> arrayExprElements = + GetElementTypes(checker, arrayExpr, false); + for (checker::Type *type : types) { + bool isAssignable = true; + for (std::size_t idx = 0; idx < arrayExprElements.size(); ++idx) { + isAssignable = CheckElement(checker, type, arrayExprElements, idx, false); + if (!isAssignable) { + break; + } + } + if (isAssignable) { + availableTypes.push_back(type); + } + } + + if (availableTypes.size() == 1) { + return availableTypes[0]; + } + return nullptr; +} + void ETSAnalyzer::GetUnionPreferredType(ir::Expression *expr, Type *originalType) const { if (originalType == nullptr || !originalType->IsETSUnionType()) { return; } + ETSChecker *checker = GetETSChecker(); checker::Type *preferredType = nullptr; + ArenaVector availableTypes(checker->Allocator()->Adapter()); for (auto &type : originalType->AsETSUnionType()->ConstituentTypes()) { if (type->IsETSArrayType() || type->IsETSTupleType() || type->IsETSResizableArrayType()) { - if (preferredType != nullptr) { - preferredType = nullptr; - break; - } - preferredType = type; + availableTypes.push_back(type); } } + + preferredType = GetArrayExprUnionPreferredType(checker, expr, availableTypes); + if (expr->IsArrayExpression()) { expr->AsArrayExpression()->SetPreferredType(preferredType); } else if (expr->IsETSNewArrayInstanceExpression()) { diff --git a/ets2panda/ir/expressions/arrayExpression.cpp b/ets2panda/ir/expressions/arrayExpression.cpp index cc9ec6588560011295baac64bb84f597b5168aae..88fcd356a99a0cfadf446af4f978d33690adf56b 100644 --- a/ets2panda/ir/expressions/arrayExpression.cpp +++ b/ets2panda/ir/expressions/arrayExpression.cpp @@ -411,19 +411,25 @@ checker::VerifiedType ArrayExpression::Check(checker::ETSChecker *checker) return {this, checker->GetAnalyzer()->Check(this)}; } -std::optional ArrayExpression::ExtractPossiblePreferredType(checker::Type *type) +std::optional ArrayExpression::ExtractPossiblePreferredType(checker::ETSChecker *checker, + checker::Type *type) { if (type->IsETSArrayType() || type->IsETSTupleType() || type->IsETSResizableArrayType()) { return std::make_optional(type); } if (type->IsETSUnionType()) { + ArenaVector availableTypes(checker->Allocator()->Adapter()); for (checker::Type *const typeOfUnion : type->AsETSUnionType()->ConstituentTypes()) { - auto possiblePreferredType = ExtractPossiblePreferredType(typeOfUnion); + auto possiblePreferredType = ExtractPossiblePreferredType(checker, typeOfUnion); if (possiblePreferredType.has_value()) { - return std::make_optional(possiblePreferredType.value()); + availableTypes.push_back(possiblePreferredType.value()); } } + if (!availableTypes.empty()) { + auto unionType = checker->CreateETSUnionType(std::move(availableTypes)); + return std::make_optional(unionType); + } } return std::nullopt; @@ -437,13 +443,13 @@ void ArrayExpression::SetPreferredTypeBasedOnFuncParam(checker::ETSChecker *chec return; } - auto possiblePreferredType = ExtractPossiblePreferredType(param); + auto possiblePreferredType = ExtractPossiblePreferredType(checker, param); if (!possiblePreferredType.has_value()) { return; } param = possiblePreferredType.value(); - if (param->IsETSTupleType()) { + if (param->IsETSTupleType() || param->IsETSUnionType()) { preferredType_ = param; return; } diff --git a/ets2panda/ir/expressions/arrayExpression.h b/ets2panda/ir/expressions/arrayExpression.h index 2af3767f2b671de1b0f2120afad0a66256e950b0..8b5d1fa2424e7df9fae130c2beabf30b57121032 100644 --- a/ets2panda/ir/expressions/arrayExpression.h +++ b/ets2panda/ir/expressions/arrayExpression.h @@ -157,7 +157,8 @@ public: { v->Accept(this); } - static std::optional ExtractPossiblePreferredType(checker::Type *type); + static std::optional ExtractPossiblePreferredType(checker::ETSChecker *checker, + checker::Type *type); void SetPreferredTypeBasedOnFuncParam(checker::ETSChecker *checker, checker::Type *param, checker::TypeRelationFlag flags); diff --git a/ets2panda/test/ast/parser/ets/union_array_cte.ets b/ets2panda/test/ast/parser/ets/union_array_cte.ets new file mode 100644 index 0000000000000000000000000000000000000000..1e7af7c0db9a4c9f5cbe5762ea209a02744399e7 --- /dev/null +++ b/ets2panda/test/ast/parser/ets/union_array_cte.ets @@ -0,0 +1,28 @@ +/* + * 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 a : Number[]|[Number, Number]|[Int, Int]|null +let b : Int[]|[Number, Number]|string[]|null +let c : Number[]|Byte[]|null + +function main() { + a = [1, 2] + b = [1.0, 2.0] + c = [1, 2] +} + +/* @@? 21:9 Error TypeError: Type 'Array' cannot be assigned to type 'Array|[Double, Double]|[Int, Int]|null' */ +/* @@? 22:9 Error TypeError: Type 'Array' cannot be assigned to type 'Array|[Double, Double]|Array|null' */ +/* @@? 23:9 Error TypeError: Type 'Array' cannot be assigned to type 'Array|Array|null' */ diff --git a/ets2panda/test/runtime/ets/union_array.ets b/ets2panda/test/runtime/ets/union_array.ets new file mode 100644 index 0000000000000000000000000000000000000000..97ea56afa7d89257d7290b1315cc4b90567fa590 --- /dev/null +++ b/ets2panda/test/runtime/ets/union_array.ets @@ -0,0 +1,32 @@ +/* + * 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. + */ + +class X {} +let a : Number[]|String[]|Boolean[]|null +let b : Int[]|String[]|Boolean[]|null +let c : String[]|char[]|Boolean[]|null +let d : Number[]|FixedArray|X|()=>void|null|[Number, String] + +function main() { + a = [1, 2, 3] + b = [1.0, 2.0, 3.0] + c = ["1", "2", "3"] + d = [1.0, 2.0] + + assertEQ((a as Number[])[0], 1) + assertEQ((b as Int[])[0], 1) + assertEQ((c as String[])[0], "1") + assertEQ((d as Number[])[0], 1) +} diff --git a/ets2panda/test/runtime/ets/union_array_catch.ets b/ets2panda/test/runtime/ets/union_array_catch.ets new file mode 100644 index 0000000000000000000000000000000000000000..62618b60072d6fdb039ca5950b68b1d1967f2b48 --- /dev/null +++ b/ets2panda/test/runtime/ets/union_array_catch.ets @@ -0,0 +1,26 @@ +/* + * 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 a: Array|Array|Array|null + +function main() { + a = [1, 2, 43] + try { + assertEQ((a as Array)[0], 1) // a should be Array + assertTrue(false, 'should throw ClassCastError') + } catch (e) { + assertTrue(e instanceof ClassCastError, 'should throw ClassCastError') + } +}