From f75c1db94fe3970779c5b03d861e6fd5113f8afe Mon Sep 17 00:00:00 2001 From: Zelentsov Dmitry Date: Thu, 15 May 2025 10:49:32 +0300 Subject: [PATCH] Fix error in Nullish Coalescing operator check Signed-off-by: Zelentsov Dmitry --- ets2panda/checker/ets/arithmetic.cpp | 102 +++++++++++++++++- ets2panda/checker/types/ets/etsEnumType.cpp | 9 +- ets2panda/checker/types/ets/etsUnionType.cpp | 10 +- ets2panda/checker/types/type.cpp | 5 + ets2panda/checker/types/type.h | 4 +- .../lowering/ets/enumPostCheckLowering.cpp | 4 +- .../compiler/lowering/ets/recordLowering.cpp | 3 +- ets2panda/lexer/token/number.h | 11 +- .../compiler/ets/null_coalescing_neg_01.ets | 52 +++++++++ .../test/runtime/ets/NullishCoalescing_02.ets | 72 +++++++++++++ 10 files changed, 248 insertions(+), 24 deletions(-) create mode 100644 ets2panda/test/ast/compiler/ets/null_coalescing_neg_01.ets create mode 100644 ets2panda/test/runtime/ets/NullishCoalescing_02.ets diff --git a/ets2panda/checker/ets/arithmetic.cpp b/ets2panda/checker/ets/arithmetic.cpp index 5b54974f4..81a900272 100644 --- a/ets2panda/checker/ets/arithmetic.cpp +++ b/ets2panda/checker/ets/arithmetic.cpp @@ -15,10 +15,8 @@ #include "arithmetic.h" -#include "checker/types/ts/nullType.h" #include "checker/types/globalTypesHolder.h" #include "lexer/token/token.h" -#include "checker/types/ets/etsEnumType.h" namespace ark::es2panda::checker { @@ -30,7 +28,7 @@ struct BinaryArithmOperands { checker::Type *reducedR; }; -static inline BinaryArithmOperands GetBinaryOperands(ETSChecker *checker, ir::BinaryExpression *expr) +static BinaryArithmOperands GetBinaryOperands(ETSChecker *checker, ir::BinaryExpression *expr) { auto typeL = expr->Left()->Check(checker); auto typeR = expr->Right()->Check(checker); @@ -815,6 +813,101 @@ std::tuple ETSChecker::CheckBinaryOperatorInstanceOf(lexer::Sour return {GlobalETSBooleanBuiltinType(), opType}; } +template +static void ConvertNumberLiteralTo(ir::NumberLiteral *lit, Type *toType) +{ + auto &number = lit->Number(); + number.SetValue(number.GetValueAndCastTo()); + toType->AddTypeFlag(TypeFlag::CONSTANT); + lit->SetTsType(toType); +} + +template +static bool CheckNumberLiteralValue(ETSChecker *checker, ir::NumberLiteral const *const lit) +{ + auto const maxTo = static_cast(std::numeric_limits::max()); + auto const minTo = static_cast(std::numeric_limits::min()); + auto const val = lit->Number().GetValue(); + if (val < minTo || val > maxTo) { + checker->LogError(diagnostic::CONSTANT_VALUE_OUT_OF_RANGE, {}, lit->Start()); + return false; + } + return true; +} + +// Just to reduce the size of 'ConvertNumberLiteral' +static void ConvertIntegerNumberLiteral(ETSChecker *checker, ir::NumberLiteral *lit, ETSObjectType *fromType, + ETSObjectType *toType) +{ + if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG)) { + ConvertNumberLiteralTo(lit, checker->GlobalLongBuiltinType()->Clone(checker)); + } else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT)) { + if (!fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) || + CheckNumberLiteralValue(checker, lit)) { + ConvertNumberLiteralTo(lit, checker->GlobalIntBuiltinType()->Clone(checker)); + } + } else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_SHORT)) { + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) && + !CheckNumberLiteralValue(checker, lit)) { + return; + } + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT) && + !CheckNumberLiteralValue(checker, lit)) { + return; + } + ConvertNumberLiteralTo(lit, checker->GlobalShortBuiltinType()->Clone(checker)); + } else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_BYTE)) { + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) && + !CheckNumberLiteralValue(checker, lit)) { + return; + } + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT) && + !CheckNumberLiteralValue(checker, lit)) { + return; + } + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_SHORT) && + !CheckNumberLiteralValue(checker, lit)) { + return; + } + ConvertNumberLiteralTo(lit, checker->GlobalByteBuiltinType()->Clone(checker)); + } +} + +static void ConvertNumberLiteral(ETSChecker *checker, ir::NumberLiteral *lit, ETSObjectType *toType) +{ + ES2PANDA_ASSERT(toType->IsBuiltinNumeric() && lit->TsType()->IsBuiltinNumeric()); + + if (auto *fromType = lit->TsType()->AsETSObjectType(); !checker->Relation()->IsIdenticalTo(fromType, toType)) { + switch (static_cast(toType->ObjectFlags() & ETSObjectFlags::BUILTIN_NUMERIC)) { + case ETSObjectFlags::BUILTIN_DOUBLE: + ConvertNumberLiteralTo(lit, checker->GlobalDoubleBuiltinType()->Clone(checker)); + break; + + case ETSObjectFlags::BUILTIN_FLOAT: + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_DOUBLE)) { + checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {fromType, toType}, lit->Start()); + } else { + ConvertNumberLiteralTo(lit, checker->GlobalFloatBuiltinType()->Clone(checker)); + } + break; + + case ETSObjectFlags::BUILTIN_LONG: + case ETSObjectFlags::BUILTIN_INT: + case ETSObjectFlags::BUILTIN_SHORT: + case ETSObjectFlags::BUILTIN_BYTE: + if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_FLOATING_POINT)) { + checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {fromType, toType}, lit->Start()); + } else { + ConvertIntegerNumberLiteral(checker, lit, fromType, toType); + } + break; + + default: + ES2PANDA_UNREACHABLE(); + } + } +} + Type *ETSChecker::CheckBinaryOperatorNullishCoalescing(ir::Expression *left, ir::Expression *right, lexer::SourcePosition pos) { @@ -830,7 +923,8 @@ Type *ETSChecker::CheckBinaryOperatorNullishCoalescing(ir::Expression *left, ir: } // If possible and required update number literal type to the proper value (identical to left-side type) - if (right->IsNumberLiteral()) { + if (right->IsNumberLiteral() && leftType->IsBuiltinNumeric()) { + ConvertNumberLiteral(this, right->AsNumberLiteral(), leftType->AsETSObjectType()); return leftType; } diff --git a/ets2panda/checker/types/ets/etsEnumType.cpp b/ets2panda/checker/types/ets/etsEnumType.cpp index 61d23b7cb..bea6d5044 100644 --- a/ets2panda/checker/types/ets/etsEnumType.cpp +++ b/ets2panda/checker/types/ets/etsEnumType.cpp @@ -77,7 +77,7 @@ bool ETSIntEnumType::AssignmentSource(TypeRelation *relation, Type *target) if (target->AsETSObjectType()->IsGlobalETSObjectType() || target->AsETSObjectType()->Name() == compiler::Signatures::NUMERIC) { result = true; - } else if (target->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC)) { + } else if (target->IsBuiltinNumeric()) { result = true; relation->GetNode()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF); } @@ -108,8 +108,7 @@ void ETSIntEnumType::Cast(TypeRelation *const relation, Type *const target) relation->Result(true); return; } - if (target->HasTypeFlag(TypeFlag::ETS_NUMERIC) || - (target->IsETSObjectType() && target->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC))) { + if (target->HasTypeFlag(TypeFlag::ETS_NUMERIC) || target->IsBuiltinNumeric()) { relation->Result(true); return; } @@ -122,11 +121,11 @@ void ETSIntEnumType::CastTarget(TypeRelation *relation, Type *source) relation->Result(true); return; } - if (source->IsETSObjectType() && source->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC)) { + if (source->IsBuiltinNumeric()) { relation->Result(true); return; } conversion::Forbidden(relation); } -} // namespace ark::es2panda::checker \ No newline at end of file +} // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsUnionType.cpp b/ets2panda/checker/types/ets/etsUnionType.cpp index d44796d81..2e6777a45 100644 --- a/ets2panda/checker/types/ets/etsUnionType.cpp +++ b/ets2panda/checker/types/ets/etsUnionType.cpp @@ -314,7 +314,7 @@ checker::Type *ETSUnionType::GetAssignableType(checker::ETSChecker *checker, che ES2PANDA_ASSERT(sourceType->IsETSObjectType()); auto *objectType = checker->GetNonConstantType(sourceType)->AsETSObjectType(); - if (!objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC)) { + if (!objectType->IsBuiltinNumeric()) { return sourceType; } @@ -390,7 +390,7 @@ checker::Type *ETSUnionType::GetAssignableBuiltinType( } ETSObjectType *objectType = constituentType->AsETSObjectType(); - if (!objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC)) { + if (!objectType->IsBuiltinNumeric()) { continue; } @@ -434,8 +434,7 @@ bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::Type *sour if (checker->Relation()->IsSupertypeOf(constituentType, source)) { rc = true; - } else if (!rc && constituentType->IsETSObjectType() && - constituentType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC)) { + } else if (!rc && constituentType->IsBuiltinNumeric()) { if (auto const id = ETSObjectType::GetPrecedence(checker, constituentType->AsETSObjectType()); id > 0U) { numericTypes.emplace(id, it); } @@ -448,8 +447,7 @@ bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::Type *sour return true; } - if (source->IsETSObjectType() && source->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC) && - !numericTypes.empty()) { + if (source->IsBuiltinNumeric() && !numericTypes.empty()) { unionTypes.erase((*std::prev(numericTypes.end())).second); return true; } diff --git a/ets2panda/checker/types/type.cpp b/ets2panda/checker/types/type.cpp index e6cfd05a7..14f74f2b1 100644 --- a/ets2panda/checker/types/type.cpp +++ b/ets2panda/checker/types/type.cpp @@ -47,6 +47,11 @@ bool Type::IsETSAsyncFuncReturnType() const return IsETSObjectType() && AsETSObjectType()->HasObjectFlag(ETSObjectFlags::ASYNC_FUNC_RETURN_TYPE); } +bool Type::IsBuiltinNumeric() const noexcept +{ + return IsETSObjectType() && AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC); +} + bool Type::IsLambdaObject() const { if (IsETSObjectType() && (AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::FUNCTIONAL_INTERFACE) || diff --git a/ets2panda/checker/types/type.h b/ets2panda/checker/types/type.h index dcf517bce..4a2f68d5c 100644 --- a/ets2panda/checker/types/type.h +++ b/ets2panda/checker/types/type.h @@ -140,11 +140,13 @@ public: return reinterpret_cast(this); } - bool IsETSDynamicType() const + [[nodiscard]] bool IsETSDynamicType() const noexcept { return IsETSObjectType() && HasTypeFlag(TypeFlag::ETS_DYNAMIC_FLAG); } + [[nodiscard]] bool IsBuiltinNumeric() const noexcept; + ETSDynamicType *AsETSDynamicType() { ES2PANDA_ASSERT(IsETSDynamicType()); diff --git a/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp b/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp index 7a48dde93..bbbd4506e 100644 --- a/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp +++ b/ets2panda/compiler/lowering/ets/enumPostCheckLowering.cpp @@ -96,9 +96,7 @@ static EnumCastType NeedHandleEnumCasting(ir::TSAsExpression *node) } if (type->IsETSStringType()) { castType = EnumCastType::CAST_TO_STRING; - } else if (type->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC) || - (type->IsETSObjectType() && - type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_NUMERIC))) { + } else if (type->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC) || type->IsBuiltinNumeric()) { castType = EnumCastType::CAST_TO_INT; } else if (type->IsETSEnumType()) { castType = type->IsETSIntEnumType() ? EnumCastType::CAST_TO_INT_ENUM : EnumCastType::CAST_TO_STRING_ENUM; diff --git a/ets2panda/compiler/lowering/ets/recordLowering.cpp b/ets2panda/compiler/lowering/ets/recordLowering.cpp index 866cfa1b0..81799f2fc 100644 --- a/ets2panda/compiler/lowering/ets/recordLowering.cpp +++ b/ets2panda/compiler/lowering/ets/recordLowering.cpp @@ -156,10 +156,9 @@ void RecordLowering::CheckKeyType(checker::ETSChecker *checker, checker::Type co ir::ObjectExpression const *const expr, public_lib::Context *ctx) const noexcept { if (keyType->IsETSObjectType()) { - if (keyType->IsETSStringType() || + if (keyType->IsETSStringType() || keyType->IsBuiltinNumeric() || checker->Relation()->IsIdenticalTo(keyType, checker->GetGlobalTypesHolder()->GlobalNumericBuiltinType()) || checker->Relation()->IsIdenticalTo(keyType, checker->GetGlobalTypesHolder()->GlobalIntegralBuiltinType()) || - keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_NUMERIC) || keyType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::ENUM_OBJECT)) { return; } diff --git a/ets2panda/lexer/token/number.h b/ets2panda/lexer/token/number.h index 7a79eea77..1e053da19 100644 --- a/ets2panda/lexer/token/number.h +++ b/ets2panda/lexer/token/number.h @@ -155,8 +155,13 @@ public: float GetFloat() const { - ES2PANDA_ASSERT(IsFloat()); - return std::get(num_); + return std::visit(overloaded {[](float value) { return value; }, + [](int64_t value) { return static_cast(value); }, + [](int32_t value) { return static_cast(value); }, + [](int16_t value) { return static_cast(value); }, + [](int8_t value) { return static_cast(value); }, + []([[maybe_unused]] auto value) -> float { ES2PANDA_UNREACHABLE(); }}, + num_); } double GetDouble() const @@ -210,7 +215,7 @@ public: } else if constexpr (std::is_same_v) { return true; } else if constexpr (std::is_same_v) { - return IsFloat(); + return IsInteger() || IsFloat(); } else if constexpr (std::is_same_v) { return IsShort() || IsByte(); } else if constexpr (std::is_same_v) { diff --git a/ets2panda/test/ast/compiler/ets/null_coalescing_neg_01.ets b/ets2panda/test/ast/compiler/ets/null_coalescing_neg_01.ets new file mode 100644 index 000000000..095090df6 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/null_coalescing_neg_01.ets @@ -0,0 +1,52 @@ +/* + * 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(z: int|undefined) { + let x = z ?? /* @@ label1 */10000000000; + return x; +} + +function foo2(z: short|undefined) { + let x = z ?? /* @@ label2 */10000000; + return x; +} + +function foo3(z: byte|undefined) { + let x = z ?? /* @@ label3 */10000; + return x; +} + +function foo4(z: int|undefined) { + let x = z ?? /* @@ label4 */100.0; + return x; +} + +function foo5(z: long|undefined) { + let x = z ?? /* @@ label5 */100000.0f; + return x; +} + +function foo6(z: float|undefined) { + let x = z ?? /* @@ label6 */100000.0; + return x; +} + + +/* @@@ label1 Error TypeError: Value is out of range */ +/* @@@ label2 Error TypeError: Value is out of range */ +/* @@@ label3 Error TypeError: Value is out of range */ +/* @@@ label4 Error TypeError: Type 'Double' cannot be assigned to type 'Int' */ +/* @@@ label5 Error TypeError: Type 'Float' cannot be assigned to type 'Long' */ +/* @@@ label6 Error TypeError: Type 'Double' cannot be assigned to type 'Float' */ diff --git a/ets2panda/test/runtime/ets/NullishCoalescing_02.ets b/ets2panda/test/runtime/ets/NullishCoalescing_02.ets new file mode 100644 index 000000000..17388223a --- /dev/null +++ b/ets2panda/test/runtime/ets/NullishCoalescing_02.ets @@ -0,0 +1,72 @@ +/* + * 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(z: long|undefined) { + let x = z ?? 10000000000; + return x; +} + +function foo2(z: int|undefined) { + let x = z ?? 10000000; + return x; +} + +function foo3(z: short|undefined) { + let x = z ?? 10000; + return x; +} + +function foo4(z: byte|undefined) { + let x = z ?? 100; + return x; +} + +function foo5(z: float|undefined) { + let x = z ?? 100000; + return x; +} + +function foo6(z: float|undefined) { + let x = z ?? 100000.0f; + return x; +} + +function foo7(z: double|undefined) { + let x = z ?? 10000000; + return x; +} + +function foo8(z: double|undefined) { + let x = z ?? 10000000.0f; + return x; +} + +function foo9(z: double|undefined) { + let x = z ?? 10000000.0; + return x; +} + + +function main() { + assertEQ(foo1(undefined), 10000000000); + assertEQ(foo2(undefined), 10000000); + assertEQ(foo3(undefined), 10000); + assertEQ(foo4(undefined), 100); + assertEQ(foo5(undefined), 100000.0f); + assertEQ(foo6(undefined), 100000.0f); + assertEQ(foo7(undefined), 10000000.0); + assertEQ(foo8(undefined), 10000000.0); + assertEQ(foo9(undefined), 10000000.0); +} -- Gitee