diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 8884209deeb53ba3a8b4dd0b72fc0040d12aa4f8..f70e9d1dddfe5b7f4f244d338e90741827fcadf4 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -15,14 +15,13 @@ #include "ETSAnalyzer.h" -#include "checker/types/type.h" +#include "util/helpers.h" #include "varbinder/ETSBinder.h" #include "checker/ETSchecker.h" #include "checker/ets/castingContext.h" #include "checker/ets/typeRelationContext.h" -#include "util/helpers.h" - -#include +#include "checker/types/globalTypesHolder.h" +#include "checker/types/ets/etsTupleType.h" namespace ark::es2panda::checker { @@ -580,8 +579,10 @@ checker::Type *ETSAnalyzer::Check(ir::ArrayExpression *expr) const } const bool isPreferredTuple = expr->preferredType_->IsETSTupleType(); - auto *const targetElementType = - isPreferredTuple && !isArray ? expr->preferredType_->AsETSTupleType()->ElementType() : expr->preferredType_; + auto *targetElementType = expr->GetPreferredType(); + if (isPreferredTuple && !isArray) { + targetElementType = targetElementType->AsETSTupleType()->ElementType(); + } for (std::size_t idx = 0; idx < expr->elements_.size(); ++idx) { auto *const currentElement = expr->elements_[idx]; @@ -595,17 +596,18 @@ checker::Type *ETSAnalyzer::Check(ir::ArrayExpression *expr) const currentElement->AsObjectExpression()->SetPreferredType(expr->preferredType_); } - checker::Type *elementType = currentElement->Check(checker); + checker::Type *elementType = checker->GetApparentType(currentElement->Check(checker)); if (!elementType->IsETSArrayType() && isPreferredTuple) { - auto *const compareType = expr->preferredType_->AsETSTupleType()->GetTypeAtIndex(idx); + auto const *const tupleType = expr->GetPreferredType()->AsETSTupleType(); + auto *compareType = tupleType->GetTypeAtIndex(idx); if (compareType == nullptr) { - checker->ThrowTypeError( - {"Too many elements in array initializer for tuple with size of ", - static_cast(expr->preferredType_->AsETSTupleType()->GetTupleSize())}, - currentElement->Start()); + checker->ThrowTypeError({"Too many elements in array initializer for tuple with size of ", + static_cast(tupleType->GetTupleSize())}, + currentElement->Start()); } + compareType = checker->GetApparentType(compareType); checker::AssignmentContext( checker->Relation(), currentElement, elementType, compareType, currentElement->Start(), @@ -614,10 +616,11 @@ checker::Type *ETSAnalyzer::Check(ir::ArrayExpression *expr) const elementType = compareType; } + targetElementType = checker->GetApparentType(targetElementType); checker::AssignmentContext(checker->Relation(), currentElement, elementType, targetElementType, currentElement->Start(), {"Array element type '", elementType, "' is not assignable to explicit type '", - expr->GetPreferredType(), "'"}); + targetElementType, "'"}); } expr->SetPreferredType(targetElementType); @@ -690,15 +693,15 @@ checker::Type *ETSAnalyzer::Check(ir::ArrowFunctionExpression *expr) const return expr->TsType(); } -checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *expr) const +checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *const expr) const { - ETSChecker *checker = GetETSChecker(); - if (expr->TsType() != nullptr) { return expr->TsType(); } - auto *leftType = expr->Left()->Check(checker); + ETSChecker *checker = GetETSChecker(); + auto *const leftType = expr->Left()->Check(checker); + if (expr->Left()->IsMemberExpression() && expr->Left()->AsMemberExpression()->Object()->TsType()->IsETSArrayType() && expr->Left()->AsMemberExpression()->Property()->IsIdentifier() && @@ -718,23 +721,51 @@ checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *expr) const checker->ValidateUnaryOperatorOperand(expr->target_); } - auto [sourceType, relationNode] = CheckAssignmentExprOperatorType(expr); - const checker::Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(leftType); - const checker::Type *rightType = checker->TryGettingFunctionTypeFromInvokeFunction(sourceType); + auto *const appLeftType = checker->GetApparentType(leftType); + auto [rightType, relationNode] = CheckAssignmentExprOperatorType(expr, appLeftType); + auto *const appRightType = checker->GetApparentType(rightType); - checker::AssignmentContext(checker->Relation(), relationNode, sourceType, leftType, expr->Right()->Start(), - {"Type '", rightType, "' cannot be assigned to type '", targetType, "'"}); + const checker::Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(appLeftType); + const checker::Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(appRightType); + + checker::AssignmentContext(checker->Relation(), relationNode, appRightType, appLeftType, expr->Right()->Start(), + {"Type '", sourceType, "' cannot be assigned to type '", targetType, "'"}); + + checker::Type *smartType = leftType; + + if (expr->Left()->IsIdentifier()) { + // Now try to define the actual type of Identifier so that smart cast can be used in further checker processing + smartType = checker->ResolveSmartType(rightType, leftType); + auto const *const variable = expr->Target(); + + // Add/Remove/Modify smart cast for identifier + // (excluding the variables defined at top-level scope or captured in lambda-functions!) + auto const *const variableScope = variable->GetScope(); + auto const topLevelVariable = variableScope != nullptr + ? variableScope->IsGlobalScope() || (variableScope->Parent() != nullptr && + variableScope->Parent()->IsGlobalScope()) + : false; + + if (!topLevelVariable && !variable->HasFlag(varbinder::VariableFlags::BOXED)) { + if (checker->Relation()->IsIdenticalTo(leftType, smartType)) { + checker->Context().RemoveSmartCast(variable); + } else { + expr->Left()->SetTsType(smartType); + checker->Context().SetSmartCast(variable, smartType); + } + } + } - expr->SetTsType(expr->Left()->TsType()); + expr->SetTsType(smartType); return expr->TsType(); } -std::tuple ETSAnalyzer::CheckAssignmentExprOperatorType(ir::AssignmentExpression *expr) const +std::tuple ETSAnalyzer::CheckAssignmentExprOperatorType(ir::AssignmentExpression *expr, + Type *const leftType) const { ETSChecker *checker = GetETSChecker(); checker::Type *sourceType {}; ir::Expression *relationNode = expr->Right(); - auto *leftType = expr->Left()->Check(checker); switch (expr->OperatorType()) { case lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL: case lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL: @@ -798,14 +829,18 @@ checker::Type *ETSAnalyzer::Check(ir::AwaitExpression *expr) const checker::Type *ETSAnalyzer::Check(ir::BinaryExpression *expr) const { - ETSChecker *checker = GetETSChecker(); if (expr->TsType() != nullptr) { return expr->TsType(); } + + ETSChecker *checker = GetETSChecker(); checker::Type *newTsType {nullptr}; std::tie(newTsType, expr->operationType_) = checker->CheckBinaryOperator(expr->Left(), expr->Right(), expr, expr->OperatorType(), expr->Start()); expr->SetTsType(newTsType); + + checker->Context().CheckBinarySmartCastCondition(expr); + return expr->TsType(); } @@ -977,26 +1012,48 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSReExportDeclaration *e checker::Type *ETSAnalyzer::Check(ir::ConditionalExpression *expr) const { - ETSChecker *checker = GetETSChecker(); if (expr->TsType() != nullptr) { return expr->TsType(); } + ETSChecker *const checker = GetETSChecker(); + + SmartCastArray smartCasts = checker->Context().EnterTestExpression(); checker->CheckTruthinessOfType(expr->Test()); - auto *const consequent = expr->consequent_; - auto *const alternate = expr->alternate_; - auto *const consequentType = consequent->Check(checker); - auto *const alternateType = alternate->Check(checker); + SmartCastTypes testedTypes = checker->Context().ExitTestExpression(); + + if (testedTypes.has_value()) { + for (auto [variable, consequentType, _] : *testedTypes) { + checker->ApplySmartCast(variable, consequentType); + } + } + + auto *const consequentType = expr->Consequent()->Check(checker); + + SmartCastArray consequentSmartCasts = checker->Context().CloneSmartCasts(); + checker->Context().RestoreSmartCasts(smartCasts); + + if (testedTypes.has_value()) { + for (auto [variable, _, alternateType] : *testedTypes) { + checker->ApplySmartCast(variable, alternateType); + } + } + + auto *const alternateType = expr->Alternate()->Check(checker); + + // Here we need to combine types from consequent and alternate if blocks. + checker->Context().CombineSmartCasts(consequentSmartCasts); if (checker->IsTypeIdenticalTo(consequentType, alternateType)) { expr->SetTsType(checker->GetNonConstantTypeFromPrimitiveType(consequentType)); } else { expr->SetTsType(checker->CreateETSUnionType({consequentType, alternateType})); if (expr->TsType()->IsETSReferenceType()) { - checker->MaybeBoxExpression(consequent); - checker->MaybeBoxExpression(alternate); + checker->MaybeBoxExpression(expr->Consequent()); + checker->MaybeBoxExpression(expr->Alternate()); } } + return expr->TsType(); } @@ -1012,12 +1069,20 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::FunctionExpression *expr) checker::Type *ETSAnalyzer::Check(ir::Identifier *expr) const { - ETSChecker *checker = GetETSChecker(); - if (expr->TsType() != nullptr) { - return expr->TsType(); - } + if (expr->TsType() == nullptr) { + ETSChecker *checker = GetETSChecker(); - expr->SetTsType(checker->ResolveIdentifier(expr)); + auto *identType = checker->ResolveIdentifier(expr); + if (expr->Variable() != nullptr && (expr->Parent() == nullptr || !expr->Parent()->IsAssignmentExpression() || + expr != expr->Parent()->AsAssignmentExpression()->Left())) { + if (auto *const smartType = checker->Context().GetSmartCast(expr->Variable()); smartType != nullptr) { + identType = smartType; + } + } + expr->SetTsType(identType); + + checker->Context().CheckIdentifierSmartCastCondition(expr); + } return expr->TsType(); } @@ -1037,13 +1102,18 @@ checker::Type *ETSAnalyzer::SetAndAdjustType(ETSChecker *checker, ir::MemberExpr checker::Type *ETSAnalyzer::Check(ir::MemberExpression *expr) const { - ETSChecker *checker = GetETSChecker(); if (expr->TsType() != nullptr) { return expr->TsType(); } ASSERT(!expr->IsOptional()); - auto *const baseType = checker->GetApparentType(expr->Object()->Check(checker)); + ETSChecker *checker = GetETSChecker(); + auto *baseType = checker->GetApparentType(expr->Object()->Check(checker)); + // Note: don't use possible smart cast to null-like types. + // Such situation should be correctly resolved in the subsequent lowering. + if (baseType->DefinitelyETSNullish() && expr->Object()->IsIdentifier()) { + baseType = checker->GetApparentType(expr->Object()->AsIdentifier()->Variable()->TsType()); + } checker->CheckNonNullish(expr->Object()); if (expr->IsComputed()) { @@ -1198,11 +1268,13 @@ void ETSAnalyzer::CheckObjectExprProps(const ir::ObjectExpression *expr) const } value->SetTsType(value->Check(checker)); + auto *const valueType = checker->GetApparentType(value->TsType()); + const checker::Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(valueType); const checker::Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(propType); checker::AssignmentContext( - checker->Relation(), value, value->TsType(), propType, value->Start(), - {"Type '", value->TsType(), "' is not compatible with type '", targetType, "' at property '", pname, "'"}); + checker->Relation(), value, valueType, propType, value->Start(), + {"Type '", sourceType, "' is not compatible with type '", targetType, "' at property '", pname, "'"}); } } @@ -1296,7 +1368,7 @@ checker::Type *ETSAnalyzer::Check(ir::ThisExpression *expr) const } ``` here when "this" is used inside an extension function, we need to bind "this" to the first - parameter(MANDATORY_PARAM_THIS), and capture the paramter's variable other than containing class's variable + parameter(MANDATORY_PARAM_THIS), and capture the parameter's variable other than containing class's variable */ auto *variable = checker->AsETSChecker()->Scope()->Find(varbinder::VarBinder::MANDATORY_PARAM_THIS).variable; if (checker->HasStatus(checker::CheckerStatus::IN_INSTANCE_EXTENSION_METHOD)) { @@ -1387,6 +1459,8 @@ checker::Type *ETSAnalyzer::Check(ir::UnaryExpression *expr) const expr->Argument()->AddBoxingUnboxingFlags(checker->GetUnboxingFlag(unboxedOperandType)); } + checker->Context().CheckUnarySmartCastCondition(expr); + return expr->TsType(); } @@ -1436,6 +1510,7 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::YieldExpression *expr) co { UNREACHABLE(); } + // compile methods for LITERAL EXPRESSIONS in alphabetical order checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::BigIntLiteral *expr) const { @@ -1611,6 +1686,7 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ImportSpecifier *st) cons { UNREACHABLE(); } + // compile methods for STATEMENTS in alphabetical order checker::Type *ETSAnalyzer::Check(ir::AssertStatement *st) const { @@ -1633,15 +1709,27 @@ checker::Type *ETSAnalyzer::Check(ir::BlockStatement *st) const ETSChecker *checker = GetETSChecker(); checker::ScopeContext scopeCtx(checker, st->Scope()); - for (auto *it : st->Statements()) { - it->Check(checker); + auto it = st->Statements().begin(); + while (it != st->Statements().end()) { + (*it)->Check(checker); + + // NOTE! Processing of trailing blocks was moved here so that smart casts could be applied correctly + if (auto const tb = st->trailingBlocks_.find(*it); tb != st->trailingBlocks_.end()) { + auto *const trailingBlock = tb->second; + trailingBlock->Check(checker); + it = st->Statements().emplace(std::next(it), trailingBlock); + } + + ++it; } - for (auto [stmt, trailing_block] : st->trailingBlocks_) { - auto iterator = std::find(st->Statements().begin(), st->Statements().end(), stmt); - ASSERT(iterator != st->Statements().end()); - st->Statements().insert(iterator + 1, trailing_block); - trailing_block->Check(checker); + // Remove possible smart casts for variables declared in inner scope: + if (auto const *const scope = st->Scope(); !scope->IsGlobalScope()) { + for (auto const *const decl : scope->Decls()) { + if (decl->IsLetOrConstDecl() && decl->Node()->IsIdentifier()) { + checker->Context().RemoveSmartCast(decl->Node()->AsIdentifier()->Variable()); + } + } } return nullptr; @@ -1651,6 +1739,8 @@ checker::Type *ETSAnalyzer::Check(ir::BreakStatement *st) const { ETSChecker *checker = GetETSChecker(); st->target_ = checker->FindJumpTarget(st->Type(), st, st->Ident()); + + checker->AddStatus(CheckerStatus::MEET_BREAK); return nullptr; } @@ -1665,6 +1755,8 @@ checker::Type *ETSAnalyzer::Check(ir::ContinueStatement *st) const { ETSChecker *checker = GetETSChecker(); st->target_ = checker->FindJumpTarget(st->Type(), st, st->Ident()); + + checker->AddStatus(CheckerStatus::MEET_CONTINUE); return nullptr; } @@ -1678,9 +1770,13 @@ checker::Type *ETSAnalyzer::Check(ir::DoWhileStatement *st) const ETSChecker *checker = GetETSChecker(); checker::ScopeContext scopeCtx(checker, st->Scope()); + // NOTE: Smart casts are not processed correctly within the loops now, thus clear them at this point. + auto [smartCasts, clearFlag] = checker->Context().EnterLoop(); + checker->CheckTruthinessOfType(st->Test()); st->Body()->Check(checker); + checker->Context().ExitLoop(smartCasts, clearFlag); return nullptr; } @@ -1712,6 +1808,9 @@ checker::Type *ETSAnalyzer::Check(ir::ForOfStatement *const st) const ETSChecker *checker = GetETSChecker(); checker::ScopeContext scopeCtx(checker, st->Scope()); + // NOTE: Smart casts are not processed correctly within the loops now, thus clear them at this point. + auto [smartCasts, clearFlag] = checker->Context().EnterLoop(); + checker::Type *const exprType = st->Right()->Check(checker); if (exprType == nullptr) { checker->ThrowTypeError(MISSING_SOURCE_EXPR_TYPE, st->Right()->Start()); @@ -1761,6 +1860,7 @@ checker::Type *ETSAnalyzer::Check(ir::ForOfStatement *const st) const st->Body()->Check(checker); + checker->Context().ExitLoop(smartCasts, clearFlag); return nullptr; } @@ -1769,6 +1869,9 @@ checker::Type *ETSAnalyzer::Check(ir::ForUpdateStatement *st) const ETSChecker *checker = GetETSChecker(); checker::ScopeContext scopeCtx(checker, st->Scope()); + // NOTE: Smart casts are not processed correctly within the loops now, thus clear them at this point. + auto [smartCasts, clearFlag] = checker->Context().EnterLoop(); + if (st->Init() != nullptr) { st->Init()->Check(checker); } @@ -1783,6 +1886,7 @@ checker::Type *ETSAnalyzer::Check(ir::ForUpdateStatement *st) const st->Body()->Check(checker); + checker->Context().ExitLoop(smartCasts, clearFlag); return nullptr; } @@ -1793,13 +1897,63 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::FunctionDeclaration *st) checker::Type *ETSAnalyzer::Check(ir::IfStatement *st) const { - ETSChecker *checker = GetETSChecker(); - checker->CheckTruthinessOfType(st->test_); + ETSChecker *const checker = GetETSChecker(); - st->consequent_->Check(checker); + SmartCastArray smartCasts = checker->Context().EnterTestExpression(); + checker->CheckTruthinessOfType(st->Test()); + SmartCastTypes testedTypes = checker->Context().ExitTestExpression(); + + if (testedTypes.has_value()) { + for (auto [variable, consequentType, _] : *testedTypes) { + checker->ApplySmartCast(variable, consequentType); + } + } + + checker->Context().EnterPath(); + st->Consequent()->Check(checker); + bool const consequentTerminated = checker->Context().ExitPath(); if (st->Alternate() != nullptr) { - st->alternate_->Check(checker); + SmartCastArray consequentSmartCasts = checker->Context().CloneSmartCasts(); + checker->Context().RestoreSmartCasts(smartCasts); + + if (testedTypes.has_value()) { + for (auto [variable, _, alternateType] : *testedTypes) { + checker->ApplySmartCast(variable, alternateType); + } + } + + checker->Context().EnterPath(); + st->Alternate()->Check(checker); + bool const alternateTerminated = checker->Context().ExitPath(); + + if (alternateTerminated) { + if (!consequentTerminated) { + // Here we need to restore types from consequent if block. + checker->Context().RestoreSmartCasts(consequentSmartCasts); + } else { + // Here we need to restore initial smart types. + checker->Context().RestoreSmartCasts(smartCasts); + } + } else if (!consequentTerminated) { + // Here we need to combine types from consequent and alternate if blocks. + checker->Context().CombineSmartCasts(consequentSmartCasts); + } + } else { + if (consequentTerminated) { + // Restore smart casts to initial state. + checker->Context().RestoreSmartCasts(smartCasts); + if (!testedTypes.has_value()) { + return nullptr; + } + // Add the alternate smart casts + for (auto [variable, _, alternateType] : *testedTypes) { + checker->ApplySmartCast(variable, alternateType); + } + } else { + // Here we need to combine types from consequent if block and initial. + checker->Context().CombineSmartCasts(smartCasts); + } } return nullptr; @@ -1850,9 +2004,9 @@ checker::Type *ETSAnalyzer::GetFunctionReturnType(ir::ReturnStatement *st, ir::S st->argument_->AsArrayExpression()->SetPreferredType(funcReturnType); } - checker::Type *argumentType = st->argument_->Check(checker); - - CheckReturnType(checker, funcReturnType, argumentType, st->argument_, containingFunc->IsAsyncFunc()); + checker::Type *argumentType = checker->GetApparentType(st->argument_->Check(checker)); + CheckReturnType(checker, checker->GetApparentType(funcReturnType), argumentType, st->argument_, + containingFunc->IsAsyncFunc()); } } else { // Case when function's return type should be inferred from return statement(s): @@ -1880,6 +2034,8 @@ checker::Type *ETSAnalyzer::Check(ir::ReturnStatement *st) const ASSERT(ancestor && ancestor->IsScriptFunction()); auto *containingFunc = ancestor->AsScriptFunction(); + checker->AddStatus(CheckerStatus::MEET_RETURN); + if (containingFunc->IsConstructor()) { if (st->argument_ != nullptr) { checker->ThrowTypeError("Return statement with expression isn't allowed in constructor.", st->Start()); @@ -1960,33 +2116,37 @@ checker::Type *ETSAnalyzer::Check(ir::ThrowStatement *st) const if (checker->Relation()->IsAssignableTo(argType, checker->GlobalBuiltinExceptionType())) { checker->CheckThrowingStatements(st); } + + checker->AddStatus(CheckerStatus::MEET_THROW); return nullptr; } checker::Type *ETSAnalyzer::Check(ir::TryStatement *st) const { ETSChecker *checker = GetETSChecker(); - std::vector exceptions; - st->Block()->Check(checker); + std::vector exceptions {}; - for (auto *catchClause : st->CatchClauses()) { - auto exceptionType = catchClause->Check(checker); - if ((exceptionType != nullptr) && (catchClause->Param() != nullptr)) { - auto *clauseType = exceptionType->AsETSObjectType(); - checker->CheckExceptionClauseType(exceptions, catchClause, clauseType); - exceptions.push_back(clauseType); - } - } + st->Block()->Check(checker); + auto smartCasts = checker->Context().CloneSmartCasts(true); bool defaultCatchFound = false; - for (auto *catchClause : st->CatchClauses()) { if (defaultCatchFound) { checker->ThrowTypeError("Default catch clause should be the last in the try statement", catchClause->Start()); } + if (auto const exceptionType = catchClause->Check(checker); + exceptionType != nullptr && catchClause->Param() != nullptr) { + auto *clauseType = exceptionType->AsETSObjectType(); + checker->CheckExceptionClauseType(exceptions, catchClause, clauseType); + exceptions.emplace_back(clauseType); + } + defaultCatchFound = catchClause->IsDefaultCatchClause(); + + checker->Context().CombineSmartCasts(smartCasts); + smartCasts = checker->Context().CloneSmartCasts(true); } if (st->HasFinalizer()) { @@ -1998,20 +2158,49 @@ checker::Type *ETSAnalyzer::Check(ir::TryStatement *st) const checker::Type *ETSAnalyzer::Check(ir::VariableDeclarator *st) const { + if (st->TsType() != nullptr) { + return st->TsType(); + } + ETSChecker *checker = GetETSChecker(); ASSERT(st->Id()->IsIdentifier()); + auto *const ident = st->Id()->AsIdentifier(); ir::ModifierFlags flags = ir::ModifierFlags::NONE; - if (st->Id()->Parent()->Parent()->AsVariableDeclaration()->Kind() == + if (ident->Parent()->Parent()->AsVariableDeclaration()->Kind() == ir::VariableDeclaration::VariableDeclarationKind::CONST) { flags |= ir::ModifierFlags::CONST; } - if (st->Id()->IsOptionalDeclaration()) { + + if (ident->IsOptionalDeclaration()) { flags |= ir::ModifierFlags::OPTIONAL; } - st->SetTsType(checker->CheckVariableDeclaration(st->Id()->AsIdentifier(), - st->Id()->AsIdentifier()->TypeAnnotation(), st->Init(), flags)); - return st->TsType(); + + auto *const variableType = checker->CheckVariableDeclaration(ident, ident->TypeAnnotation(), st->Init(), flags); + auto *smartType = variableType; + + // Now try to define the actual type of Identifier so that smart cast can be used in further checker processing + // NOTE: T_S and K_o_t_l_i_n don't act in such way, but we can try - why not? :) + if (auto *const initType = st->Init() != nullptr ? st->Init()->TsType() : nullptr; initType != nullptr) { + smartType = checker->ResolveSmartType(initType, variableType); + + // Set smart type for identifier if it differs from annotated type + // Top-level and captured variables are not processed here! + if (!checker->Relation()->IsIdenticalTo(variableType, smartType)) { + // Add constness to the smart type if required (initializer type usually is not const) + if (ident->Variable()->Declaration()->IsConstDecl() && !smartType->HasTypeFlag(TypeFlag::CONSTANT) && + !smartType->DefinitelyETSNullish()) { + smartType = smartType->Clone(checker); + smartType->AddTypeFlag(TypeFlag::CONSTANT); + } + + ident->SetTsType(smartType); + checker->Context().SetSmartCast(ident->Variable(), smartType); + } + } + + st->SetTsType(smartType); + return smartType; } checker::Type *ETSAnalyzer::Check(ir::VariableDeclaration *st) const @@ -2029,11 +2218,16 @@ checker::Type *ETSAnalyzer::Check(ir::WhileStatement *st) const ETSChecker *checker = GetETSChecker(); checker::ScopeContext scopeCtx(checker, st->Scope()); - checker->CheckTruthinessOfType(st->Test()); + // NOTE: Smart casts are not processed correctly within the loops now, thus clear them at this point. + auto [smartCasts, clearFlag] = checker->Context().EnterLoop(); + checker->CheckTruthinessOfType(st->Test()); st->Body()->Check(checker); + + checker->Context().ExitLoop(smartCasts, clearFlag); return nullptr; } + // from ts folder checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::TSAnyKeyword *node) const { @@ -2079,6 +2273,10 @@ checker::Type *ETSAnalyzer::Check(ir::TSAsExpression *expr) const } } + if (sourceType->DefinitelyETSNullish() && !targetType->PossiblyETSNullish()) { + checker->ThrowTypeError("Cannot cast 'null' or 'undefined' to non-nullish type.", expr->Expr()->Start()); + } + const checker::CastingContext ctx(checker->Relation(), expr->Expr(), sourceType, targetType, expr->Expr()->Start(), {"Cannot cast type '", sourceType, "' to '", targetType, "'"}); @@ -2258,15 +2456,27 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::TSNeverKeyword *node) con checker::Type *ETSAnalyzer::Check(ir::TSNonNullExpression *expr) const { - ETSChecker *checker = GetETSChecker(); - auto exprType = expr->expr_->Check(checker); + if (expr->TsType() == nullptr) { + ETSChecker *checker = GetETSChecker(); + auto exprType = expr->expr_->Check(checker); + + if (!exprType->PossiblyETSNullish()) { + checker->ThrowTypeError( + "Bad operand type, the operand of the non-nullish expression must be a nullish type", + expr->Expr()->Start()); + } + + // If the actual [smart] type is definitely 'null' or 'undefined' then probably CTE should be thrown. + // Anyway we'll definitely obtain NullPointerException at runtime. + if (exprType->DefinitelyETSNullish()) { + checker->ThrowTypeError( + "Bad operand type, the operand of the non-nullish expression is 'null' or 'undefined'.", + expr->Expr()->Start()); + } - if (!exprType->PossiblyETSNullish()) { - checker->ThrowTypeError("Bad operand type, the operand of the non-nullish expression must be a nullish type", - expr->Expr()->Start()); + expr->SetTsType(checker->GetNonNullishType(exprType)); } - expr->SetTsType(checker->GetNonNullishType(exprType)); return expr->TsType(); } diff --git a/ets2panda/checker/ETSAnalyzer.h b/ets2panda/checker/ETSAnalyzer.h index 6e399a65b1d1a6e3aa9823230460b2779ded97f0..7dccf4cda6f878162224a57f185e04cc755ea92e 100644 --- a/ets2panda/checker/ETSAnalyzer.h +++ b/ets2panda/checker/ETSAnalyzer.h @@ -39,7 +39,8 @@ public: checker::Type *PreferredType(ir::ObjectExpression *expr) const; checker::Type *GetPreferredType(ir::ArrayExpression *expr) const; void CheckObjectExprProps(const ir::ObjectExpression *expr) const; - std::tuple CheckAssignmentExprOperatorType(ir::AssignmentExpression *expr) const; + std::tuple CheckAssignmentExprOperatorType(ir::AssignmentExpression *expr, + Type *leftType) const; private: ETSChecker *GetETSChecker() const; diff --git a/ets2panda/checker/ETSAnalyzerHelpers.cpp b/ets2panda/checker/ETSAnalyzerHelpers.cpp index bc4ac968a0b6d7ab69d3d169fc245beb543726b4..a99deb5222f367b4e0b15a6bcef907f6c7078727 100644 --- a/ets2panda/checker/ETSAnalyzerHelpers.cpp +++ b/ets2panda/checker/ETSAnalyzerHelpers.cpp @@ -500,12 +500,16 @@ void InferReturnType(ETSChecker *checker, ir::ScriptFunction *containingFunc, ch if (stArgument != nullptr && stArgument->IsArrowFunctionExpression()) { auto arrowFunc = stArgument->AsArrowFunctionExpression(); auto typeAnnotation = arrowFunc->CreateTypeAnnotation(checker); + + auto *const argumentType = checker->GetApparentType(arrowFunc->TsType()); funcReturnType = typeAnnotation->GetType(checker); - const Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(arrowFunc->TsType()); - const Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(funcReturnType); + auto *const functionType = checker->GetApparentType(funcReturnType); + + const Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(argumentType); + const Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(functionType); checker::AssignmentContext( - checker->Relation(), arrowFunc, arrowFunc->TsType(), funcReturnType, stArgument->Start(), + checker->Relation(), arrowFunc, argumentType, functionType, stArgument->Start(), {"Type '", sourceType, "' is not compatible with the enclosing method's return type '", targetType, "'"}, checker::TypeRelationFlag::DIRECT_RETURN); } diff --git a/ets2panda/checker/ETSchecker.cpp b/ets2panda/checker/ETSchecker.cpp index 37a8bea978960ac15a3457cdd684a2d485cb7460..a149d621b44eabd12a4a0a55ce4b2c1621a5356a 100644 --- a/ets2panda/checker/ETSchecker.cpp +++ b/ets2panda/checker/ETSchecker.cpp @@ -24,7 +24,7 @@ #include "varbinder/ETSBinder.h" #include "parser/program/program.h" #include "checker/ets/aliveAnalyzer.h" - +#include "checker/types/globalTypesHolder.h" #include "ir/base/scriptFunction.h" #include "util/helpers.h" diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 13251995c93320c19c6ccb0a680e6f4d5efd2119..7680f446667a804addcffa506bef484acfa2802a 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -16,19 +16,10 @@ #ifndef ES2PANDA_CHECKER_ETS_CHECKER_H #define ES2PANDA_CHECKER_ETS_CHECKER_H -#include "checker/checkerContext.h" -#include "varbinder/scope.h" #include "checker/checker.h" -#include "checker/ets/primitiveWrappers.h" -#include "checker/ets/typeConverter.h" -#include "checker/types/ets/etsObjectType.h" -#include "checker/types/ets/etsTupleType.h" + #include "checker/types/ets/types.h" -#include "checker/types/globalTypesHolder.h" -#include "ir/ts/tsTypeParameter.h" -#include "ir/ts/tsTypeParameterInstantiation.h" -#include "lexer/token/tokenType.h" -#include "util/ustring.h" +#include "checker/ets/primitiveWrappers.h" #include "checker/resolveResult.h" namespace ark::es2panda::varbinder { @@ -76,6 +67,11 @@ public: { } + ~ETSChecker() override = default; + + NO_COPY_SEMANTIC(ETSChecker); + NO_MOVE_SEMANTIC(ETSChecker); + [[nodiscard]] static inline TypeFlag ETSType(const Type *const type) noexcept { return static_cast(type->TypeFlags() & TypeFlag::ETS_TYPE); @@ -133,7 +129,8 @@ public: Type *GuaranteedTypeForUncheckedCast(Type *base, Type *substituted); Type *GuaranteedTypeForUncheckedCallReturn(Signature *sig); Type *GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *prop); - bool IsETSChecker() override + + [[nodiscard]] bool IsETSChecker() const noexcept override { return true; } @@ -470,8 +467,12 @@ public: checker::Type *CheckVariableDeclaration(ir::Identifier *ident, ir::TypeNode *typeAnnotation, ir::Expression *init, ir::ModifierFlags flags); void CheckTruthinessOfType(ir::Expression *expr); + void CheckNonNullish(ir::Expression const *expr); Type *GetNonNullishType(Type *type); + Type *RemoveNullType(Type *type); + Type *RemoveUndefinedType(Type *type); + void ConcatConstantString(util::UString &target, Type *type); Type *HandleStringConcatenation(Type *leftType, Type *rightType); Type *ResolveIdentifier(ir::Identifier *ident); @@ -563,6 +564,15 @@ public: static ir::MethodDefinition *GenerateDefaultGetterSetter(ir::ClassProperty *field, varbinder::ClassScope *scope, bool isSetter, ETSChecker *checker); + // Smart cast support + [[nodiscard]] checker::Type *ResolveSmartType(checker::Type *sourceType, checker::Type *targetType); + [[nodiscard]] std::pair CheckTestNullishCondition(Type *testedType, Type *actualType, bool strict); + [[nodiscard]] std::pair CheckTestObjectCondition(ETSObjectType *testedType, Type *actualType, + bool strict); + [[nodiscard]] std::pair CheckTestObjectCondition(ETSArrayType *testedType, Type *actualType); + + void ApplySmartCast(varbinder::Variable const *variable, checker::Type *smartType) noexcept; + bool IsInLocalClass(const ir::AstNode *node) const; // Exception ETSObjectType *CheckExceptionOrErrorType(checker::Type *type, lexer::SourcePosition pos); diff --git a/ets2panda/checker/TSAnalyzer.cpp b/ets2panda/checker/TSAnalyzer.cpp index 195832d35809dc23c9e53d548dd8cd3131211dd4..71e3cee58bf7ea5eecc1dc4b5a533bd1a2d9a8f5 100644 --- a/ets2panda/checker/TSAnalyzer.cpp +++ b/ets2panda/checker/TSAnalyzer.cpp @@ -668,10 +668,10 @@ checker::Type *TSAnalyzer::Check(ir::ConditionalExpression *expr) const checker::Type *testType = expr->Test()->Check(checker); checker->CheckTruthinessOfType(testType, expr->Test()->Start()); - checker->CheckTestingKnownTruthyCallableOrAwaitableType(expr->Test(), testType, expr->consequent_); + checker->CheckTestingKnownTruthyCallableOrAwaitableType(expr->Test(), testType, expr->Consequent()); - checker::Type *consequentType = expr->consequent_->Check(checker); - checker::Type *alternateType = expr->alternate_->Check(checker); + checker::Type *consequentType = expr->Consequent()->Check(checker); + checker::Type *alternateType = expr->Alternate()->Check(checker); return checker->CreateUnionType({consequentType, alternateType}); } @@ -1400,14 +1400,14 @@ checker::Type *TSAnalyzer::Check(ir::FunctionDeclaration *st) const checker::Type *TSAnalyzer::Check(ir::IfStatement *st) const { TSChecker *checker = GetTSChecker(); - checker::Type *testType = st->test_->Check(checker); + checker::Type *testType = st->Test()->Check(checker); checker->CheckTruthinessOfType(testType, st->Start()); - checker->CheckTestingKnownTruthyCallableOrAwaitableType(st->test_, testType, st->consequent_); + checker->CheckTestingKnownTruthyCallableOrAwaitableType(st->Test(), testType, st->Consequent()); - st->consequent_->Check(checker); + st->Consequent()->Check(checker); if (st->Alternate() != nullptr) { - st->alternate_->Check(checker); + st->Alternate()->Check(checker); } return nullptr; diff --git a/ets2panda/checker/checker.cpp b/ets2panda/checker/checker.cpp index e114cc50538920c6956f39417a45d3dd63ec3e66..74e8235f7d25088c1c661a218750bfd3499bbdae 100644 --- a/ets2panda/checker/checker.cpp +++ b/ets2panda/checker/checker.cpp @@ -35,7 +35,7 @@ namespace ark::es2panda::checker { Checker::Checker() : allocator_(SpaceType::SPACE_TYPE_COMPILER, nullptr, true), - context_(&allocator_, CheckerStatus::NO_OPTS), + context_(this, CheckerStatus::NO_OPTS), globalTypes_(allocator_.New(&allocator_)), relation_(allocator_.New(this)) { diff --git a/ets2panda/checker/checker.h b/ets2panda/checker/checker.h index 6be3c3dc3319598523618653180d31fdbdc926ca..3162cb8a363cb487c50f72138b890fb260bc601e 100644 --- a/ets2panda/checker/checker.h +++ b/ets2panda/checker/checker.h @@ -16,20 +16,10 @@ #ifndef ES2PANDA_CHECKER_CHECKER_H #define ES2PANDA_CHECKER_CHECKER_H -#include "varbinder/enumMemberResult.h" -#include "checker/checkerContext.h" -#include "checker/SemanticAnalyzer.h" -#include "checker/types/typeRelation.h" -#include "util/enumbitops.h" -#include "util/ustring.h" #include "es2panda.h" -#include "macros.h" - -#include -#include -#include -#include +#include "checker/checkerContext.h" +#include "checker/SemanticAnalyzer.h" namespace ark::es2panda::parser { class Program; @@ -71,60 +61,61 @@ class Checker { public: explicit Checker(); virtual ~Checker() = default; + NO_COPY_SEMANTIC(Checker); NO_MOVE_SEMANTIC(Checker); - ArenaAllocator *Allocator() + [[nodiscard]] ArenaAllocator *Allocator() noexcept { return &allocator_; } - varbinder::Scope *Scope() const + [[nodiscard]] varbinder::Scope *Scope() const noexcept { return scope_; } - CheckerContext &Context() + [[nodiscard]] CheckerContext &Context() noexcept { return context_; } - bool HasStatus(CheckerStatus status) + [[nodiscard]] bool HasStatus(CheckerStatus status) noexcept { return (context_.Status() & status) != 0; } - void RemoveStatus(CheckerStatus status) + void RemoveStatus(CheckerStatus status) noexcept { context_.Status() &= ~status; } - void AddStatus(CheckerStatus status) + void AddStatus(CheckerStatus status) noexcept { context_.Status() |= status; } - TypeRelation *Relation() const + [[nodiscard]] TypeRelation *Relation() const noexcept { return relation_; } - GlobalTypesHolder *GetGlobalTypesHolder() const + [[nodiscard]] GlobalTypesHolder *GetGlobalTypesHolder() const noexcept { return globalTypes_; } - RelationHolder &IdenticalResults() + [[nodiscard]] RelationHolder &IdenticalResults() noexcept { return identicalResults_; } - RelationHolder &AssignableResults() + [[nodiscard]] RelationHolder &AssignableResults() noexcept { return assignableResults_; } - RelationHolder &ComparableResults() + [[nodiscard]] RelationHolder &ComparableResults() noexcept { return comparableResults_; } @@ -134,28 +125,30 @@ public: return uncheckedCastableResults_; } - RelationHolder &SupertypeResults() + [[nodiscard]] RelationHolder &SupertypeResults() noexcept { return supertypeResults_; } - std::unordered_set &TypeStack() + [[nodiscard]] std::unordered_set &TypeStack() noexcept { return typeStack_; } - virtual bool IsETSChecker() + [[nodiscard]] virtual bool IsETSChecker() const noexcept { return false; } - ETSChecker *AsETSChecker() + [[nodiscard]] ETSChecker *AsETSChecker() { + ASSERT(IsETSChecker()); return reinterpret_cast(this); } - const ETSChecker *AsETSChecker() const + [[nodiscard]] const ETSChecker *AsETSChecker() const { + ASSERT(IsETSChecker()); return reinterpret_cast(this); } @@ -288,7 +281,7 @@ public: Signature *containingSignature) : checker_(checker), prev_(checker->context_) { - checker_->context_ = CheckerContext(checker->Allocator(), newStatus, containingClass, containingSignature); + checker_->context_ = CheckerContext(checker, newStatus, containingClass, containingSignature); } NO_COPY_SEMANTIC(SavedCheckerContext); diff --git a/ets2panda/checker/checkerContext.cpp b/ets2panda/checker/checkerContext.cpp index 1646496b30c90a50e8ce0c8b0eeee3b2d356bbff..4284f826eeb58f2b909eddb62da0f84a3756e9e0 100644 --- a/ets2panda/checker/checkerContext.cpp +++ b/ets2panda/checker/checkerContext.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -13,7 +13,312 @@ * limitations under the License. */ -#include "checkerContext.h" +#include "ETSchecker.h" namespace ark::es2panda::checker { + +CheckerContext::CheckerContext(Checker *checker, CheckerStatus newStatus, ETSObjectType const *containingClass, + Signature *containingSignature) + : parent_(checker), + status_(newStatus), + capturedVars_(parent_->Allocator()->Adapter()), + smartCasts_(parent_->Allocator()->Adapter()), + containingClass_(containingClass), + containingSignature_(containingSignature), + testSmartCasts_(parent_->Allocator()->Adapter()) +{ +} + +SmartCastTypes CheckerContext::CloneTestSmartCasts(bool const clearData) noexcept +{ + if (testSmartCasts_.empty()) { + return std::nullopt; + } + + SmartCastTestArray smartCasts {}; + smartCasts.reserve(testSmartCasts_.size()); + + for (auto [variable, types] : testSmartCasts_) { + if (types.first != nullptr || types.second != nullptr) { + smartCasts.emplace_back(variable, types.first, types.second); + } + } + + if (clearData) { + ClearTestSmartCasts(); + } + + return std::make_optional(smartCasts); +} + +SmartCastArray CheckerContext::CloneSmartCasts(bool const clearData) noexcept +{ + SmartCastArray smartCasts {}; + + if (!smartCasts_.empty()) { + smartCasts.reserve(smartCasts_.size()); + + for (auto const [variable, type] : smartCasts_) { + smartCasts.emplace_back(variable, type); + } + } + + if (clearData) { + ClearSmartCasts(); + } + + return smartCasts; +} + +void CheckerContext::RestoreSmartCasts(SmartCastArray const &prevSmartCasts) noexcept +{ + smartCasts_.clear(); + if (!prevSmartCasts.empty()) { + for (auto [variable, type] : prevSmartCasts) { + smartCasts_.emplace(variable, type); + } + } +} + +void CheckerContext::RemoveSmartCasts(SmartCastArray const &otherSmartCasts) noexcept +{ + if (!smartCasts_.empty()) { + auto it = smartCasts_.begin(); + while (it != smartCasts_.end()) { + if (std::find_if(otherSmartCasts.begin(), otherSmartCasts.end(), [&it](auto const &item) -> bool { + return item.first == it->first; + }) == otherSmartCasts.end()) { + it = smartCasts_.erase(it); + } else { + ++it; + } + } + } +} + +checker::Type *CheckerContext::CombineTypes(checker::Type *const typeOne, checker::Type *const typeTwo) const noexcept +{ + ASSERT(typeOne != nullptr && typeTwo != nullptr); + auto *const checker = parent_->AsETSChecker(); + + if (checker->Relation()->IsIdenticalTo(typeOne, typeTwo)) { + // no type change is required + return nullptr; + } + + return checker->CreateETSUnionType({typeOne, typeTwo}); +} + +void CheckerContext::CombineSmartCasts(SmartCastArray &alternateSmartCasts) noexcept +{ + auto *const checker = parent_->AsETSChecker(); + + auto smartCast = alternateSmartCasts.begin(); + while (smartCast != alternateSmartCasts.end()) { + auto const currentCast = smartCasts_.find(smartCast->first); + if (currentCast == smartCasts_.end()) { + // Remove smart cast that doesn't present in the current set. + smartCast = alternateSmartCasts.erase(smartCast); + continue; + } + + // Smart type was modified + if (auto *const smartType = CombineTypes(smartCast->second, currentCast->second); smartType != nullptr) { + // Remove it or set to new value + if (checker->Relation()->IsIdenticalTo(currentCast->first->TsType(), smartType)) { + smartCasts_.erase(currentCast); + smartCast = alternateSmartCasts.erase(smartCast); + continue; + } + + currentCast->second = smartType; + } + ++smartCast; + } + + // Remove smart casts that don't present in the alternate set. + RemoveSmartCasts(alternateSmartCasts); +} + +// Second return value shows if the 'IN_LOOP' flag should be cleared on exit from the loop (case of nested loops). +std::pair CheckerContext::EnterLoop() noexcept +{ + bool const clearFlag = !IsInLoop(); + if (clearFlag) { + status_ |= CheckerStatus::IN_LOOP; + } + + return {CloneSmartCasts(true), clearFlag}; +} + +void CheckerContext::ExitLoop(SmartCastArray &prevSmartCasts, bool const clearFlag) noexcept +{ + if (clearFlag) { + status_ &= ~CheckerStatus::IN_LOOP; + } + + // Now we don't process smart casts inside the loops correctly, thus just combine them on exit from the loop. + CombineSmartCasts(prevSmartCasts); +} + +// Check that the expression is a part of logical OR/AND or unary negation operators chain +// (other cases are not interested) +bool CheckerContext::IsInValidChain(ir::AstNode const *parent) noexcept +{ + while (parent != nullptr && !parent->IsIfStatement() && !parent->IsConditionalExpression()) { + if (parent->IsBinaryExpression()) { + auto const operation = parent->AsBinaryExpression()->OperatorType(); + if (operation != lexer::TokenType::PUNCTUATOR_LOGICAL_OR && + operation != lexer::TokenType::PUNCTUATOR_LOGICAL_AND) { + return false; + } + } else if (parent->IsUnaryExpression()) { + if (parent->AsUnaryExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) { + return false; + } + } else { + return false; + } + parent = parent->Parent(); + } + return parent != nullptr; +} + +void CheckerContext::CheckIdentifierSmartCastCondition(ir::Identifier const *const identifier) noexcept +{ + if (!IsInTestExpression()) { + return; + } + + auto const *const variable = identifier->Variable(); + ASSERT(variable != nullptr); + + // Smart cast for extended conditional check can be applied only to the variables of reference types. + if (auto const *const variableType = variable->TsType(); !variableType->IsETSReferenceType()) { + return; + } + + if (!IsInValidChain(identifier->Parent())) { + return; + } + + ASSERT(testCondition_.variable == nullptr); + if (identifier->TsType()->PossiblyETSNullish()) { + testCondition_ = {variable, parent_->AsETSChecker()->GlobalETSNullType(), true, false}; + } +} + +void CheckerContext::CheckUnarySmartCastCondition(ir::UnaryExpression const *const unaryExpression) noexcept +{ + if (!IsInTestExpression() || unaryExpression->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) { + return; + } + + auto const *const argument = unaryExpression->Argument(); + if (argument == nullptr || (!argument->IsIdentifier() && !argument->IsBinaryExpression())) { + return; + } + + if (!IsInValidChain(unaryExpression->Parent())) { + return; + } + + if (testCondition_.variable != nullptr) { + testCondition_.negate = !testCondition_.negate; + } +} + +void CheckerContext::CheckBinarySmartCastCondition(ir::BinaryExpression *const binaryExpression) noexcept +{ + if (!IsInTestExpression() || !IsInValidChain(binaryExpression->Parent())) { + return; + } + + if (auto const operatorType = binaryExpression->OperatorType(); operatorType == lexer::TokenType::KEYW_INSTANCEOF) { + ASSERT(testCondition_.variable == nullptr); + if (binaryExpression->Left()->IsIdentifier()) { + testCondition_ = {binaryExpression->Left()->AsIdentifier()->Variable(), + binaryExpression->Right()->TsType()}; + } + } else if (operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL || + operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL || + operatorType == lexer::TokenType::PUNCTUATOR_EQUAL || + operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) { + ASSERT(testCondition_.variable == nullptr); + CheckSmartCastEqualityCondition(binaryExpression); + } +} + +// Extracted just to avoid large length and depth of method 'CheckBinarySmartCastCondition()'. +void CheckerContext::CheckSmartCastEqualityCondition(ir::BinaryExpression *const binaryExpression) noexcept +{ + varbinder::Variable const *variable = nullptr; + checker::Type *testedType = nullptr; + auto const operatorType = binaryExpression->OperatorType(); + + bool strict = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL || + operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL; + + // extracted just to avoid extra nested level + auto const getTestedType = [&variable, &testedType, &strict](ir::Identifier const *const identifier, + ir::Expression *const expression) -> void { + ASSERT(identifier != nullptr && expression != nullptr); + variable = identifier->Variable(); + if (expression->IsLiteral()) { + testedType = expression->TsType(); + if (!expression->IsNullLiteral() && !expression->IsUndefinedLiteral()) { + strict = false; + } + } + }; + + if (binaryExpression->Left()->IsIdentifier()) { + getTestedType(binaryExpression->Left()->AsIdentifier(), binaryExpression->Right()); + } + + if (testedType == nullptr && binaryExpression->Right()->IsIdentifier()) { + getTestedType(binaryExpression->Right()->AsIdentifier(), binaryExpression->Left()); + } + + if (testedType != nullptr) { + bool const negate = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL || + operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL; + + if (testedType->DefinitelyETSNullish()) { + testCondition_ = {variable, testedType, negate, strict}; + } else if (!negate || !strict) { + // NOTE: we cannot say anything about variable from the expressions like 'x !== "str"' + testedType = parent_->AsETSChecker()->ResolveSmartType(testedType, variable->TsType()); + testCondition_ = {variable, testedType, negate, strict}; + } + } +} + +void CheckerContext::ClearTestSmartCasts() noexcept +{ + testCondition_ = {}; + testSmartCasts_.clear(); + operatorType_ = lexer::TokenType::EOS; +} + +checker::Type *CheckerContext::GetSmartCast(varbinder::Variable const *const variable) const noexcept +{ + if (IsInTestExpression()) { + if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) { + if (auto const it = testSmartCasts_.find(variable); + it != testSmartCasts_.end() && it->second.first != nullptr) { + return it->second.first; + } + } else if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) { + if (auto const it = testSmartCasts_.find(variable); + it != testSmartCasts_.end() && it->second.second != nullptr) { + return it->second.second; + } + } + } + + auto const it = smartCasts_.find(variable); + return it == smartCasts_.end() ? nullptr : it->second; +} + } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/checkerContext.h b/ets2panda/checker/checkerContext.h index f5e753ab7ce34036120a52c723ca9f477665b44a..17c82432150b166b72605253e26b21bfc9a4d9ea 100644 --- a/ets2panda/checker/checkerContext.h +++ b/ets2panda/checker/checkerContext.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -16,11 +16,8 @@ #ifndef ES2PANDA_CHECKER_CHECKER_CONTEXT_H #define ES2PANDA_CHECKER_CHECKER_CONTEXT_H -#include +#include "checker/types/type.h" #include "varbinder/variable.h" -#include "util/enumbitops.h" - -#include namespace ark::es2panda::checker { @@ -46,70 +43,95 @@ enum class CheckerStatus : uint32_t { IGNORE_VISIBILITY = 1U << 14U, IN_INSTANCE_EXTENSION_METHOD = 1U << 15U, IN_LOCAL_CLASS = 1U << 16U, - IN_INSTANCEOF_CONTEXT = 1U << 17U + IN_INSTANCEOF_CONTEXT = 1U << 17U, + IN_TEST_EXPRESSION = 1U << 18U, + IN_LOOP = 1U << 19U, + MEET_RETURN = 1U << 20U, + MEET_BREAK = 1U << 21U, + MEET_CONTINUE = 1U << 22U, + MEET_THROW = 1U << 23U, }; DEFINE_BITOPS(CheckerStatus) using CapturedVarsMap = ArenaUnorderedMap; +using SmartCastMap = ArenaMap; +using SmartCastArray = std::vector>; +using SmartCastTestMap = ArenaMap>; +using SmartCastTuple = std::tuple; +using SmartCastTestArray = std::vector; + +struct SmartCastCondition final { + SmartCastCondition() = default; + ~SmartCastCondition() = default; + + DEFAULT_COPY_SEMANTIC(SmartCastCondition); + DEFAULT_MOVE_SEMANTIC(SmartCastCondition); + + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + varbinder::Variable const *variable = nullptr; + checker::Type *testedType = nullptr; + bool negate = false; + bool strict = true; + // NOLINTEND(misc-non-private-member-variables-in-classes) +}; + +using SmartCastTypes = std::optional; -class CheckerContext { +class CheckerContext final { public: - explicit CheckerContext(ArenaAllocator *allocator, CheckerStatus newStatus) - : CheckerContext(allocator, newStatus, nullptr) - { - } + explicit CheckerContext(Checker *checker, CheckerStatus newStatus) : CheckerContext(checker, newStatus, nullptr) {} - explicit CheckerContext(ArenaAllocator *allocator, CheckerStatus newStatus, const ETSObjectType *containingClass) - : CheckerContext(allocator, newStatus, containingClass, nullptr) + explicit CheckerContext(Checker *checker, CheckerStatus newStatus, const ETSObjectType *containingClass) + : CheckerContext(checker, newStatus, containingClass, nullptr) { } - explicit CheckerContext(ArenaAllocator *allocator, CheckerStatus newStatus, const ETSObjectType *containingClass, - Signature *containingSignature) - : status_(newStatus), - capturedVars_(allocator->Adapter()), - containingClass_(containingClass), - containingSignature_(containingSignature) - { - } + explicit CheckerContext(Checker *checker, CheckerStatus newStatus, const ETSObjectType *containingClass, + Signature *containingSignature); + + CheckerContext() = delete; + ~CheckerContext() = default; + + DEFAULT_COPY_SEMANTIC(CheckerContext); + DEFAULT_MOVE_SEMANTIC(CheckerContext); - const CapturedVarsMap &CapturedVars() const + [[nodiscard]] const CapturedVarsMap &CapturedVars() const noexcept { return capturedVars_; } - CapturedVarsMap &CapturedVars() + [[nodiscard]] CapturedVarsMap &CapturedVars() noexcept { return capturedVars_; } - const CheckerStatus &Status() const + [[nodiscard]] const CheckerStatus &Status() const noexcept { return status_; } - ETSObjectType *ContainingClass() const + [[nodiscard]] ETSObjectType *ContainingClass() const noexcept { return const_cast(containingClass_); } - Signature *ContainingSignature() const + [[nodiscard]] Signature *ContainingSignature() const noexcept { return containingSignature_; } - CheckerStatus &Status() + [[nodiscard]] CheckerStatus &Status() noexcept { return status_; } - void SetContainingSignature(Signature *containingSignature) + void SetContainingSignature(Signature *containingSignature) noexcept { containingSignature_ = containingSignature; } - void SetContainingClass(ETSObjectType *containingClass) + void SetContainingClass(ETSObjectType *containingClass) noexcept { containingClass_ = containingClass; } @@ -119,15 +141,94 @@ public: capturedVars_.emplace(var, pos); } - DEFAULT_COPY_SEMANTIC(CheckerContext); - DEFAULT_MOVE_SEMANTIC(CheckerContext); - ~CheckerContext() = default; + void ClearSmartCasts() noexcept + { + smartCasts_.clear(); + } + + void RemoveSmartCast(varbinder::Variable const *const variable) noexcept + { + smartCasts_.erase(variable); + } + + void SetSmartCast(varbinder::Variable const *const variable, checker::Type *const smartType) noexcept + { + smartCasts_.insert_or_assign(variable, smartType); + } + + [[nodiscard]] checker::Type *GetSmartCast(varbinder::Variable const *const variable) const noexcept; + [[nodiscard]] SmartCastArray CloneSmartCasts(bool clearData = false) noexcept; + void RestoreSmartCasts(SmartCastArray const &prevSmartCasts) noexcept; + void CombineSmartCasts(SmartCastArray &alternateSmartCasts) noexcept; + + [[nodiscard]] SmartCastArray EnterTestExpression() noexcept + { + status_ |= CheckerStatus::IN_TEST_EXPRESSION; + ClearTestSmartCasts(); + return CloneSmartCasts(false); + } + + [[nodiscard]] bool IsInTestExpression() const noexcept + { + return (status_ & CheckerStatus::IN_TEST_EXPRESSION) != 0; + } + + SmartCastTypes ExitTestExpression() + { + status_ &= ~CheckerStatus::IN_TEST_EXPRESSION; + CheckTestSmartCastCondition(lexer::TokenType::EOS); + return CloneTestSmartCasts(true); + } + + [[nodiscard]] std::pair EnterLoop() noexcept; + + [[nodiscard]] bool IsInLoop() const noexcept + { + return (status_ & CheckerStatus::IN_LOOP) != 0; + } + + void ExitLoop(SmartCastArray &prevSmartCasts, bool clearFlag) noexcept; + + void EnterPath() noexcept + { + status_ &= ~(CheckerStatus::MEET_RETURN | CheckerStatus::MEET_BREAK | CheckerStatus::MEET_CONTINUE | + CheckerStatus::MEET_THROW); + } + + [[nodiscard]] bool ExitPath() noexcept + { + auto const rc = (status_ & (CheckerStatus::MEET_RETURN | CheckerStatus::MEET_BREAK | + CheckerStatus::MEET_CONTINUE | CheckerStatus::MEET_THROW)) != 0; + status_ &= ~(CheckerStatus::MEET_RETURN | CheckerStatus::MEET_BREAK | CheckerStatus::MEET_CONTINUE | + CheckerStatus::MEET_THROW); + return rc; + } + + void CheckTestSmartCastCondition(lexer::TokenType operatorType); + void CheckIdentifierSmartCastCondition(ir::Identifier const *identifier) noexcept; + void CheckUnarySmartCastCondition(ir::UnaryExpression const *unaryExpression) noexcept; + void CheckBinarySmartCastCondition(ir::BinaryExpression *binaryExpression) noexcept; private: + Checker *parent_; CheckerStatus status_; CapturedVarsMap capturedVars_; + SmartCastMap smartCasts_; const ETSObjectType *containingClass_ {nullptr}; Signature *containingSignature_ {nullptr}; + + lexer::TokenType operatorType_ = lexer::TokenType::EOS; + SmartCastCondition testCondition_ {}; + SmartCastTestMap testSmartCasts_; + + void RemoveSmartCasts(SmartCastArray const &otherSmartCasts) noexcept; + [[nodiscard]] checker::Type *CombineTypes(checker::Type *typeOne, checker::Type *typeTwo) const noexcept; + [[nodiscard]] static bool IsInValidChain(ir::AstNode const *parent) noexcept; + void CheckSmartCastEqualityCondition(ir::BinaryExpression *binaryExpression) noexcept; + [[nodiscard]] SmartCastTypes CloneTestSmartCasts(bool clearData = true) noexcept; + void ClearTestSmartCasts() noexcept; + [[nodiscard]] std::optional ResolveSmartCastTypes(); + [[nodiscard]] bool CheckTestOrSmartCastCondition(SmartCastTuple const &types); }; } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/ets/aliveAnalyzer.cpp b/ets2panda/checker/ets/aliveAnalyzer.cpp index e325add7724559582751bb7cdf7bc0fd48476172..40c8330ea6d7e555e3a2666d3a28a44af7b53e97 100644 --- a/ets2panda/checker/ets/aliveAnalyzer.cpp +++ b/ets2panda/checker/ets/aliveAnalyzer.cpp @@ -44,8 +44,8 @@ #include "ir/ets/etsNewClassInstanceExpression.h" #include "ir/ets/etsStructDeclaration.h" #include "ir/ts/tsInterfaceDeclaration.h" +#include "checker/types/globalTypesHolder.h" #include "varbinder/variable.h" -#include "varbinder/scope.h" #include "varbinder/declaration.h" #include "checker/ETSchecker.h" #include "ir/base/catchClause.h" diff --git a/ets2panda/checker/ets/arithmetic.cpp b/ets2panda/checker/ets/arithmetic.cpp index 3ccb73b7a28aac7d88b01ade0c78bb2b6a918c54..41260b0e0b42c754b53d3d3d704a2de915cb4d53 100644 --- a/ets2panda/checker/ets/arithmetic.cpp +++ b/ets2panda/checker/ets/arithmetic.cpp @@ -15,10 +15,7 @@ #include "arithmetic.h" -#include "ir/expressions/identifier.h" #include "varbinder/variable.h" -#include "varbinder/scope.h" -#include "varbinder/declaration.h" #include "checker/ETSchecker.h" namespace ark::es2panda::checker { @@ -650,17 +647,22 @@ std::tuple ETSChecker::CheckBinaryOperator(ir::Expression *left, { checker::Type *const leftType = left->Check(this); + if (leftType == nullptr) { + ThrowTypeError("Unexpected type error in binary expression", left->Start()); + } + if (operationType == lexer::TokenType::KEYW_INSTANCEOF) { AddStatus(checker::CheckerStatus::IN_INSTANCEOF_CONTEXT); } - checker::Type *rightType = right->Check(this); + Context().CheckTestSmartCastCondition(operationType); + checker::Type *rightType = right->Check(this); if (right->IsTypeNode()) { rightType = right->AsTypeNode()->GetType(this); } - if ((leftType == nullptr) || (rightType == nullptr)) { + if (rightType == nullptr) { ThrowTypeError("Unexpected type error in binary expression", pos); } diff --git a/ets2panda/checker/ets/conversion.cpp b/ets2panda/checker/ets/conversion.cpp index 31d1c4b85c9a7f8d1e10d8ef52dd419e70dfc7f0..ad79c23571e805fb7f5794dceeb626fe1529c154 100644 --- a/ets2panda/checker/ets/conversion.cpp +++ b/ets2panda/checker/ets/conversion.cpp @@ -19,6 +19,7 @@ #include "checker/ets/narrowingConverter.h" #include "checker/ets/unboxingConverter.h" #include "checker/ets/wideningConverter.h" +#include "checker/types/globalTypesHolder.h" namespace ark::es2panda::checker::conversion { void Identity(TypeRelation *const relation, Type *const source, Type *const target) diff --git a/ets2panda/checker/ets/function.cpp b/ets2panda/checker/ets/function.cpp index ad7093bf74455d8555cc2fc40495811f8bfff915..f3cd919da29aed3fa397e662a4ff78c1a8804846 100644 --- a/ets2panda/checker/ets/function.cpp +++ b/ets2panda/checker/ets/function.cpp @@ -13,22 +13,13 @@ * limitations under the License. */ -#include "varbinder/varbinder.h" -#include "varbinder/declaration.h" #include "varbinder/ETSBinder.h" -#include "varbinder/scope.h" -#include "varbinder/variable.h" -#include "varbinder/variableFlags.h" #include "checker/ETSchecker.h" #include "checker/ets/castingContext.h" #include "checker/ets/function_helpers.h" #include "checker/ets/typeRelationContext.h" #include "checker/types/ets/etsAsyncFuncReturnType.h" #include "checker/types/ets/etsObjectType.h" -#include "checker/types/type.h" -#include "checker/types/typeFlag.h" -#include "ir/astNode.h" -#include "ir/typeNode.h" #include "ir/base/catchClause.h" #include "ir/base/classDefinition.h" #include "ir/base/classProperty.h" @@ -59,8 +50,6 @@ #include "ir/statements/returnStatement.h" #include "ir/statements/switchStatement.h" #include "ir/statements/whileStatement.h" -#include "ir/ts/tsArrayType.h" -#include "ir/ts/tsInterfaceBody.h" #include "ir/ts/tsTypeAliasDeclaration.h" #include "ir/ts/tsTypeParameter.h" #include "ir/ts/tsTypeParameterInstantiation.h" @@ -293,17 +282,19 @@ bool ETSChecker::ValidateSignatureRequiredParams(Signature *substitutedSig, this, substitutedSig->Function()->Params()[index], flags); } - auto *const argumentType = argument->Check(this); - const Type *targetType = TryGettingFunctionTypeFromInvokeFunction(substitutedSig->Params()[index]->TsType()); + auto *argumentType = GetApparentType(argument->Check(this)); + auto *targetType = GetApparentType(substitutedSig->Params()[index]->TsType()); auto const invocationCtx = checker::InvocationContext( - Relation(), argument, argumentType, substitutedSig->Params()[index]->TsType(), argument->Start(), - {"Type '", argumentType, "' is not compatible with type '", targetType, "' at index ", index + 1}, flags); + Relation(), argument, argumentType, targetType, argument->Start(), + {"Type '", TryGettingFunctionTypeFromInvokeFunction(argumentType), "' is not compatible with type '", + TryGettingFunctionTypeFromInvokeFunction(targetType), "' at index ", index + 1}, + flags); + if (!invocationCtx.IsInvocable()) { - if (CheckOptionalLambdaFunction(argument, substitutedSig, index)) { - continue; + if (!CheckOptionalLambdaFunction(argument, substitutedSig, index)) { + return false; } - return false; } } @@ -1326,6 +1317,8 @@ void ETSChecker::CheckCapturedVariable(ir::AstNode *const node, varbinder::Varia if (resolved == var) { var->AddFlag(varbinder::VariableFlags::BOXED); + // For mutable captured variable [possible] smart-cast is senseless (or even erroneous) + Context().RemoveSmartCast(var); } } } diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 3ec818dcc75b05e7613fff394cb4c2d5fcbffd0a..9a1e3ba3b21aac58ac8d61183961c4e2f5114f50 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -13,86 +13,39 @@ * limitations under the License. */ -#include "compiler/lowering/scopesInit/scopesInitPhase.h" -#include "varbinder/variableFlags.h" -#include "checker/checker.h" -#include "checker/checkerContext.h" -#include "checker/ets/narrowingWideningConverter.h" -#include "checker/types/globalTypesHolder.h" -#include "checker/types/ets/etsObjectType.h" -#include "ir/astNode.h" -#include "lexer/token/tokenType.h" -#include "ir/base/catchClause.h" -#include "ir/expression.h" -#include "ir/typeNode.h" -#include "ir/base/scriptFunction.h" -#include "ir/base/classProperty.h" -#include "ir/base/methodDefinition.h" -#include "ir/statements/blockStatement.h" -#include "ir/statements/classDeclaration.h" -#include "ir/statements/variableDeclarator.h" -#include "ir/statements/switchCaseStatement.h" -#include "ir/expressions/identifier.h" -#include "ir/expressions/arrayExpression.h" -#include "ir/expressions/objectExpression.h" -#include "ir/expressions/callExpression.h" -#include "ir/expressions/memberExpression.h" -#include "ir/expressions/literals/booleanLiteral.h" -#include "ir/expressions/literals/charLiteral.h" -#include "ir/expressions/binaryExpression.h" -#include "ir/expressions/assignmentExpression.h" -#include "ir/expressions/arrowFunctionExpression.h" -#include "ir/expressions/literals/numberLiteral.h" -#include "ir/expressions/literals/undefinedLiteral.h" -#include "ir/expressions/literals/nullLiteral.h" -#include "ir/statements/labelledStatement.h" -#include "ir/statements/tryStatement.h" -#include "ir/ets/etsFunctionType.h" -#include "ir/ets/etsNewClassInstanceExpression.h" -#include "ir/ets/etsParameterExpression.h" -#include "ir/ts/tsAsExpression.h" -#include "ir/ts/tsTypeAliasDeclaration.h" -#include "ir/ts/tsEnumMember.h" -#include "ir/ts/tsTypeParameter.h" -#include "ir/ets/etsTypeReference.h" -#include "ir/ets/etsTypeReferencePart.h" -#include "ir/ets/etsPrimitiveType.h" -#include "ir/ts/tsQualifiedName.h" -#include "varbinder/variable.h" -#include "varbinder/scope.h" -#include "varbinder/declaration.h" -#include "parser/ETSparser.h" -#include "parser/program/program.h" -#include "checker/ETSchecker.h" +#include "util/helpers.h" #include "varbinder/ETSBinder.h" +#include "parser/ETSparser.h" + +#include "checker/types/ets/etsTupleType.h" +#include "checker/ets/narrowingWideningConverter.h" #include "checker/ets/typeRelationContext.h" #include "checker/ets/boxingConverter.h" #include "checker/ets/unboxingConverter.h" -#include "checker/types/ets/types.h" -#include "util/helpers.h" +#include "checker/ETSchecker.h" +#include "checker/types/globalTypesHolder.h" + +#include "compiler/lowering/scopesInit/scopesInitPhase.h" namespace ark::es2panda::checker { void ETSChecker::CheckTruthinessOfType(ir::Expression *expr) { - checker::Type *type = expr->Check(this); - auto *unboxedType = ETSBuiltinTypeAsConditionalType(type); + auto *const testType = expr->Check(this); + auto *const conditionType = ETSBuiltinTypeAsConditionalType(testType); - if (unboxedType == nullptr) { + if (conditionType == nullptr || !conditionType->IsConditionalExprType()) { ThrowTypeError("Condition must be of possible condition type", expr->Start()); } - if (unboxedType->IsETSVoidType()) { + if (conditionType->IsETSVoidType()) { ThrowTypeError("An expression of type 'void' cannot be tested for truthiness", expr->Start()); } - if (!unboxedType->IsConditionalExprType()) { - ThrowTypeError("Condition must be of possible condition type", expr->Start()); + if (conditionType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { + FlagExpressionWithUnboxing(testType, conditionType, expr); } - if (unboxedType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { - FlagExpressionWithUnboxing(type, unboxedType, expr); - } - expr->SetTsType(unboxedType); + expr->SetTsType(conditionType); } void ETSChecker::CheckNonNullish(ir::Expression const *expr) @@ -102,7 +55,7 @@ void ETSChecker::CheckNonNullish(ir::Expression const *expr) } } -Type *ETSChecker::GetNonNullishType(Type *type) +checker::Type *ETSChecker::GetNonNullishType(checker::Type *type) { if (type->DefinitelyNotETSNullish()) { return type; @@ -110,6 +63,11 @@ Type *ETSChecker::GetNonNullishType(Type *type) if (type->IsETSTypeParameter()) { return Allocator()->New(type->AsETSTypeParameter()); } + + if (type->IsETSNullType() || type->IsETSUndefinedType()) { + return GetGlobalTypesHolder()->GlobalBuiltinNeverType(); + } + ArenaVector copied(Allocator()->Adapter()); for (auto const &t : type->AsETSUnionType()->ConstituentTypes()) { if (t->IsETSNullType() || t->IsETSUndefinedType()) { @@ -120,6 +78,60 @@ Type *ETSChecker::GetNonNullishType(Type *type) return copied.empty() ? GetGlobalTypesHolder()->GlobalBuiltinNeverType() : CreateETSUnionType(std::move(copied)); } +checker::Type *ETSChecker::RemoveNullType(checker::Type *const type) +{ + if (type->DefinitelyNotETSNullish() || type->IsETSUndefinedType()) { + return type; + } + + if (type->IsETSTypeParameter()) { + return Allocator()->New(type->AsETSTypeParameter()); + } + + if (type->IsETSNullType()) { + return GetGlobalTypesHolder()->GlobalBuiltinNeverType(); + } + + ASSERT(type->IsETSUnionType()); + ArenaVector copiedTypes(Allocator()->Adapter()); + + for (auto *constituentType : type->AsETSUnionType()->ConstituentTypes()) { + if (!constituentType->IsETSNullType()) { + copiedTypes.push_back(RemoveNullType(constituentType)); + } + } + + return copiedTypes.empty() ? GetGlobalTypesHolder()->GlobalBuiltinNeverType() + : CreateETSUnionType(std::move(copiedTypes)); +} + +checker::Type *ETSChecker::RemoveUndefinedType(checker::Type *const type) +{ + if (type->DefinitelyNotETSNullish() || type->IsETSNullType()) { + return type; + } + + if (type->IsETSTypeParameter()) { + return Allocator()->New(type->AsETSTypeParameter()); + } + + if (type->IsETSUndefinedType()) { + return GetGlobalTypesHolder()->GlobalBuiltinNeverType(); + } + + ASSERT(type->IsETSUnionType()); + ArenaVector copiedTypes(Allocator()->Adapter()); + + for (auto *constituentType : type->AsETSUnionType()->ConstituentTypes()) { + if (!constituentType->IsETSUndefinedType()) { + copiedTypes.push_back(RemoveUndefinedType(constituentType)); + } + } + + return copiedTypes.empty() ? GetGlobalTypesHolder()->GlobalBuiltinNeverType() + : CreateETSUnionType(std::move(copiedTypes)); +} + // NOTE(vpukhov): can be implemented with relation if etscompiler will support it template static bool MatchConstituentOrConstraint(P const &pred, const Type *type) @@ -150,25 +162,25 @@ static bool MatchConstituentOrConstraint(P const &pred, const Type *type) return false; } -bool Type::PossiblyETSNull() const +bool checker::Type::PossiblyETSNull() const { const auto pred = [](const Type *t) { return t->IsETSNullType(); }; return MatchConstituentOrConstraint(pred, this); } -bool Type::PossiblyETSUndefined() const +bool checker::Type::PossiblyETSUndefined() const { const auto pred = [](const Type *t) { return t->IsETSUndefinedType(); }; return MatchConstituentOrConstraint(pred, this); } -bool Type::PossiblyETSNullish() const +bool checker::Type::PossiblyETSNullish() const { const auto pred = [](const Type *t) { return t->IsETSNullType() || t->IsETSUndefinedType(); }; return MatchConstituentOrConstraint(pred, this); } -bool Type::DefinitelyETSNullish() const +bool checker::Type::DefinitelyETSNullish() const { const auto pred = [](const Type *t) { return !(t->IsTypeParameter() || t->IsETSUnionType() || t->IsETSNullType() || t->IsETSUndefinedType()); @@ -176,12 +188,12 @@ bool Type::DefinitelyETSNullish() const return !MatchConstituentOrConstraint(pred, this); } -bool Type::DefinitelyNotETSNullish() const +bool checker::Type::DefinitelyNotETSNullish() const { return !PossiblyETSNullish(); } -bool Type::PossiblyETSString() const +bool checker::Type::PossiblyETSString() const { const auto pred = [](const Type *t) { return t->IsETSStringType() || (t->IsETSObjectType() && t->AsETSObjectType()->IsGlobalETSObjectType()); @@ -189,23 +201,23 @@ bool Type::PossiblyETSString() const return MatchConstituentOrConstraint(pred, this); } -bool Type::IsETSReferenceType() const +bool checker::Type::IsETSReferenceType() const { return IsETSObjectType() || IsETSArrayType() || IsETSNullType() || IsETSUndefinedType() || IsETSStringType() || IsETSTypeParameter() || IsETSUnionType() || IsETSNonNullishType() || IsETSBigIntType(); } -bool Type::IsETSUnboxableObject() const +bool checker::Type::IsETSUnboxableObject() const { return IsETSObjectType() && AsETSObjectType()->HasObjectFlag(ETSObjectFlags::UNBOXABLE_TYPE); } -bool ETSChecker::IsConstantExpression(ir::Expression *expr, Type *type) +bool ETSChecker::IsConstantExpression(ir::Expression *expr, checker::Type *type) { return (type->HasTypeFlag(TypeFlag::CONSTANT) && (expr->IsIdentifier() || expr->IsMemberExpression())); } -Type *ETSChecker::GetNonConstantTypeFromPrimitiveType(Type *type) const +checker::Type *ETSChecker::GetNonConstantTypeFromPrimitiveType(checker::Type *type) const { if (type->IsETSStringType()) { return GlobalBuiltinETSStringType(); @@ -249,7 +261,7 @@ Type *ETSChecker::GetNonConstantTypeFromPrimitiveType(Type *type) const return type; } -Type *ETSChecker::GetTypeOfSetterGetter(varbinder::Variable *const var) +checker::Type *ETSChecker::GetTypeOfSetterGetter(varbinder::Variable *const var) { auto *propType = var->TsType()->AsETSFunctionType(); if (propType->HasTypeFlag(checker::TypeFlag::GETTER)) { @@ -290,7 +302,7 @@ void ETSChecker::IterateInVariableContext(varbinder::Variable *const var) } } -Type *ETSChecker::GetTypeOfVariable(varbinder::Variable *const var) +checker::Type *ETSChecker::GetTypeOfVariable(varbinder::Variable *const var) { if (IsVariableGetterSetter(var)) { return GetTypeOfSetterGetter(var); @@ -351,7 +363,7 @@ Type *ETSChecker::GetTypeOfVariable(varbinder::Variable *const var) } // Determine if unchecked cast is needed and yield guaranteed source type -Type *ETSChecker::GuaranteedTypeForUncheckedCast(Type *base, Type *substituted) +checker::Type *ETSChecker::GuaranteedTypeForUncheckedCast(checker::Type *base, checker::Type *substituted) { // Apparent type acts as effective representation for type. // For T extends SomeClass|undefined @@ -363,7 +375,7 @@ Type *ETSChecker::GuaranteedTypeForUncheckedCast(Type *base, Type *substituted) } // Determine if substituted property access requires cast from erased type -Type *ETSChecker::GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *const prop) +checker::Type *ETSChecker::GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *const prop) { if (IsVariableStatic(prop)) { return nullptr; @@ -388,7 +400,7 @@ Type *ETSChecker::GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable * } // Determine if substituted method cast requires cast from erased type -Type *ETSChecker::GuaranteedTypeForUncheckedCallReturn(Signature *sig) +checker::Type *ETSChecker::GuaranteedTypeForUncheckedCallReturn(Signature *sig) { if (sig->HasSignatureFlag(checker::SignatureFlags::THIS_RETURN_TYPE)) { return sig->ReturnType(); @@ -652,7 +664,8 @@ void ETSChecker::ValidateResolvedIdentifier(ir::Identifier *const ident, varbind ExtraCheckForResolvedError(ident); } - auto *const resolvedType = GetApparentType(GetTypeOfVariable(resolved)); + auto *smartType = Context().GetSmartCast(resolved); + auto *const resolvedType = GetApparentType(smartType != nullptr ? smartType : GetTypeOfVariable(resolved)); switch (ident->Parent()->Type()) { case ir::AstNodeType::CALL_EXPRESSION: { @@ -795,7 +808,7 @@ void ETSChecker::SaveCapturedVariable(varbinder::Variable *const var, ir::Identi } } -Type *ETSChecker::ResolveIdentifier(ir::Identifier *const ident) +checker::Type *ETSChecker::ResolveIdentifier(ir::Identifier *const ident) { if (ident->Variable() != nullptr) { auto *const resolved = ident->Variable(); @@ -839,12 +852,11 @@ void ETSChecker::ValidateUnaryOperatorOperand(varbinder::Variable *variable) } } -std::tuple ETSChecker::ApplyBinaryOperatorPromotion(Type *left, Type *right, TypeFlag test, - bool doPromotion) +std::tuple ETSChecker::ApplyBinaryOperatorPromotion(checker::Type *left, checker::Type *right, + TypeFlag test, bool const doPromotion) { - Type *unboxedL = ETSBuiltinTypeAsPrimitiveType(left); - Type *unboxedR = ETSBuiltinTypeAsPrimitiveType(right); - bool bothConst = false; + Type *const unboxedL = ETSBuiltinTypeAsPrimitiveType(left); + Type *const unboxedR = ETSBuiltinTypeAsPrimitiveType(right); if (unboxedL == nullptr || unboxedR == nullptr) { return {nullptr, false}; @@ -854,28 +866,32 @@ std::tuple ETSChecker::ApplyBinaryOperatorPromotion(Type *left, Ty return {nullptr, false}; } - if (unboxedL->HasTypeFlag(TypeFlag::CONSTANT) && unboxedR->HasTypeFlag(TypeFlag::CONSTANT)) { - bothConst = true; - } - if (doPromotion) { - if (unboxedL->HasTypeFlag(TypeFlag::ETS_NUMERIC) && unboxedR->HasTypeFlag(TypeFlag::ETS_NUMERIC)) { - if (unboxedL->IsDoubleType() || unboxedR->IsDoubleType()) { - return {GlobalDoubleType(), bothConst}; - } + bool const bothConst = unboxedL->HasTypeFlag(TypeFlag::CONSTANT) && unboxedR->HasTypeFlag(TypeFlag::CONSTANT); - if (unboxedL->IsFloatType() || unboxedR->IsFloatType()) { - return {GlobalFloatType(), bothConst}; - } + // extract just to reduce nested levels + auto const numericPromotion = [this, unboxedL, unboxedR, bothConst]() -> std::tuple { + if (unboxedL->IsDoubleType() || unboxedR->IsDoubleType()) { + return {GlobalDoubleType(), bothConst}; + } - if (unboxedL->IsLongType() || unboxedR->IsLongType()) { - return {GlobalLongType(), bothConst}; - } + if (unboxedL->IsFloatType() || unboxedR->IsFloatType()) { + return {GlobalFloatType(), bothConst}; + } - if (unboxedL->IsCharType() && unboxedR->IsCharType()) { - return {GlobalCharType(), bothConst}; - } + if (unboxedL->IsLongType() || unboxedR->IsLongType()) { + return {GlobalLongType(), bothConst}; + } + + if (unboxedL->IsCharType() && unboxedR->IsCharType()) { + return {GlobalCharType(), bothConst}; + } + + return {GlobalIntType(), bothConst}; + }; - return {GlobalIntType(), bothConst}; + if (doPromotion) { + if (unboxedL->HasTypeFlag(TypeFlag::ETS_NUMERIC) && unboxedR->HasTypeFlag(TypeFlag::ETS_NUMERIC)) { + return numericPromotion(); } if (IsTypeIdenticalTo(unboxedL, unboxedR)) { @@ -1154,7 +1170,7 @@ checker::Type *ETSChecker::FixOptionalVariableType(varbinder::Variable *const bi } checker::Type *ETSChecker::CheckVariableDeclaration(ir::Identifier *ident, ir::TypeNode *typeAnnotation, - ir::Expression *init, ir::ModifierFlags flags) + ir::Expression *init, ir::ModifierFlags const flags) { const util::StringView &varName = ident->Name(); ASSERT(ident->Variable()); @@ -1214,7 +1230,7 @@ checker::Type *ETSChecker::CheckVariableDeclaration(ir::Identifier *ident, ir::T InferTypesForLambda(lambda, typeAnnotation->AsETSFunctionType()); } } - checker::Type *initType = init->Check(this); + checker::Type *initType = GetApparentType(init->Check(this)); if (initType == nullptr) { ThrowTypeError("Cannot get the expression type", init->Start()); @@ -1236,6 +1252,7 @@ checker::Type *ETSChecker::CheckVariableDeclaration(ir::Identifier *ident, ir::T } if (annotationType != nullptr) { + annotationType = GetApparentType(annotationType); const Type *targetType = TryGettingFunctionTypeFromInvokeFunction(annotationType); const Type *sourceType = TryGettingFunctionTypeFromInvokeFunction(initType); @@ -1259,6 +1276,330 @@ checker::Type *ETSChecker::CheckVariableDeclaration(ir::Identifier *ident, ir::T return FixOptionalVariableType(bindingVar, flags); } +//==============================================================================// +// Smart cast support +//==============================================================================// + +checker::Type *ETSChecker::ResolveSmartType(checker::Type *sourceType, checker::Type *targetType) +{ + targetType = GetApparentType(targetType); + + // For left-hand variable of primitive type leave it as is. + if (targetType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE_RETURN)) { + return targetType; + } + + // For left-hand variable of tuple type leave it as is. + if (targetType->IsETSTupleType()) { + return targetType; + } + + // For left-hand variable of builtin type leave it as is. + if (targetType->IsETSObjectType() && targetType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE)) { + return targetType; + } + + sourceType = GetApparentType(sourceType); + + // For the Function source or target types leave the target type as is + // until we will be able to create the functional interface type from the source. + if (targetType->HasTypeFlag(TypeFlag::FUNCTION) || sourceType->HasTypeFlag(TypeFlag::FUNCTION)) { + return targetType; + } + + // Nothing to do with identical types: + auto *nonConstSourceType = !sourceType->IsConstantType() ? sourceType : sourceType->Clone(this); + nonConstSourceType->RemoveTypeFlag(TypeFlag::CONSTANT); + + auto *nonConstTargetType = !targetType->IsConstantType() ? targetType : targetType->Clone(this); + nonConstTargetType->RemoveTypeFlag(TypeFlag::CONSTANT); + + if (Relation()->IsIdenticalTo(nonConstSourceType, nonConstTargetType) || + Relation()->IsIdenticalTo(GlobalBuiltinJSValueType(), nonConstTargetType)) { + return targetType; + } + + // For null or undefined source type return it as is. + if (sourceType->DefinitelyETSNullish()) { + return sourceType; + } + + // In case of Union left-hand type we have to select the proper type from the Union + // NOTE: it always exists at this point! + if (targetType->IsETSUnionType()) { + sourceType = targetType->AsETSUnionType()->GetAssignableType(this, sourceType); + ASSERT(sourceType != nullptr); + return sourceType; + } + + // If source is reference type, set it as the current and use it for identifier smart cast + if (sourceType->IsETSReferenceType()) { + return sourceType; + } + + // For right-hand variable of primitive type apply boxing conversion (case: 'let x: Object = 5', then x => Int). + if (sourceType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE) && !sourceType->IsETSVoidType() && + targetType->IsETSObjectType()) { + return PrimitiveTypeAsETSBuiltinType(sourceType); + } + + // NOTE - it seems that all the other possible cases are assignments like: + // 'Object = ObjectLiteral' or smth similar ??? + // thus for such cases also leave the target type as is. + // Possible errors in tests should clarify this hypothesis sooner or later :) + return targetType; +} + +// Auxiliary method to reduce the size of common 'CheckTestSmartCastConditions' function. +std::pair ETSChecker::CheckTestNullishCondition(Type *testedType, Type *actualType, bool const strict) +{ + static checker::Type *globalNullType = CreateETSUnionType({GlobalETSNullType(), GlobalETSUndefinedType()}); + + if (!strict) { + return {globalNullType, GetNonNullishType(actualType)}; + } + + if (testedType->IsETSNullType()) { + return {GlobalETSNullType(), RemoveNullType(actualType)}; + } + + if (testedType->IsETSUndefinedType()) { + return {GlobalETSUndefinedType(), RemoveUndefinedType(actualType)}; + } + + return {globalNullType, GetNonNullishType(actualType)}; +} + +// Auxiliary method to reduce the size of common 'CheckTestSmartCastConditions' function. +std::pair ETSChecker::CheckTestObjectCondition(ETSArrayType *testedType, Type *actualType) +{ + auto *const apparentType = GetApparentType(actualType); + + if (apparentType->IsETSUnionType()) { + return apparentType->AsETSUnionType()->GetComplimentaryType(this, testedType); + } + + // Both testing and actual (smart) types are arrays. Set types according to their relation. + // NOTE: probably the rules of type extraction should be modified later on! + if (apparentType->IsETSArrayType()) { + auto *const arrayType = apparentType->AsETSArrayType(); + + if (Relation()->IsIdenticalTo(arrayType, testedType) || + arrayType->AssemblerName() == testedType->AssemblerName()) { + return {testedType, GetGlobalTypesHolder()->GlobalNeverType()}; + } + + if (Relation()->IsSupertypeOf(arrayType, testedType)) { + return {testedType, actualType}; + } + + if (Relation()->IsSupertypeOf(testedType, arrayType)) { + return {testedType, actualType}; + } + } else if (apparentType->IsETSObjectType() && apparentType->AsETSObjectType()->IsGlobalETSObjectType()) { + return {testedType, actualType}; + } + + return {GetGlobalTypesHolder()->GlobalNeverType(), actualType}; +} + +// Auxiliary method to reduce the size of common 'CheckTestSmartCastConditions' function. +std::pair ETSChecker::CheckTestObjectCondition(ETSObjectType *testedType, Type *actualType, + bool const strict) +{ + auto *const apparentType = GetApparentType(actualType); + + if (apparentType->IsETSUnionType()) { + return apparentType->AsETSUnionType()->GetComplimentaryType(this, testedType); + } + + // Both testing and actual (smart) types are objects. Set types according to their relation. + // NOTE: probably the rules of type extraction should be modified later on! + if (apparentType->IsETSObjectType()) { + auto *const objectType = apparentType->AsETSObjectType(); + + if (Relation()->IsIdenticalTo(objectType, testedType) || + objectType->AssemblerName() == testedType->AssemblerName()) { + return {testedType, strict ? GetGlobalTypesHolder()->GlobalNeverType() : actualType}; + } + + if (Relation()->IsSupertypeOf(objectType, testedType)) { + return {testedType, actualType}; + } + + if (Relation()->IsSupertypeOf(testedType, objectType)) { + return {testedType, actualType}; + } + + return {GetGlobalTypesHolder()->GlobalNeverType(), actualType}; + } + + // NOTE: other cases (for example with functional types) will be implemented later on + return {testedType, actualType}; +} + +static constexpr std::size_t const VARIABLE_POSITION = 0UL; +static constexpr std::size_t const CONSEQUENT_TYPE_POSITION = 1UL; +static constexpr std::size_t const ALTERNATE_TYPE_POSITION = 2UL; + +void CheckerContext::CheckTestSmartCastCondition(lexer::TokenType operatorType) +{ + if (operatorType != lexer::TokenType::EOS && operatorType != lexer::TokenType::PUNCTUATOR_LOGICAL_AND && + operatorType != lexer::TokenType::PUNCTUATOR_LOGICAL_OR) { + return; + } + + auto types = ResolveSmartCastTypes(); + + if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) { + if (types.has_value()) { + auto const &variable = std::get(*types); + // NOTE: now we support only cases like 'if (x != null && y == null)' but don't support different type + // checks for a single variable (like 'if (x != null && x instanceof string)'), because it seems that + // it doesn't make much sense. + // Can be implemented later on if the need arises. + if (auto [_, inserted] = + testSmartCasts_.emplace(variable, std::make_pair(std::get(*types), + std::get(*types))); + !inserted) { + testSmartCasts_[variable] = {nullptr, nullptr}; + } + } + // Clear alternate types, because now they become indefinite + for (auto &smartCast : testSmartCasts_) { + smartCast.second.second = nullptr; + } + } else if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) { + if (bool const cleanConsequent = types.has_value() ? CheckTestOrSmartCastCondition(*types) : true; + cleanConsequent) { + // Clear consequent types, because now they become indefinite + for (auto &smartCast : testSmartCasts_) { + smartCast.second.first = nullptr; + } + } + } else if (types.has_value()) { + testSmartCasts_.emplace( + std::get(*types), + std::make_pair(std::get(*types), std::get(*types))); + } + + testCondition_ = {}; + operatorType_ = operatorType; +} + +std::optional CheckerContext::ResolveSmartCastTypes() +{ + if (testCondition_.variable == nullptr) { + return std::nullopt; + } + + // Exclude processing of global variables and those captured in lambdas and modified there + auto const *const variableScope = testCondition_.variable->GetScope(); + auto const topLevelVariable = + variableScope != nullptr ? variableScope->IsGlobalScope() || + (variableScope->Parent() != nullptr && variableScope->Parent()->IsGlobalScope()) + : false; + if (topLevelVariable && testCondition_.variable->HasFlag(varbinder::VariableFlags::BOXED)) { + return std::nullopt; + } + + ASSERT(testCondition_.testedType != nullptr); + // NOTE: functional types are not supported now + if (!testCondition_.testedType->IsETSReferenceType() || + testCondition_.testedType->HasTypeFlag(TypeFlag::FUNCTION)) { + return std::nullopt; + } + + auto *smartType = GetSmartCast(testCondition_.variable); + if (smartType == nullptr) { + smartType = testCondition_.variable->TsType(); + } + + auto *const checker = parent_->AsETSChecker(); + Type *consequentType = nullptr; + Type *alternateType = nullptr; + + if (testCondition_.testedType->DefinitelyETSNullish()) { + // In case of testing for 'null' and/or 'undefined' remove corresponding null-like types. + std::tie(consequentType, alternateType) = + checker->CheckTestNullishCondition(testCondition_.testedType, smartType, testCondition_.strict); + } else { + if (testCondition_.testedType->IsETSTypeParameter()) { + testCondition_.testedType = checker->GetApparentType(testCondition_.testedType); + } + + if (testCondition_.testedType->IsETSObjectType()) { + auto *const testedType = testCondition_.testedType->AsETSObjectType(); + std::tie(consequentType, alternateType) = + checker->CheckTestObjectCondition(testedType, smartType, testCondition_.strict); + } else if (testCondition_.testedType->IsETSArrayType()) { + auto *const testedType = testCondition_.testedType->AsETSArrayType(); + std::tie(consequentType, alternateType) = checker->CheckTestObjectCondition(testedType, smartType); + } else if (testCondition_.testedType->IsETSUnionType()) { + // NOTE: now we don't support 'instanceof' operation for union types? + UNREACHABLE(); + } else { + // NOTE: it seems that no more cases are possible here! :) + UNREACHABLE(); + } + } + + return !testCondition_.negate + ? std::make_optional(std::make_tuple(testCondition_.variable, consequentType, alternateType)) + : std::make_optional(std::make_tuple(testCondition_.variable, alternateType, consequentType)); +} + +void ETSChecker::ApplySmartCast(varbinder::Variable const *const variable, checker::Type *const smartType) noexcept +{ + ASSERT(variable != nullptr); + if (smartType != nullptr) { + auto *variableType = variable->TsType(); + + if (Relation()->IsIdenticalTo(variableType, smartType)) { + Context().RemoveSmartCast(variable); + } else { + Context().SetSmartCast(variable, smartType); + } + } +} + +bool CheckerContext::CheckTestOrSmartCastCondition(SmartCastTuple const &types) +{ + auto *const &variable = std::get(types); + auto *const &consequentTypeNew = std::get(types); + auto *const &alternateTypeNew = std::get(types); + + if (auto const it = testSmartCasts_.find(variable); it != testSmartCasts_.end()) { + auto *const consequentTypeOld = it->second.first; + if (consequentTypeOld == nullptr) { + return true; + } + + if (consequentTypeNew != nullptr && !parent_->Relation()->IsIdenticalTo(consequentTypeOld, consequentTypeNew)) { + it->second.first = parent_->AsETSChecker()->CreateETSUnionType({consequentTypeOld, consequentTypeNew}); + } + + if (auto *const alternateTypeOld = it->second.second; alternateTypeOld != nullptr) { + if (alternateTypeNew != nullptr && + !parent_->Relation()->IsIdenticalTo(alternateTypeOld, alternateTypeNew)) { + it->second.second = parent_->AsETSChecker()->CreateETSUnionType({alternateTypeOld, alternateTypeNew}); + } + } else { + it->second.second = alternateTypeNew; + } + + return false; + } + + // NOTE: now we support only cases like 'if (x != null || y != null)' or 'if (x instanceof A || x instanceof B)' + // although it seems that the resulting variable type in the second case isn't used in subsequent code directly. + // More complex conditions can be implemented later on if the need arises. + testSmartCasts_.emplace(variable, std::make_pair(consequentTypeNew, alternateTypeNew)); + return true; +} + +//==============================================================================// + void ETSChecker::SetArrayPreferredTypeForNestedMemberExpressions(ir::MemberExpression *expr, Type *annotationType) { if ((expr == nullptr) || (annotationType == nullptr)) { @@ -1640,8 +1981,8 @@ Type *ETSChecker::HandleStringConcatenation(Type *leftType, Type *rightType) return CreateETSStringLiteralType(concatenated.View()); } -ETSFunctionType *ETSChecker::FindFunctionInVectorGivenByName(util::StringView name, - ArenaVector &list) +checker::ETSFunctionType *ETSChecker::FindFunctionInVectorGivenByName(util::StringView name, + ArenaVector &list) { for (auto *it : list) { if (it->Name() == name) { @@ -1652,7 +1993,7 @@ ETSFunctionType *ETSChecker::FindFunctionInVectorGivenByName(util::StringView na return nullptr; } -bool ETSChecker::IsFunctionContainsSignature(ETSFunctionType *funcType, Signature *signature) +bool ETSChecker::IsFunctionContainsSignature(checker::ETSFunctionType *funcType, Signature *signature) { for (auto *it : funcType->CallSignatures()) { Relation()->IsCompatibleTo(it, signature); @@ -1664,7 +2005,7 @@ bool ETSChecker::IsFunctionContainsSignature(ETSFunctionType *funcType, Signatur return false; } -void ETSChecker::CheckFunctionContainsClashingSignature(const ETSFunctionType *funcType, Signature *signature) +void ETSChecker::CheckFunctionContainsClashingSignature(const checker::ETSFunctionType *funcType, Signature *signature) { for (auto *it : funcType->CallSignatures()) { SavedTypeRelationFlagsContext strfCtx(Relation(), TypeRelationFlag::NONE); @@ -1684,7 +2025,7 @@ void ETSChecker::CheckFunctionContainsClashingSignature(const ETSFunctionType *f } } -void ETSChecker::MergeSignatures(ETSFunctionType *target, ETSFunctionType *source) +void ETSChecker::MergeSignatures(checker::ETSFunctionType *target, checker::ETSFunctionType *source) { for (auto *s : source->CallSignatures()) { if (IsFunctionContainsSignature(target, s)) { @@ -1696,7 +2037,8 @@ void ETSChecker::MergeSignatures(ETSFunctionType *target, ETSFunctionType *sourc } } -void ETSChecker::MergeComputedAbstracts(ArenaVector &merged, ArenaVector ¤t) +void ETSChecker::MergeComputedAbstracts(ArenaVector &merged, + ArenaVector ¤t) { for (auto *curr : current) { auto name = curr->Name(); @@ -1864,7 +2206,7 @@ Type *ETSChecker::ETSBuiltinTypeAsPrimitiveType(Type *objectType) return converter.Result(); } -Type *ETSChecker::ETSBuiltinTypeAsConditionalType(Type *objectType) +Type *ETSChecker::ETSBuiltinTypeAsConditionalType(Type *const objectType) { if ((objectType == nullptr) || !objectType->IsConditionalExprType()) { return nullptr; @@ -2231,7 +2573,7 @@ bool ETSChecker::IsSameDeclarationType(varbinder::LocalVariable *target, varbind return target->Declaration()->Type() == compare->Declaration()->Type(); } -void ETSChecker::AddBoxingFlagToPrimitiveType(TypeRelation *relation, Type *target) +void ETSChecker::AddBoxingFlagToPrimitiveType(checker::TypeRelation *relation, checker::Type *target) { auto boxingResult = PrimitiveTypeAsETSBuiltinType(target); if ((boxingResult != nullptr) && !relation->OnlyCheckBoxingUnboxing()) { @@ -2240,7 +2582,8 @@ void ETSChecker::AddBoxingFlagToPrimitiveType(TypeRelation *relation, Type *targ } } -void ETSChecker::AddUnboxingFlagToPrimitiveType(TypeRelation *relation, Type *source, Type *self) +void ETSChecker::AddUnboxingFlagToPrimitiveType(checker::TypeRelation *relation, checker::Type *source, + checker::Type *self) { auto unboxingResult = UnboxingConverter(this, relation, source, self).Result(); if ((unboxingResult != nullptr) && relation->IsTrue() && !relation->OnlyCheckBoxingUnboxing()) { @@ -2248,7 +2591,7 @@ void ETSChecker::AddUnboxingFlagToPrimitiveType(TypeRelation *relation, Type *so } } -void ETSChecker::CheckUnboxedTypeWidenable(TypeRelation *relation, Type *target, Type *self) +void ETSChecker::CheckUnboxedTypeWidenable(checker::TypeRelation *relation, checker::Type *target, checker::Type *self) { checker::SavedTypeRelationFlagsContext savedTypeRelationFlagCtx( relation, TypeRelationFlag::ONLY_CHECK_WIDENING | @@ -2264,7 +2607,8 @@ void ETSChecker::CheckUnboxedTypeWidenable(TypeRelation *relation, Type *target, } } -void ETSChecker::CheckUnboxedTypesAssignable(TypeRelation *relation, Type *source, Type *target) +void ETSChecker::CheckUnboxedTypesAssignable(checker::TypeRelation *relation, checker::Type *source, + checker::Type *target) { auto *unboxedSourceType = relation->GetChecker()->AsETSChecker()->ETSBuiltinTypeAsPrimitiveType(source); auto *unboxedTargetType = relation->GetChecker()->AsETSChecker()->ETSBuiltinTypeAsPrimitiveType(target); @@ -2278,7 +2622,8 @@ void ETSChecker::CheckUnboxedTypesAssignable(TypeRelation *relation, Type *sourc } } -void ETSChecker::CheckBoxedSourceTypeAssignable(TypeRelation *relation, Type *source, Type *target) +void ETSChecker::CheckBoxedSourceTypeAssignable(checker::TypeRelation *relation, checker::Type *source, + checker::Type *target) { ASSERT(relation != nullptr); checker::SavedTypeRelationFlagsContext savedTypeRelationFlagCtx( @@ -2310,7 +2655,8 @@ void ETSChecker::CheckBoxedSourceTypeAssignable(TypeRelation *relation, Type *so } } -void ETSChecker::CheckUnboxedSourceTypeWithWideningAssignable(TypeRelation *relation, Type *source, Type *target) +void ETSChecker::CheckUnboxedSourceTypeWithWideningAssignable(checker::TypeRelation *relation, checker::Type *source, + checker::Type *target) { auto *unboxedSourceType = relation->GetChecker()->AsETSChecker()->ETSBuiltinTypeAsPrimitiveType(source); if (unboxedSourceType == nullptr) { @@ -2702,14 +3048,14 @@ bool ETSChecker::TypeInference(Signature *signature, const ArenaVectorIsETSFunctionType()); InferTypesForLambda(lambda, typeAnn->AsETSFunctionType()); - Type *const argType = arrowFuncExpr->Check(this); - const Type *targetType = TryGettingFunctionTypeFromInvokeFunction(signature->Params()[index]->TsType()); + Type *const argumentType = GetApparentType(arrowFuncExpr->Check(this)); + Type *const parameterType = GetApparentType(signature->Params()[index]->TsType()); + const Type *targetType = TryGettingFunctionTypeFromInvokeFunction(parameterType); const std::initializer_list msg = { - "Type '", argType, "' is not compatible with type '", targetType, "' at index ", index + 1}; + "Type '", argumentType, "' is not compatible with type '", targetType, "' at index ", index + 1}; - checker::InvocationContext invokationCtx(Relation(), arguments[index], argType, - signature->Params()[index]->TsType(), arrowFuncExpr->Start(), msg, - flags); + checker::InvocationContext invokationCtx(Relation(), arguments[index], argumentType, parameterType, + arrowFuncExpr->Start(), msg, flags); invocable &= invokationCtx.IsInvocable(); } diff --git a/ets2panda/checker/ets/object.cpp b/ets2panda/checker/ets/object.cpp index 21661758cddcb67ddee91b6ef1623af3c87acb1d..f6402412f388a0cc35ae9dcd89e1f549896a6387 100644 --- a/ets2panda/checker/ets/object.cpp +++ b/ets2panda/checker/ets/object.cpp @@ -15,7 +15,6 @@ #include "boxingConverter.h" #include "varbinder/variableFlags.h" -#include "checker/ets/castingContext.h" #include "checker/types/ets/etsObjectType.h" #include "ir/astNode.h" #include "ir/typeNode.h" @@ -28,33 +27,23 @@ #include "ir/statements/blockStatement.h" #include "ir/statements/variableDeclarator.h" #include "ir/statements/expressionStatement.h" -#include "ir/expressions/binaryExpression.h" #include "ir/expressions/identifier.h" #include "ir/expressions/functionExpression.h" #include "ir/expressions/memberExpression.h" #include "ir/expressions/callExpression.h" -#include "ir/expressions/superExpression.h" #include "ir/expressions/assignmentExpression.h" -#include "ir/expressions/thisExpression.h" -#include "ir/statements/classDeclaration.h" -#include "ir/statements/returnStatement.h" #include "ir/ts/tsClassImplements.h" #include "ir/ts/tsInterfaceHeritage.h" -#include "ir/ts/tsInterfaceBody.h" #include "ir/ts/tsInterfaceDeclaration.h" #include "ir/ts/tsTypeParameter.h" #include "ir/ts/tsTypeParameterDeclaration.h" #include "ir/ets/etsTypeReference.h" #include "ir/ets/etsTypeReferencePart.h" #include "ir/ets/etsNewClassInstanceExpression.h" -#include "varbinder/variable.h" -#include "varbinder/scope.h" #include "varbinder/declaration.h" -#include "varbinder/ETSBinder.h" #include "checker/ETSchecker.h" -#include "checker/types/typeFlag.h" #include "checker/types/ets/etsDynamicType.h" -#include "checker/types/ets/types.h" +#include "checker/types/ets/etsTupleType.h" #include "checker/ets/typeRelationContext.h" #include "ir/ets/etsUnionType.h" @@ -1324,11 +1313,13 @@ PropertySearchFlags ETSChecker::GetSearchFlags(const ir::MemberExpression *const (targetRef->HasFlag(varbinder::VariableFlags::CLASS_OR_INTERFACE) || (targetRef->HasFlag(varbinder::VariableFlags::TYPE_ALIAS) && targetRef->TsType()->Variable()->HasFlag(varbinder::VariableFlags::CLASS_OR_INTERFACE)))) { - searchFlag &= ~(PropertySearchFlags::SEARCH_INSTANCE); + searchFlag &= ~PropertySearchFlags::SEARCH_INSTANCE; } else if (memberExpr->Object()->IsThisExpression() || + (targetRef != nullptr && targetRef->Declaration() != nullptr && + targetRef->Declaration()->IsLetOrConstDecl()) || (memberExpr->Object()->IsIdentifier() && memberExpr->ObjType()->GetDeclNode() != nullptr && memberExpr->ObjType()->GetDeclNode()->IsTSInterfaceDeclaration())) { - searchFlag &= ~(PropertySearchFlags::SEARCH_STATIC); + searchFlag &= ~PropertySearchFlags::SEARCH_STATIC; } return searchFlag; } diff --git a/ets2panda/checker/ets/typeCreation.cpp b/ets2panda/checker/ets/typeCreation.cpp index 4fd56095113e4ebb06c005343de5cf3c54885c92..5a3dd13f2384bbece40ec9595831800b156898de 100644 --- a/ets2panda/checker/ets/typeCreation.cpp +++ b/ets2panda/checker/ets/typeCreation.cpp @@ -15,7 +15,8 @@ #include #include "checker/ETSchecker.h" -#include "checker/ets/boxingConverter.h" + +#include "checker/types/globalTypesHolder.h" #include "checker/types/ets/byteType.h" #include "checker/types/ets/charType.h" #include "checker/types/ets/etsDynamicFunctionType.h" @@ -32,9 +33,7 @@ #include "ir/ts/tsEnumDeclaration.h" #include "ir/ts/tsEnumMember.h" #include "ir/ts/tsInterfaceDeclaration.h" -#include "varbinder/varbinder.h" -#include "varbinder/ETSBinder.h" -#include "parser/program/program.h" +#include "checker/ets/boxingConverter.h" #include "util/helpers.h" #include "checker/types/ts/bigintType.h" diff --git a/ets2panda/checker/types/ets/etsNullishTypes.h b/ets2panda/checker/types/ets/etsNullishTypes.h index becc8594a8db1e66cc8a03217b7cdd734e347326..195332ffd27cb4541ca6d7ee6bdd31beb21f0027 100644 --- a/ets2panda/checker/types/ets/etsNullishTypes.h +++ b/ets2panda/checker/types/ets/etsNullishTypes.h @@ -37,6 +37,11 @@ public: void ToDebugInfoType([[maybe_unused]] std::stringstream &ss) const override; Type *Instantiate(ArenaAllocator *allocator, TypeRelation *relation, GlobalTypesHolder *globalTypes) override; + + std::tuple ResolveConditionExpr() const override + { + return {IsConstantType(), false}; + } }; class ETSUndefinedType : public Type { @@ -55,6 +60,11 @@ public: void ToDebugInfoType([[maybe_unused]] std::stringstream &ss) const override; Type *Instantiate(ArenaAllocator *allocator, TypeRelation *relation, GlobalTypesHolder *globalTypes) override; + + std::tuple ResolveConditionExpr() const override + { + return {IsConstantType(), false}; + } }; } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsObjectType.cpp b/ets2panda/checker/types/ets/etsObjectType.cpp index 9900e213c9a7352e972c7013bcad521b16e09504..77518f3c801a70bc489ddc4e9355a43e7bee0e65 100644 --- a/ets2panda/checker/types/ets/etsObjectType.cpp +++ b/ets2panda/checker/types/ets/etsObjectType.cpp @@ -15,11 +15,12 @@ #include "etsObjectType.h" -#include "varbinder/declaration.h" #include "checker/ETSchecker.h" #include "checker/ets/conversion.h" +#include "checker/types/ts/objectType.h" #include "checker/types/typeFlag.h" #include "checker/types/typeRelation.h" +#include "checker/types/globalTypesHolder.h" #include "ir/base/methodDefinition.h" #include "ir/base/scriptFunction.h" #include "ir/expressions/identifier.h" @@ -885,4 +886,33 @@ void ETSObjectType::DebugInfoTypeFromName(std::stringstream &ss, util::StringVie ss << compiler::Signatures::MANGLE_SEPARATOR; } +std::uint32_t ETSObjectType::GetPrecedence(ETSObjectType const *type) noexcept +{ + ASSERT(type != nullptr); + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_BYTE)) { + return 1U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)) { + return 2U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_SHORT)) { + return 3U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_INT)) { + return 4U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG)) { + return 5U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_FLOAT)) { + return 6U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_DOUBLE)) { + return 7U; + } + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_BIGINT)) { + return 8U; + } + return 0U; +} } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsObjectType.h b/ets2panda/checker/types/ets/etsObjectType.h index 7ecf3b997deafb6ca0892b7b02070fb11fdb747c..d749000bf0b9f716aa6b6f518890891db3264788 100644 --- a/ets2panda/checker/types/ets/etsObjectType.h +++ b/ets2panda/checker/types/ets/etsObjectType.h @@ -537,6 +537,8 @@ public: return {false, false}; } + [[nodiscard]] static std::uint32_t GetPrecedence(ETSObjectType const *type) noexcept; + bool IsPropertiesInstantiated() const { return propertiesInstantiated_; diff --git a/ets2panda/checker/types/ets/etsUnionType.cpp b/ets2panda/checker/types/ets/etsUnionType.cpp index 736ac4688b43e00611918c19345669ecb631d024..bb4253a1fe27976bec88340fbead01b688180e2d 100644 --- a/ets2panda/checker/types/ets/etsUnionType.cpp +++ b/ets2panda/checker/types/ets/etsUnionType.cpp @@ -269,6 +269,8 @@ void ETSUnionType::LinearizeAndEraseIdentical(TypeRelation *relation, ArenaVecto auto const &otherTypes = ct->AsETSUnionType()->ConstituentTypes(); types.insert(types.end(), otherTypes.begin(), otherTypes.end()); types[i] = nullptr; + } else if (ct->IsNeverType()) { + types[i] = nullptr; } } size_t insPos = 0; @@ -349,6 +351,211 @@ void ETSUnionType::IsSubtypeOf(TypeRelation *relation, Type *target) } } +// NOTE! When calling this method we assume that 'AssignmentTarget(...)' check was passes successfully, +// thus the required assignable type always exists. +checker::Type *ETSUnionType::GetAssignableType(checker::ETSChecker *checker, checker::Type *sourceType) const noexcept +{ + if (sourceType->IsETSUnionType() || sourceType->IsETSArrayType() || sourceType->IsETSFunctionType()) { + return sourceType; + } + + auto *objectType = sourceType->IsETSObjectType() ? sourceType->AsETSObjectType() : nullptr; + if (objectType != nullptr && (!objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE) || + objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_STRING))) { + // NOTE: here wo don't cast the actual type to possible base type using in the union, but use it as is! + return sourceType; + } + + std::map numericTypes {}; + bool const isBool = objectType != nullptr ? objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN) + : sourceType->HasTypeFlag(TypeFlag::ETS_BOOLEAN); + bool const isChar = objectType != nullptr ? objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR) + : sourceType->HasTypeFlag(TypeFlag::CHAR); + + if (checker::Type *assignableType = GetAssignableBuiltinType(checker, objectType, isBool, isChar, numericTypes); + assignableType != nullptr) { + return assignableType; + } + + if (auto const sourceId = + objectType != nullptr ? ETSObjectType::GetPrecedence(objectType) : Type::GetPrecedence(sourceType); + sourceId > 0U) { + for (auto const [id, type] : numericTypes) { + if (id >= sourceId) { + return type; + } + } + } + + for (auto *constituentType : constituentTypes_) { + if (constituentType->IsETSObjectType() && constituentType->AsETSObjectType()->IsGlobalETSObjectType()) { + return constituentType; + } + } + + return nullptr; +} + +checker::Type *ETSUnionType::GetAssignableBuiltinType( + checker::ETSChecker *checker, checker::ETSObjectType *sourceType, bool const isBool, bool const isChar, + std::map &numericTypes) const noexcept +{ + checker::Type *assignableType = nullptr; + + for (auto *constituentType : constituentTypes_) { + if (!constituentType->IsETSObjectType()) { + continue; + } + + auto *const type = constituentType->AsETSObjectType(); + if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN)) { + if (isBool) { + assignableType = constituentType; + break; + } + } else if (type->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)) { + if (isChar) { + assignableType = constituentType; + break; + } + } else if (auto const id = ETSObjectType::GetPrecedence(type); id > 0U) { + numericTypes.emplace(id, constituentType); + } else if (assignableType == nullptr && sourceType != nullptr && + checker->Relation()->IsSupertypeOf(type, sourceType)) { + assignableType = constituentType; + } + } + + return assignableType; +} + +bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::ETSObjectType *sourceType) noexcept +{ + std::map::const_iterator> numericTypes {}; + bool const isBool = sourceType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN); + bool const isChar = sourceType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR); + + auto it = constituentTypes_.cbegin(); + while (it != constituentTypes_.cend()) { + auto *constituentType = (*it)->IsETSTypeParameter() ? checker->GetApparentType(*it) : *it; + + if (checker->Relation()->IsIdenticalTo(constituentType, sourceType) || + // NOTE: just a temporary solution because now Relation()->IsIdenticalTo(...) returns + // 'false' for the types like 'ArrayLike' + constituentType->ToString() == static_cast(sourceType)->ToString()) { + constituentTypes_.erase(it); + return true; + } + + if (checker->Relation()->IsSupertypeOf(constituentType, sourceType)) { + return true; + } + if (checker->Relation()->IsSupertypeOf(sourceType, constituentType)) { + return true; + } + + if (constituentType->IsETSObjectType()) { + auto *const objectType = (*it)->AsETSObjectType(); + if (isBool && objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN)) { + constituentTypes_.erase(it); + return true; + } + if (isChar && objectType->HasObjectFlag(ETSObjectFlags::BUILTIN_CHAR)) { + constituentTypes_.erase(it); + return true; + } + if (auto const id = ETSObjectType::GetPrecedence(objectType); id > 0U) { + numericTypes.emplace(id, it); + } + } + + ++it; + } + + if (auto const sourceId = ETSObjectType::GetPrecedence(sourceType); sourceId > 0U) { + for (auto const [id, it1] : numericTypes) { + if (id >= sourceId) { + constituentTypes_.erase(it1); + return true; + } + } + } + + return false; +} + +bool ETSUnionType::ExtractType(checker::ETSChecker *checker, checker::ETSArrayType *sourceType) noexcept +{ + auto it = constituentTypes_.cbegin(); + while (it != constituentTypes_.cend()) { + if (auto *constituentType = checker->GetApparentType(*it); + constituentType != nullptr && constituentType->IsETSArrayType()) { + if (checker->Relation()->IsIdenticalTo(constituentType, sourceType) || + // NOTE: just a temporary solution because now Relation()->IsIdenticalTo(...) returns + // 'false' for the types like 'ArrayLike' + constituentType->ToString() == static_cast(sourceType)->ToString()) { + constituentTypes_.erase(it); + return true; + } + + if (checker->Relation()->IsSupertypeOf(constituentType, sourceType)) { + return true; + } + if (checker->Relation()->IsSupertypeOf(sourceType, constituentType)) { + return true; + } + } + ++it; + } + + it = constituentTypes_.cbegin(); + while (it != constituentTypes_.cend()) { + if (auto *constituentType = checker->GetApparentType(*it); + constituentType != nullptr && constituentType->IsETSObjectType() && + constituentType->AsETSObjectType()->IsGlobalETSObjectType()) { + return true; + } + ++it; + } + + return false; +} + +std::pair ETSUnionType::GetComplimentaryType(ETSChecker *const checker, + checker::Type *sourceType) +{ + checker::Type *clone = Clone(checker); + bool ok = true; + + if (sourceType->IsETSUnionType()) { + for (auto *const constituentType : sourceType->AsETSUnionType()->ConstituentTypes()) { + if (ok = clone->AsETSUnionType()->ExtractType(checker, constituentType->AsETSObjectType()); !ok) { + break; + } + } + } else if (sourceType->IsETSArrayType()) { + ok = clone->AsETSUnionType()->ExtractType(checker, sourceType->AsETSArrayType()); + } else { + if (sourceType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE) && !sourceType->IsETSVoidType()) { + sourceType = checker->PrimitiveTypeAsETSBuiltinType(sourceType); + } + + if (sourceType->IsETSObjectType()) { + ok = clone->AsETSUnionType()->ExtractType(checker, sourceType->AsETSObjectType()); + } + } + + if (!ok) { + return std::make_pair(checker->GetGlobalTypesHolder()->GlobalNeverType(), this); + } + + if (clone->AsETSUnionType()->ConstituentTypes().size() == 1U) { + clone = clone->AsETSUnionType()->ConstituentTypes().front(); + } + + return std::make_pair(sourceType, clone); +} + Type *ETSUnionType::FindTypeIsCastableToThis(ir::Expression *node, TypeRelation *relation, Type *source) const { ASSERT(node); @@ -479,5 +686,4 @@ bool ETSUnionType::HasUndefinedType() const } return false; } - } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/ets/etsUnionType.h b/ets2panda/checker/types/ets/etsUnionType.h index e1d99fc68b89e1485221fbd6097b3618627501c6..490039df3ac9e773ed2f0eca3cd098131ce04b6c 100644 --- a/ets2panda/checker/types/ets/etsUnionType.h +++ b/ets2panda/checker/types/ets/etsUnionType.h @@ -27,7 +27,7 @@ public: // constituentTypes must be normalized explicit ETSUnionType(ETSChecker *checker, ArenaVector &&constituentTypes); - const ArenaVector &ConstituentTypes() const + [[nodiscard]] const ArenaVector &ConstituentTypes() const noexcept { return constituentTypes_; } @@ -69,6 +69,10 @@ public: return std::all_of(constituentTypes_.cbegin(), constituentTypes_.cend(), p); } + [[nodiscard]] checker::Type *GetAssignableType(ETSChecker *checker, checker::Type *sourceType) const noexcept; + [[nodiscard]] std::pair GetComplimentaryType(ETSChecker *checker, + checker::Type *sourceType); + private: static bool EachTypeRelatedToSomeType(TypeRelation *relation, ETSUnionType *source, ETSUnionType *target); static bool TypeRelatedToSomeType(TypeRelation *relation, Type *source, ETSUnionType *target); @@ -79,10 +83,16 @@ private: void RelationTarget(TypeRelation *relation, Type *source, RelFN const &relFn); static void LinearizeAndEraseIdentical(TypeRelation *relation, ArenaVector &types); + [[nodiscard]] bool ExtractType(ETSChecker *checker, checker::ETSObjectType *sourceType) noexcept; + [[nodiscard]] bool ExtractType(ETSChecker *checker, checker::ETSArrayType *sourceType) noexcept; + + [[nodiscard]] checker::Type *GetAssignableBuiltinType( + checker::ETSChecker *checker, checker::ETSObjectType *sourceType, bool isBool, bool isChar, + std::map &numericTypes) const noexcept; static Type *ComputeAssemblerLUB(ETSChecker *checker, ETSUnionType *un); - ArenaVector const constituentTypes_; + ArenaVector constituentTypes_; Type *assemblerLub_ {nullptr}; }; } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/type.cpp b/ets2panda/checker/types/type.cpp index 7596a971c18fcf8b48005c0be8030d66781c6a4e..3d443396d49f3fa039cd9d1107931f8b439ddb36 100644 --- a/ets2panda/checker/types/type.cpp +++ b/ets2panda/checker/types/type.cpp @@ -18,6 +18,7 @@ #include "checker/types/typeFlag.h" #include "checker/types/typeRelation.h" #include "checker/types/ets/etsObjectType.h" +#include "checker/checker.h" namespace ark::es2panda::checker { @@ -60,7 +61,7 @@ void Type::ToStringAsSrc(std::stringstream &ss) const std::string Type::ToString() const { - std::stringstream ss; + std::stringstream ss {}; ToString(ss); return ss.str(); } @@ -127,8 +128,43 @@ Type *Type::Instantiate([[maybe_unused]] ArenaAllocator *allocator, [[maybe_unus return nullptr; } +Type *Type::Clone(Checker *const checker) +{ + return Instantiate(checker->Allocator(), checker->Relation(), checker->GetGlobalTypesHolder()); +} + Type *Type::Substitute([[maybe_unused]] TypeRelation *relation, [[maybe_unused]] const Substitution *substitution) { return this; } + +std::uint32_t Type::GetPrecedence(Type const *type) noexcept +{ + ASSERT(type != nullptr); + if (type->HasTypeFlag(TypeFlag::BYTE)) { + return 1U; + } + if (type->HasTypeFlag(TypeFlag::CHAR)) { + return 2U; + } + if (type->HasTypeFlag(TypeFlag::SHORT)) { + return 3U; + } + if (type->HasTypeFlag(TypeFlag::INT)) { + return 4U; + } + if (type->HasTypeFlag(TypeFlag::LONG)) { + return 5U; + } + if (type->HasTypeFlag(TypeFlag::FLOAT)) { + return 6U; + } + if (type->HasTypeFlag(TypeFlag::DOUBLE)) { + return 7U; + } + if (type->HasTypeFlag(TypeFlag::BIGINT)) { + return 8U; + } + return 0U; +} } // namespace ark::es2panda::checker diff --git a/ets2panda/checker/types/type.h b/ets2panda/checker/types/type.h index e66d11f301115f69862ef60caab6339d4754b818..18e2722e966906790f193b5a19a2e12c2ad3db4f 100644 --- a/ets2panda/checker/types/type.h +++ b/ets2panda/checker/types/type.h @@ -21,10 +21,6 @@ #include "checker/types/typeRelation.h" #include "checker/types/typeFacts.h" -#include "macros.h" -#include -#include - namespace ark::es2panda::varbinder { class Variable; } // namespace ark::es2panda::varbinder @@ -229,8 +225,9 @@ public: bool IsLambdaObject() const; virtual void ToString(std::stringstream &ss, bool precise) const = 0; void ToString(std::stringstream &ss) const; - std::string ToString() const; - std::string ToStringPrecise() const; + [[nodiscard]] std::string ToString() const; + [[nodiscard]] std::string ToStringPrecise() const; + virtual void ToStringAsSrc(std::stringstream &ss) const; std::string ToStringAsSrc() const; @@ -262,7 +259,10 @@ public: virtual void IsSubtypeOf(TypeRelation *relation, Type *target); virtual Type *AsSuper(Checker *checker, varbinder::Variable *sourceVar); + [[nodiscard]] static std::uint32_t GetPrecedence(Type const *type) noexcept; + virtual Type *Instantiate(ArenaAllocator *allocator, TypeRelation *relation, GlobalTypesHolder *globalTypes); + [[nodiscard]] virtual Type *Clone(Checker *checker); virtual Type *Substitute(TypeRelation *relation, const Substitution *substitution); protected: diff --git a/ets2panda/compiler/base/lreference.cpp b/ets2panda/compiler/base/lreference.cpp index a047c992aa488e77e5e29063dec0bab15d1340bf..3175dafa63d31b4828a3c2ae7d1a404c2ce9980f 100644 --- a/ets2panda/compiler/base/lreference.cpp +++ b/ets2panda/compiler/base/lreference.cpp @@ -22,7 +22,7 @@ #include "compiler/core/pandagen.h" #include "compiler/core/ETSGen.h" #include "checker/types/ets/etsUnionType.h" -#include "ir/astNode.h" +#include "checker/types/ets/etsTupleType.h" #include "ir/base/spreadElement.h" #include "ir/base/classProperty.h" #include "ir/base/classDefinition.h" diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index 770c3326900a8615bf5d71c8de4828e64c955ca8..0959996d3d9e458aeb86866060fb9ef4465c7bda 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -18,7 +18,6 @@ #include "compiler/base/catchTable.h" #include "compiler/base/condition.h" #include "compiler/base/lreference.h" -#include "compiler/core/ETSGen.h" #include "compiler/core/switchBuilder.h" #include "compiler/function/functionBuilder.h" #include "checker/types/ets/etsDynamicFunctionType.h" @@ -1038,13 +1037,25 @@ void ETSCompiler::Compile(const ir::Identifier *expr) const return; } - auto ttctx = compiler::TargetTypeContext(etsg, expr->TsType()); + auto *const smartType = expr->TsType(); + auto ttctx = compiler::TargetTypeContext(etsg, smartType); ASSERT(expr->Variable() != nullptr); if (!expr->Variable()->HasFlag(varbinder::VariableFlags::TYPE_ALIAS)) { etsg->LoadVar(expr, expr->Variable()); } else { - etsg->SetAccumulatorType(expr->TsType()); + etsg->SetAccumulatorType(smartType); + } + + // In case when smart cast type of identifier differs from common variable type + // set the accumulator type to the correct actual value and perform cast if required + if (!etsg->Checker()->AsETSChecker()->Relation()->IsIdenticalTo(const_cast(smartType), + expr->Variable()->TsType())) { + etsg->SetAccumulatorType(smartType); + if (smartType->IsETSReferenceType() && //! smartType->DefinitelyNotETSNullish() && + (expr->Parent() == nullptr || !expr->Parent()->IsTSAsExpression())) { + etsg->CastToReftype(expr, smartType, false); + } } } @@ -1128,11 +1139,12 @@ void ETSCompiler::Compile(const ir::MemberExpression *expr) const return; } - if (etsg->Checker()->IsVariableStatic(expr->PropVar())) { + auto const *const variable = expr->PropVar(); + if (etsg->Checker()->IsVariableStatic(variable)) { auto ttctx = compiler::TargetTypeContext(etsg, expr->TsType()); if (expr->PropVar()->TsType()->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) { - checker::Signature *sig = expr->PropVar()->TsType()->AsETSFunctionType()->FindGetter(); + checker::Signature *sig = variable->TsType()->AsETSFunctionType()->FindGetter(); etsg->CallStatic0(expr, sig->InternalName()); etsg->SetAccumulatorType(expr->TsType()); return; @@ -1152,8 +1164,9 @@ void ETSCompiler::Compile(const ir::MemberExpression *expr) const auto ttctx = compiler::TargetTypeContext(etsg, expr->TsType()); - if (expr->PropVar()->TsType()->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) { - checker::Signature *sig = expr->PropVar()->TsType()->AsETSFunctionType()->FindGetter(); + if (auto const *const variableType = variable->TsType(); + variableType->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) { + checker::Signature *sig = variableType->AsETSFunctionType()->FindGetter(); etsg->CallThisVirtual0(expr, objReg, sig->InternalName()); } else if (objectType->IsETSDynamicType()) { etsg->LoadPropertyDynamic(expr, expr->TsType(), objReg, propName); diff --git a/ets2panda/compiler/core/ETSGen.cpp b/ets2panda/compiler/core/ETSGen.cpp index ec269411c3fe47591d58e12ac9a0edaf5f83600c..4a27efbe95867c5241677274c32ec1aeb6caf1be 100644 --- a/ets2panda/compiler/core/ETSGen.cpp +++ b/ets2panda/compiler/core/ETSGen.cpp @@ -44,6 +44,7 @@ #include "checker/types/ets/etsAsyncFuncReturnType.h" #include "checker/types/ets/types.h" #include "parser/program/program.h" +#include "checker/types/globalTypesHolder.h" namespace ark::es2panda::compiler { @@ -1186,6 +1187,15 @@ void ETSGen::EmitUnboxedCall(const ir::AstNode *node, std::string_view signature CheckedReferenceNarrowing(node, boxedType); } + // to cast to primitive types we probably have to cast to corresponding boxed built-in types first. + auto *const checker = Checker()->AsETSChecker(); + auto const *accumulatorType = GetAccumulatorType(); + if (accumulatorType->IsETSObjectType() && //! accumulatorType->DefinitelyNotETSNullish() && + !checker->Relation()->IsIdenticalTo(const_cast(accumulatorType), + const_cast(boxedType))) { + CastToReftype(node, boxedType, false); + } + Ra().Emit(node, signatureFlag, dummyReg_, 0); SetAccumulatorType(targetType); } @@ -2130,7 +2140,8 @@ void ETSGen::Binary(const ir::AstNode *node, lexer::TokenType op, VReg lhs) } } ASSERT(node->IsAssignmentExpression() || node->IsBinaryExpression()); - ASSERT(GetAccumulatorType() == node->AsExpression()->TsType()); + ASSERT(Checker()->Relation()->IsIdenticalTo(const_cast(GetAccumulatorType()), + const_cast(node->AsExpression()->TsType()))); } void ETSGen::Condition(const ir::AstNode *node, lexer::TokenType op, VReg lhs, Label *ifFalse) diff --git a/ets2panda/compiler/core/emitter.cpp b/ets2panda/compiler/core/emitter.cpp index 39a717516009b4dbfeb85fbe6d9d01e8f9f49806..589c14ace07238e6245f6f747842c4345634c1fe 100644 --- a/ets2panda/compiler/core/emitter.cpp +++ b/ets2panda/compiler/core/emitter.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -26,16 +26,9 @@ #include "compiler/debugger/debuginfoDumper.h" #include "compiler/base/catchTable.h" #include "es2panda.h" -#include "ir/statements/blockStatement.h" #include "parser/program/program.h" #include "checker/types/type.h" #include "generated/isa.h" -#include "macros.h" - -#include -#include -#include -#include namespace ark::es2panda::compiler { using LiteralPair = std::pair; diff --git a/ets2panda/compiler/lowering/ets/optionalLowering.cpp b/ets2panda/compiler/lowering/ets/optionalLowering.cpp index 63481c63979ba8b3f527fffae914b7ce090673be..029d4cb16b4dcb4e58bc653650817c5809af941b 100644 --- a/ets2panda/compiler/lowering/ets/optionalLowering.cpp +++ b/ets2panda/compiler/lowering/ets/optionalLowering.cpp @@ -71,7 +71,7 @@ static ir::AstNode *LowerOptionalExpr(GetSource const &getSource, SetSource cons stmts[0]->AsVariableDeclaration()->Declarators()[0]->SetInit(getSource(expr)); stmts[1]->AsExpressionStatement()->GetExpression()->AsConditionalExpression()->SetAlternate(chain->GetExpression()); - setSource(expr, parser->CreateFormattedExpression("@@I1!", parser::DEFAULT_SOURCE_FILE, + setSource(expr, parser->CreateFormattedExpression("@@I1", parser::DEFAULT_SOURCE_FILE, tmpIdentClone->Clone(allocator, nullptr))); return sequenceExpr; } diff --git a/ets2panda/compiler/lowering/ets/tupleLowering.cpp b/ets2panda/compiler/lowering/ets/tupleLowering.cpp index 2a2d77bb0ea0c4edde6f5323e8b95117e9ae071b..d945e2cb95061c984f07164c34a025c4750a45b8 100644 --- a/ets2panda/compiler/lowering/ets/tupleLowering.cpp +++ b/ets2panda/compiler/lowering/ets/tupleLowering.cpp @@ -16,13 +16,10 @@ #include "tupleLowering.h" #include "checker/ETSchecker.h" -#include "checker/checker.h" -#include "checker/types/type.h" +#include "checker/types/ets/etsTupleType.h" #include "compiler/core/ASTVerifier.h" #include "compiler/core/compilerContext.h" #include "compiler/lowering/util.h" -#include "ir/astNode.h" -#include "ir/expression.h" #include "ir/expressions/assignmentExpression.h" #include "ir/expressions/identifier.h" #include "ir/expressions/memberExpression.h" diff --git a/ets2panda/ir/astNode.h b/ets2panda/ir/astNode.h index be32df0db641d28cb1df5abbdaa843aa7e14e306..2262ce13c0ca61462ad9316f23ddfd5ef82700fc 100644 --- a/ets2panda/ir/astNode.h +++ b/ets2panda/ir/astNode.h @@ -22,9 +22,6 @@ #include "lexer/token/sourceLocation.h" #include "util/enumbitops.h" -#include -#include "macros.h" - namespace ark::es2panda::compiler { class PandaGen; class ETSGen; diff --git a/ets2panda/ir/expressions/arrayExpression.cpp b/ets2panda/ir/expressions/arrayExpression.cpp index fb53daba5369d3b125f946c130d1630f1e1a1a0e..07cafdd76b65dede41ea7a90d2fa5ac403733ab8 100644 --- a/ets2panda/ir/expressions/arrayExpression.cpp +++ b/ets2panda/ir/expressions/arrayExpression.cpp @@ -17,7 +17,7 @@ #include "checker/ETSchecker.h" #include "checker/TSchecker.h" -#include "checker/ets/castingContext.h" +#include "checker/types/ets/etsTupleType.h" #include "checker/ets/typeRelationContext.h" #include "checker/ts/destructuringContext.h" #include "compiler/core/ETSGen.h" diff --git a/ets2panda/ir/expressions/binaryExpression.cpp b/ets2panda/ir/expressions/binaryExpression.cpp index 212c09939918854a1670f5d114c2ca8c9757fc39..19c716ba9c6604be7dbc0977501618bd029c46e4 100644 --- a/ets2panda/ir/expressions/binaryExpression.cpp +++ b/ets2panda/ir/expressions/binaryExpression.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -18,8 +18,10 @@ #include "compiler/core/pandagen.h" #include "compiler/core/ETSGen.h" #include "checker/TSchecker.h" -#include "ir/astDump.h" +#include "ir/astNode.h" +#include "ir/expression.h" #include "ir/srcDump.h" +#include "ir/visitor/AstVisitor.h" namespace ark::es2panda::ir { void BinaryExpression::TransformChildren(const NodeTransformer &cb) diff --git a/ets2panda/ir/expressions/binaryExpression.h b/ets2panda/ir/expressions/binaryExpression.h index 5a16ef9e078c515b60eae19a017a950186e5ca88..9b60317962983a0b6d248b5c6fa586617d059c6c 100644 --- a/ets2panda/ir/expressions/binaryExpression.h +++ b/ets2panda/ir/expressions/binaryExpression.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -16,8 +16,8 @@ #ifndef ES2PANDA_IR_EXPRESSION_BINARY_EXPRESSION_H #define ES2PANDA_IR_EXPRESSION_BINARY_EXPRESSION_H +#include "checker/checkerContext.h" #include "ir/expression.h" -#include "lexer/token/tokenType.h" namespace ark::es2panda::checker { class ETSAnalyzer; diff --git a/ets2panda/ir/expressions/conditionalExpression.cpp b/ets2panda/ir/expressions/conditionalExpression.cpp index 7428c26f9ca62b08dd15f0a0a0430fcfde0021af..bd7b5add97e14602a8751656f8d0465268a55756 100644 --- a/ets2panda/ir/expressions/conditionalExpression.cpp +++ b/ets2panda/ir/expressions/conditionalExpression.cpp @@ -84,22 +84,14 @@ checker::Type *ConditionalExpression::Check(checker::ETSChecker *checker) ConditionalExpression *ConditionalExpression::Clone(ArenaAllocator *const allocator, AstNode *const parent) { - auto *const test = test_ != nullptr ? test_->Clone(allocator, nullptr)->AsExpression() : nullptr; - auto *const consequent = consequent_ != nullptr ? consequent_->Clone(allocator, nullptr)->AsExpression() : nullptr; - auto *const alternate = alternate_ != nullptr ? alternate_->Clone(allocator, nullptr)->AsExpression() : nullptr; + auto *const test = test_->Clone(allocator, nullptr)->AsExpression(); + auto *const consequent = consequent_->Clone(allocator, nullptr)->AsExpression(); + auto *const alternate = alternate_->Clone(allocator, nullptr)->AsExpression(); if (auto *const clone = allocator->New(test, consequent, alternate); clone != nullptr) { - if (test != nullptr) { - test->SetParent(clone); - } - - if (consequent != nullptr) { - consequent->SetParent(clone); - } - - if (alternate != nullptr) { - alternate->SetParent(clone); - } + test->SetParent(clone); + consequent->SetParent(clone); + alternate->SetParent(clone); if (parent != nullptr) { clone->SetParent(parent); diff --git a/ets2panda/ir/expressions/conditionalExpression.h b/ets2panda/ir/expressions/conditionalExpression.h index 96ba89d6cedfcf78d7426b58eeba32458313abc8..8cd6031ad033c9dcc6d2ecc7d1a3f8731e7a7cdf 100644 --- a/ets2panda/ir/expressions/conditionalExpression.h +++ b/ets2panda/ir/expressions/conditionalExpression.h @@ -37,10 +37,6 @@ public: { } - // NOTE (csabahurton): these friend relationships can be removed once there are getters for private fields - friend class checker::TSAnalyzer; - friend class checker::ETSAnalyzer; - [[nodiscard]] const Expression *Test() const noexcept { return test_; @@ -62,6 +58,11 @@ public: return consequent_; } + [[nodiscard]] Expression *Consequent() noexcept + { + return consequent_; + } + void SetConsequent(Expression *expr) noexcept { consequent_ = expr; @@ -73,6 +74,11 @@ public: return alternate_; } + [[nodiscard]] Expression *Alternate() noexcept + { + return alternate_; + } + void SetAlternate(Expression *expr) noexcept { alternate_ = expr; diff --git a/ets2panda/ir/expressions/identifier.cpp b/ets2panda/ir/expressions/identifier.cpp index 17c05b958022f74b88c38d2816258859f4fa5260..8d2fe53153ff7c6aab1a80385945828c1dcd4aae 100644 --- a/ets2panda/ir/expressions/identifier.cpp +++ b/ets2panda/ir/expressions/identifier.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -37,6 +37,7 @@ Identifier::Identifier([[maybe_unused]] Tag const tag, Identifier const &other, Identifier *Identifier::Clone(ArenaAllocator *const allocator, AstNode *const parent) { if (auto *const clone = allocator->New(Tag {}, *this, allocator); clone != nullptr) { + clone->SetTsType(TsType()); if (parent != nullptr) { clone->SetParent(parent); } diff --git a/ets2panda/ir/expressions/identifier.h b/ets2panda/ir/expressions/identifier.h index 8a35310ae9aa52248540f5348b59b264db639fab..acfa6b6eb648f2f892de641743874af75cef7bec 100644 --- a/ets2panda/ir/expressions/identifier.h +++ b/ets2panda/ir/expressions/identifier.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -16,8 +16,8 @@ #ifndef ES2PANDA_IR_EXPRESSION_IDENTIFIER_H #define ES2PANDA_IR_EXPRESSION_IDENTIFIER_H +#include "checker/checkerContext.h" #include "ir/expression.h" -#include "util/ustring.h" #include "ir/validationInfo.h" namespace ark::es2panda::varbinder { diff --git a/ets2panda/ir/expressions/memberExpression.cpp b/ets2panda/ir/expressions/memberExpression.cpp index fc42331da9fa6ee9aefc2c127cb29dfb4d340c01..94b6931ec23978b2389cb97d06a3b843cf27da14 100644 --- a/ets2panda/ir/expressions/memberExpression.cpp +++ b/ets2panda/ir/expressions/memberExpression.cpp @@ -17,6 +17,7 @@ #include "checker/TSchecker.h" #include "checker/ets/castingContext.h" +#include "checker/types/ets/etsTupleType.h" #include "compiler/core/ETSGen.h" #include "compiler/core/pandagen.h" #include "ir/astDump.h" diff --git a/ets2panda/ir/expressions/unaryExpression.cpp b/ets2panda/ir/expressions/unaryExpression.cpp index effb17947b9200ec9743f86968e8e73bf028b3d4..8b0478c8ea7baa017af13ea5862afe1f1242f380 100644 --- a/ets2panda/ir/expressions/unaryExpression.cpp +++ b/ets2panda/ir/expressions/unaryExpression.cpp @@ -20,7 +20,6 @@ #include "checker/TSchecker.h" #include "checker/ETSchecker.h" #include "ir/astDump.h" -#include "ir/srcDump.h" namespace ark::es2panda::ir { void UnaryExpression::TransformChildren(const NodeTransformer &cb) @@ -83,4 +82,5 @@ UnaryExpression *UnaryExpression::Clone(ArenaAllocator *const allocator, AstNode throw Error(ErrorType::GENERIC, "", CLONE_ALLOCATION_ERROR); } + } // namespace ark::es2panda::ir diff --git a/ets2panda/ir/expressions/unaryExpression.h b/ets2panda/ir/expressions/unaryExpression.h index edd1da871d2eeb3224acc320802ec38fac9b380f..df35b4fec540fc9bfb8d88267425ca09976cb4b4 100644 --- a/ets2panda/ir/expressions/unaryExpression.h +++ b/ets2panda/ir/expressions/unaryExpression.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -16,8 +16,8 @@ #ifndef ES2PANDA_IR_EXPRESSION_UNARY_EXPRESSION_H #define ES2PANDA_IR_EXPRESSION_UNARY_EXPRESSION_H +#include "checker/checkerContext.h" #include "ir/expression.h" -#include "lexer/token/tokenType.h" namespace ark::es2panda::compiler { class PandaGen; diff --git a/ets2panda/ir/statements/ifStatement.cpp b/ets2panda/ir/statements/ifStatement.cpp index 24dba5ca9ba58cde938619a6d8aea576c9d2ec2c..c82bae2e519a092562e0ee0d67d67e1be5ae453a 100644 --- a/ets2panda/ir/statements/ifStatement.cpp +++ b/ets2panda/ir/statements/ifStatement.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -14,7 +14,6 @@ */ #include "ifStatement.h" -#include #include "checker/TSchecker.h" #include "compiler/core/ETSGen.h" @@ -100,4 +99,27 @@ checker::Type *IfStatement::Check([[maybe_unused]] checker::ETSChecker *checker) { return checker->GetAnalyzer()->Check(this); } + +IfStatement *IfStatement::Clone(ArenaAllocator *const allocator, AstNode *const parent) +{ + auto *const test = test_->Clone(allocator, nullptr)->AsExpression(); + auto *const consequent = consequent_->Clone(allocator, nullptr)->AsStatement(); + auto *const alternate = alternate_ != nullptr ? consequent_->Clone(allocator, nullptr)->AsStatement() : nullptr; + + if (auto *const clone = allocator->New(test, consequent, alternate); clone != nullptr) { + if (parent != nullptr) { + clone->SetParent(parent); + } + + test->SetParent(clone); + consequent->SetParent(clone); + if (alternate != nullptr) { + alternate->SetParent(clone); + } + + clone->SetRange(Range()); + return clone; + } + throw Error(ErrorType::GENERIC, "", CLONE_ALLOCATION_ERROR); +} } // namespace ark::es2panda::ir diff --git a/ets2panda/ir/statements/ifStatement.h b/ets2panda/ir/statements/ifStatement.h index 3541f32ee2a59b79de018f0409bff8f19babcc7c..0de01613717f0ef5b3365eb321963a73bc912d7f 100644 --- a/ets2panda/ir/statements/ifStatement.h +++ b/ets2panda/ir/statements/ifStatement.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Huawei Device Co., Ltd. + * Copyright (c) 2021 - 2024 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 @@ -26,36 +26,49 @@ class ETSAnalyzer; namespace ark::es2panda::ir { class Expression; -class IfStatement : public Statement { +class IfStatement final : public Statement { public: + IfStatement() = delete; + ~IfStatement() override = default; + + NO_COPY_SEMANTIC(IfStatement); + NO_MOVE_SEMANTIC(IfStatement); + explicit IfStatement(Expression *test, Statement *consequent, Statement *alternate) : Statement(AstNodeType::IF_STATEMENT), test_(test), consequent_(consequent), alternate_(alternate) { } - // NOTE (csabahurton): these friend relationships can be removed once there are getters for private fields - friend class checker::ETSAnalyzer; - friend class checker::TSAnalyzer; + [[nodiscard]] const Expression *Test() const noexcept + { + return test_; + } - const Expression *Test() const + [[nodiscard]] Expression *Test() noexcept { return test_; } - const Statement *Consequent() const + [[nodiscard]] const Statement *Consequent() const noexcept { return consequent_; } - Statement *Alternate() + [[nodiscard]] Statement *Consequent() noexcept + { + return consequent_; + } + + [[nodiscard]] Statement *Alternate() noexcept { return alternate_; } - const Statement *Alternate() const + [[nodiscard]] const Statement *Alternate() const noexcept { return alternate_; } + void TransformChildren(const NodeTransformer &cb) override; void SetReturnType(checker::ETSChecker *checker, checker::Type *type) override @@ -81,6 +94,8 @@ public: v->Accept(this); } + [[nodiscard]] IfStatement *Clone(ArenaAllocator *allocator, AstNode *parent) override; + private: Expression *test_; Statement *consequent_; diff --git a/ets2panda/parser/ETSparser.h b/ets2panda/parser/ETSparser.h index d4f1151550e91e91e3b7e71044166e1fdedaa24b..79e9d4cda2b4d9a11a734d7d4667fa997c1d442f 100644 --- a/ets2panda/parser/ETSparser.h +++ b/ets2panda/parser/ETSparser.h @@ -16,8 +16,6 @@ #ifndef ES2PANDA_PARSER_CORE_ETS_PARSER_H #define ES2PANDA_PARSER_CORE_ETS_PARSER_H -#include -#include "parserFlags.h" #include "util/arktsconfig.h" #include "util/importPathManager.h" #include "TypedParser.h" diff --git a/ets2panda/test/compiler/ets/array_indexing_with_chaining_non_nullish-expected.txt b/ets2panda/test/compiler/ets/array_indexing_with_chaining_non_nullish-expected.txt index f1f7c1a008a555cafa2e4025ad2ad6c14b30dd5e..fbcb2d6d444bb1a572116c537df974813e8b6b00 100644 --- a/ets2panda/test/compiler/ets/array_indexing_with_chaining_non_nullish-expected.txt +++ b/ets2panda/test/compiler/ets/array_indexing_with_chaining_non_nullish-expected.txt @@ -520,22 +520,9 @@ "alternate": { "type": "MemberExpression", "object": { - "type": "TSNonNullExpression", - "expression": { - "type": "Identifier", - "name": "gensym$_2", - "decorators": [], - "loc": { - "start": { - "line": 19, - "column": 33 - }, - "end": { - "line": 19, - "column": 41 - } - } - }, + "type": "Identifier", + "name": "gensym$_2", + "decorators": [], "loc": { "start": { "line": 19, @@ -907,4 +894,3 @@ } } } -TypeError: Bad operand type, the operand of the non-nullish expression must be a nullish type [array_indexing_with_chaining_non_nullish.ets:19:33] diff --git a/ets2panda/test/compiler/ets/array_indexing_with_chaining_nullish-expected.txt b/ets2panda/test/compiler/ets/array_indexing_with_chaining_nullish-expected.txt index 22716174bf0e6bf36ab6471ac23dff01b0883408..8e44cb782ba8318788d6fbc98212291128a16dcc 100644 --- a/ets2panda/test/compiler/ets/array_indexing_with_chaining_nullish-expected.txt +++ b/ets2panda/test/compiler/ets/array_indexing_with_chaining_nullish-expected.txt @@ -520,22 +520,9 @@ "alternate": { "type": "MemberExpression", "object": { - "type": "TSNonNullExpression", - "expression": { - "type": "Identifier", - "name": "gensym$_2", - "decorators": [], - "loc": { - "start": { - "line": 19, - "column": 33 - }, - "end": { - "line": 19, - "column": 41 - } - } - }, + "type": "Identifier", + "name": "gensym$_2", + "decorators": [], "loc": { "start": { "line": 19, diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable-expected.txt b/ets2panda/test/compiler/ets/ensureNotNullArgNotNullable-expected.txt similarity index 93% rename from ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable-expected.txt rename to ets2panda/test/compiler/ets/ensureNotNullArgNotNullable-expected.txt index 6c42a8caf15cda81e54b508bf0e604a8091aa584..5826f6cadf499dd75bc91bc766f77dc850e90352 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable-expected.txt +++ b/ets2panda/test/compiler/ets/ensureNotNullArgNotNullable-expected.txt @@ -19,13 +19,38 @@ } }, "typeAnnotation": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "Long", - "decorators": [], + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Long", + "decorators": [], + "loc": { + "start": { + "line": 16, + "column": 12 + }, + "end": { + "line": 16, + "column": 16 + } + } + }, + "loc": { + "start": { + "line": 16, + "column": 12 + }, + "end": { + "line": 16, + "column": 17 + } + } + }, "loc": { "start": { "line": 16, @@ -33,21 +58,24 @@ }, "end": { "line": 16, - "column": 16 + "column": 17 } } }, - "loc": { - "start": { - "line": 16, - "column": 12 - }, - "end": { - "line": 16, - "column": 17 + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 16, + "column": 17 + }, + "end": { + "line": 16, + "column": 21 + } } } - }, + ], "loc": { "start": { "line": 16, @@ -55,7 +83,7 @@ }, "end": { "line": 16, - "column": 17 + "column": 21 } } }, @@ -66,7 +94,7 @@ }, "end": { "line": 16, - "column": 17 + "column": 22 } } }, @@ -605,4 +633,3 @@ } } } -TypeError: Bad operand type, the operand of the non-nullish expression must be a nullish type [n_ensureNotNullArgNotNullable.ets:19:3] diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable.ets b/ets2panda/test/compiler/ets/ensureNotNullArgNotNullable.ets similarity index 89% rename from ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable.ets rename to ets2panda/test/compiler/ets/ensureNotNullArgNotNullable.ets index b131f4ec9ce523d95da87a74db7be39491d20c63..435db4bd36d0e88b3ccc926904209710c54764ce 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullArgNotNullable.ets +++ b/ets2panda/test/compiler/ets/ensureNotNullArgNotNullable.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -13,7 +13,7 @@ * limitations under the License. */ -type tmp = Long; +type tmp = Long|null; function foo(t : tmp) : void { t!; diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable-expected.txt b/ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable-expected.txt similarity index 87% rename from ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable-expected.txt rename to ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable-expected.txt index 4e7827bd27c1c1cbaf1a2efbe998b3b0b13b6e5c..d78f19feaa9173a4e0f9c58e3e9648e177d389f8 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable-expected.txt +++ b/ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable-expected.txt @@ -186,7 +186,35 @@ "type": "Identifier", "name": "a", "typeAnnotation": { - "type": "ETSPrimitiveType", + "type": "ETSUnionType", + "types": [ + { + "type": "ETSPrimitiveType", + "loc": { + "start": { + "line": 17, + "column": 11 + }, + "end": { + "line": 17, + "column": 14 + } + } + }, + { + "type": "ETSUndefinedType", + "loc": { + "start": { + "line": 17, + "column": 15 + }, + "end": { + "line": 17, + "column": 24 + } + } + } + ], "loc": { "start": { "line": 17, @@ -194,7 +222,7 @@ }, "end": { "line": 17, - "column": 14 + "column": 24 } } }, @@ -231,7 +259,7 @@ }, "end": { "line": 17, - "column": 15 + "column": 25 } } }, @@ -358,4 +386,3 @@ } } } -TypeError: Bad operand type, the operand of the non-nullish expression must be a nullish type [n_ensureNotNullLocalNotNullable.ets:18:3] diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable.ets b/ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable.ets similarity index 88% rename from ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable.ets rename to ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable.ets index 606a4d830c279ebca5ea347c4cd77ac0c8ac1df8..f11c843e2abad405f22f07544eb2c521516aef58 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullLocalNotNullable.ets +++ b/ets2panda/test/compiler/ets/ensureNotNullLocalNotNullable.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -14,6 +14,6 @@ */ function main() : void { - let a : int; + let a : int|undefined; a!; } diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable-expected.txt b/ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable-expected.txt similarity index 89% rename from ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable-expected.txt rename to ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable-expected.txt index 9c94e68e703ba650785836ade13089960544daf9..d61a6f3c500c25f52af83fcb9fefff34c6fcb001 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable-expected.txt +++ b/ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable-expected.txt @@ -162,13 +162,38 @@ "expression": false, "params": [], "returnType": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "Object", - "decorators": [], + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 16, + "column": 18 + }, + "end": { + "line": 16, + "column": 24 + } + } + }, + "loc": { + "start": { + "line": 16, + "column": 18 + }, + "end": { + "line": 16, + "column": 26 + } + } + }, "loc": { "start": { "line": 16, @@ -176,21 +201,24 @@ }, "end": { "line": 16, - "column": 24 + "column": 26 } } }, - "loc": { - "start": { - "line": 16, - "column": 18 - }, - "end": { - "line": 16, - "column": 26 + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 16, + "column": 27 + }, + "end": { + "line": 16, + "column": 31 + } } } - }, + ], "loc": { "start": { "line": 16, @@ -198,7 +226,7 @@ }, "end": { "line": 16, - "column": 26 + "column": 31 } } }, @@ -277,7 +305,7 @@ "loc": { "start": { "line": 16, - "column": 25 + "column": 32 }, "end": { "line": 18, @@ -519,4 +547,3 @@ } } } -TypeError: Bad operand type, the operand of the non-nullish expression must be a nullish type [n_ensureNotNullReturnNotNullable.ets:21:3] diff --git a/ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable.ets b/ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable.ets similarity index 87% rename from ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable.ets rename to ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable.ets index 5a7fec71bf6c7aa86e0fe74b7c4500669b14b615..5e17b90d689485d7da0d01f636c3e16a1679e100 100644 --- a/ets2panda/test/compiler/ets/n_ensureNotNullReturnNotNullable.ets +++ b/ets2panda/test/compiler/ets/ensureNotNullReturnNotNullable.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -13,7 +13,7 @@ * limitations under the License. */ -function foo() : Object { +function foo() : Object | null { return new Object(); } diff --git a/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg-expected.txt b/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg-expected.txt index feb9b83c82d52d0a012d68ba79dce8cacf10cffd..27adac3b4e9cbf3fb551ae8aa78e1a9f7e058d0d 100644 --- a/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg-expected.txt +++ b/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg-expected.txt @@ -367,70 +367,172 @@ "id": { "type": "Identifier", "name": "myval", - "typeAnnotation": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "Short", - "decorators": [], + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 9 + }, + "end": { + "line": 17, + "column": 14 + } + } + }, + "init": { + "type": "CallExpression", + "callee": { + "type": "ArrowFunctionExpression", + "function": { + "type": "ScriptFunction", + "id": null, + "generator": false, + "async": false, + "expression": false, + "params": [], + "returnType": { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "T", + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 21 + }, + "end": { + "line": 17, + "column": 22 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 21 + }, + "end": { + "line": 17, + "column": 23 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 21 + }, + "end": { + "line": 17, + "column": 23 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 17, + "column": 23 + }, + "end": { + "line": 17, + "column": 27 + } + } + } + ], "loc": { "start": { "line": 17, - "column": 16 + "column": 21 }, "end": { "line": 17, - "column": 21 + "column": 27 + } + } + }, + "body": { + "type": "BlockStatement", + "statements": [ + { + "type": "ReturnStatement", + "argument": { + "type": "NumberLiteral", + "value": 2, + "loc": { + "start": { + "line": 17, + "column": 40 + }, + "end": { + "line": 17, + "column": 41 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 33 + }, + "end": { + "line": 17, + "column": 42 + } + } + } + ], + "loc": { + "start": { + "line": 17, + "column": 31 + }, + "end": { + "line": 17, + "column": 44 } } }, "loc": { "start": { "line": 17, - "column": 16 + "column": 17 }, "end": { "line": 17, - "column": 23 + "column": 44 } } }, "loc": { "start": { "line": 17, - "column": 16 + "column": 17 }, "end": { "line": 17, - "column": 23 + "column": 44 } } }, - "decorators": [], - "loc": { - "start": { - "line": 17, - "column": 9 - }, - "end": { - "line": 17, - "column": 14 - } - } - }, - "init": { - "type": "NumberLiteral", - "value": 2, + "arguments": [], + "optional": false, "loc": { "start": { "line": 17, - "column": 24 + "column": 17 }, "end": { "line": 17, - "column": 25 + "column": 46 } } }, @@ -441,7 +543,7 @@ }, "end": { "line": 17, - "column": 25 + "column": 46 } } } @@ -454,7 +556,7 @@ }, "end": { "line": 17, - "column": 26 + "column": 47 } } }, @@ -597,4 +699,4 @@ } } } -TypeError: Type 'T|Short' is not compatible with the enclosing method's return type 'T' [null_coalescing_generic_1_neg.ets:18:12] +TypeError: Type 'Integral|null' is not compatible with the enclosing method's return type 'Integral' [null_coalescing_generic_1_neg.ets:18:12] diff --git a/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg.ets b/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg.ets index 557016f5f1162ee4823c37bb37967c5a7e7e2cb7..b6e2a95fe9993e6dd0542fde1d599173f1bd7c66 100644 --- a/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg.ets +++ b/ets2panda/test/compiler/ets/null_coalescing_generic_1_neg.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -14,6 +14,6 @@ */ function fos(a0: T): T { - let myval: Short = 2; + let myval = (): T|null => { return 2; }(); return a0 ?? myval; } diff --git a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticdiv-expected.txt b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticdiv-expected.txt index 633d34d67cc23e40ab37cd723902be781751fe8b..9bf72ed1564f27e3e4674abc975fbff55cc1e86d 100644 --- a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticdiv-expected.txt +++ b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticdiv-expected.txt @@ -588,4 +588,3 @@ } } } -TypeError: Bad operand type: multiple types left in the normalized union type (Double|null). Unions are not allowed in binary expressions except equality. [nullable_type_in_arithmeticdiv.ets:19:18] diff --git a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus-expected.txt b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus-expected.txt index 3113c74d7948ae765287c2c13b67942111542630..ec775125323c4ab175b602c92b030e0adbfc06a9 100644 --- a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus-expected.txt +++ b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus-expected.txt @@ -588,4 +588,3 @@ } } } -TypeError: Bad operand type: multiple types left in the normalized union type (Double|null). Unions are not allowed in binary expressions except equality. [nullable_type_in_arithmeticplus.ets:19:18] diff --git a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus_w_undefined-expected.txt b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus_w_undefined-expected.txt index 5dc4bc088bad88e9c5eaa8515607fa5d43821cc5..34adee75c986f827be2d83173e1a2c9570f20b2c 100644 --- a/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus_w_undefined-expected.txt +++ b/ets2panda/test/compiler/ets/nullable_type_in_arithmeticplus_w_undefined-expected.txt @@ -588,4 +588,4 @@ } } } -TypeError: Bad operand type: multiple types left in the normalized union type (Double|null). Unions are not allowed in binary expressions except equality. [nullable_type_in_arithmeticplus_w_undefined.ets:19:18] +TypeError: Bad operand type, the types of the operands must be numeric type or String. [nullable_type_in_arithmeticplus_w_undefined.ets:19:18] diff --git a/ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam-expected.txt b/ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam-expected.txt similarity index 93% rename from ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam-expected.txt rename to ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam-expected.txt index 5d1ead0fe1762c8e2c521804a64ad78e5939ea83..2a95ff0c1822bfae9bc0b9a7388739e88959c209 100644 --- a/ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam-expected.txt +++ b/ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam-expected.txt @@ -776,32 +776,47 @@ } }, "init": { - "type": "CallExpression", - "callee": { - "type": "MemberExpression", - "object": { - "type": "Identifier", - "name": "an", - "decorators": [], - "loc": { - "start": { - "line": 24, - "column": 22 - }, - "end": { - "line": 24, - "column": 24 + "type": "TSNonNullExpression", + "expression": { + "type": "CallExpression", + "callee": { + "type": "MemberExpression", + "object": { + "type": "Identifier", + "name": "an", + "decorators": [], + "loc": { + "start": { + "line": 24, + "column": 22 + }, + "end": { + "line": 24, + "column": 24 + } } - } - }, - "property": { - "type": "Identifier", - "name": "foo", - "decorators": [], + }, + "property": { + "type": "Identifier", + "name": "foo", + "decorators": [], + "loc": { + "start": { + "line": 24, + "column": 25 + }, + "end": { + "line": 24, + "column": 28 + } + } + }, + "computed": false, + "optional": false, "loc": { "start": { "line": 24, - "column": 25 + "column": 22 }, "end": { "line": 24, @@ -809,7 +824,7 @@ } } }, - "computed": false, + "arguments": [], "optional": false, "loc": { "start": { @@ -818,12 +833,10 @@ }, "end": { "line": 24, - "column": 28 + "column": 30 } } }, - "arguments": [], - "optional": false, "loc": { "start": { "line": 24, @@ -831,7 +844,7 @@ }, "end": { "line": 24, - "column": 30 + "column": 31 } } }, @@ -842,7 +855,7 @@ }, "end": { "line": 24, - "column": 30 + "column": 31 } } } @@ -855,7 +868,7 @@ }, "end": { "line": 24, - "column": 31 + "column": 32 } } } @@ -941,4 +954,3 @@ } } } -TypeError: Value is possibly nullish. [n_assignNullableFromMethodToNullableParam.ets:24:22] diff --git a/ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam.ets b/ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam.ets similarity index 88% rename from ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam.ets rename to ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam.ets index 3a006cfb43c3a0df46829e055c6c5047175714e1..d586956acc9ee2ecf5a465009889493eec60d985 100644 --- a/ets2panda/test/parser/ets/n_assignNullableFromMethodToNullableParam.ets +++ b/ets2panda/test/parser/ets/assignNullableFromMethodToNullableParam.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -21,5 +21,5 @@ class A { function main(): void { let an : A | null = new A(); - let x : Object = an.foo(); + let x : Object = an.foo()!; } diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullable-expected.txt b/ets2panda/test/parser/ets/assignNullableToNonNullable-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullable-expected.txt rename to ets2panda/test/parser/ets/assignNullableToNonNullable-expected.txt index 2fcd3629507766d25105e4dddc665473c669ad6d..6c5199e8a47f116861ebe546f54c4419a4d22c16 100644 --- a/ets2panda/test/parser/ets/n_assignNullableToNonNullable-expected.txt +++ b/ets2panda/test/parser/ets/assignNullableToNonNullable-expected.txt @@ -707,4 +707,3 @@ } } } -TypeError: Type 'A|null' cannot be assigned to type 'Object' [n_assignNullableToNonNullable.ets:22:9] diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullable.ets b/ets2panda/test/parser/ets/assignNullableToNonNullable.ets similarity index 92% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullable.ets rename to ets2panda/test/parser/ets/assignNullableToNonNullable.ets index e0caf0f352717a9ba841d0996a4a3f3eec2f1c03..5491a9eda80e039129705d9501d20eb8d9161e22 100644 --- a/ets2panda/test/parser/ets/n_assignNullableToNonNullable.ets +++ b/ets2panda/test/parser/ets/assignNullableToNonNullable.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullableArray-expected.txt b/ets2panda/test/parser/ets/assignNullableToNonNullableArray-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullableArray-expected.txt rename to ets2panda/test/parser/ets/assignNullableToNonNullableArray-expected.txt index 4f7aa8abd648c29db121271c411a93bc2f8841b5..4991cd521d02e80cca7b534ad3dcadbd5f29ace7 100644 --- a/ets2panda/test/parser/ets/n_assignNullableToNonNullableArray-expected.txt +++ b/ets2panda/test/parser/ets/assignNullableToNonNullableArray-expected.txt @@ -803,4 +803,3 @@ } } } -TypeError: Type 'A[]|null' cannot be assigned to type 'Object[]' [n_assignNullableToNonNullableArray.ets:22:10] diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullableArray.ets b/ets2panda/test/parser/ets/assignNullableToNonNullableArray.ets similarity index 92% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullableArray.ets rename to ets2panda/test/parser/ets/assignNullableToNonNullableArray.ets index 4dbbeff5565ff8a63b54be342e1bc858846916d3..f6f2d00d342a612fb493be172077dc51ad9fe17f 100644 --- a/ets2panda/test/parser/ets/n_assignNullableToNonNullableArray.ets +++ b/ets2panda/test/parser/ets/assignNullableToNonNullableArray.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullableTypeAlias-expected.txt b/ets2panda/test/parser/ets/assignNullableToNonNullableTypeAlias-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullableTypeAlias-expected.txt rename to ets2panda/test/parser/ets/assignNullableToNonNullableTypeAlias-expected.txt index 3764badd42b9d799e64e048630654a5757039669..500d43f57713c35fb2346286d4855cf29acc23ef 100644 --- a/ets2panda/test/parser/ets/n_assignNullableToNonNullableTypeAlias-expected.txt +++ b/ets2panda/test/parser/ets/assignNullableToNonNullableTypeAlias-expected.txt @@ -776,4 +776,3 @@ } } } -TypeError: Type 'A|null' cannot be assigned to type 'Object' [n_assignNullableToNonNullableTypeAlias.ets:24:9] diff --git a/ets2panda/test/parser/ets/n_assignNullableToNonNullableTypeAlias.ets b/ets2panda/test/parser/ets/assignNullableToNonNullableTypeAlias.ets similarity index 100% rename from ets2panda/test/parser/ets/n_assignNullableToNonNullableTypeAlias.ets rename to ets2panda/test/parser/ets/assignNullableToNonNullableTypeAlias.ets diff --git a/ets2panda/test/parser/ets/await_keyword-expected.txt b/ets2panda/test/parser/ets/await_keyword-expected.txt index c8559d4e7e65f58dc38617c507a7e7ef70cdbe84..2e37ff1d071d82df5eb70d4d8079bde13f6fe931 100644 --- a/ets2panda/test/parser/ets/await_keyword-expected.txt +++ b/ets2panda/test/parser/ets/await_keyword-expected.txt @@ -603,8 +603,235 @@ } }, "init": { - "type": "NullLiteral", - "value": null, + "type": "CallExpression", + "callee": { + "type": "ArrowFunctionExpression", + "function": { + "type": "ScriptFunction", + "id": null, + "generator": false, + "async": false, + "expression": false, + "params": [], + "returnType": { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Promise", + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 54 + }, + "end": { + "line": 17, + "column": 61 + } + } + }, + "typeParams": { + "type": "TSTypeParameterInstantiation", + "params": [ + { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 62 + }, + "end": { + "line": 17, + "column": 68 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 62 + }, + "end": { + "line": 17, + "column": 70 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 62 + }, + "end": { + "line": 17, + "column": 70 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 17, + "column": 71 + }, + "end": { + "line": 17, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 17, + "column": 62 + }, + "end": { + "line": 17, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 17, + "column": 61 + }, + "end": { + "line": 17, + "column": 76 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 54 + }, + "end": { + "line": 17, + "column": 78 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 54 + }, + "end": { + "line": 17, + "column": 78 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 17, + "column": 79 + }, + "end": { + "line": 17, + "column": 83 + } + } + } + ], + "loc": { + "start": { + "line": 17, + "column": 54 + }, + "end": { + "line": 17, + "column": 83 + } + } + }, + "body": { + "type": "BlockStatement", + "statements": [ + { + "type": "ReturnStatement", + "argument": { + "type": "NullLiteral", + "value": null, + "loc": { + "start": { + "line": 17, + "column": 96 + }, + "end": { + "line": 17, + "column": 100 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 89 + }, + "end": { + "line": 17, + "column": 101 + } + } + } + ], + "loc": { + "start": { + "line": 17, + "column": 87 + }, + "end": { + "line": 17, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 50 + }, + "end": { + "line": 17, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 50 + }, + "end": { + "line": 17, + "column": 103 + } + } + }, + "arguments": [], + "optional": false, "loc": { "start": { "line": 17, @@ -612,7 +839,7 @@ }, "end": { "line": 17, - "column": 54 + "column": 105 } } }, @@ -623,7 +850,7 @@ }, "end": { "line": 17, - "column": 54 + "column": 105 } } } @@ -636,7 +863,7 @@ }, "end": { "line": 17, - "column": 55 + "column": 106 } } }, @@ -1197,8 +1424,235 @@ } }, "init": { - "type": "NullLiteral", - "value": null, + "type": "CallExpression", + "callee": { + "type": "ArrowFunctionExpression", + "function": { + "type": "ScriptFunction", + "id": null, + "generator": false, + "async": false, + "expression": false, + "params": [], + "returnType": { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Promise", + "decorators": [], + "loc": { + "start": { + "line": 23, + "column": 54 + }, + "end": { + "line": 23, + "column": 61 + } + } + }, + "typeParams": { + "type": "TSTypeParameterInstantiation", + "params": [ + { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 23, + "column": 62 + }, + "end": { + "line": 23, + "column": 68 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 62 + }, + "end": { + "line": 23, + "column": 70 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 62 + }, + "end": { + "line": 23, + "column": 70 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 23, + "column": 71 + }, + "end": { + "line": 23, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 23, + "column": 62 + }, + "end": { + "line": 23, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 23, + "column": 61 + }, + "end": { + "line": 23, + "column": 76 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 54 + }, + "end": { + "line": 23, + "column": 78 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 54 + }, + "end": { + "line": 23, + "column": 78 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 23, + "column": 79 + }, + "end": { + "line": 23, + "column": 83 + } + } + } + ], + "loc": { + "start": { + "line": 23, + "column": 54 + }, + "end": { + "line": 23, + "column": 83 + } + } + }, + "body": { + "type": "BlockStatement", + "statements": [ + { + "type": "ReturnStatement", + "argument": { + "type": "NullLiteral", + "value": null, + "loc": { + "start": { + "line": 23, + "column": 96 + }, + "end": { + "line": 23, + "column": 100 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 89 + }, + "end": { + "line": 23, + "column": 101 + } + } + } + ], + "loc": { + "start": { + "line": 23, + "column": 87 + }, + "end": { + "line": 23, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 50 + }, + "end": { + "line": 23, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 23, + "column": 50 + }, + "end": { + "line": 23, + "column": 103 + } + } + }, + "arguments": [], + "optional": false, "loc": { "start": { "line": 23, @@ -1206,7 +1660,7 @@ }, "end": { "line": 23, - "column": 54 + "column": 105 } } }, @@ -1217,7 +1671,7 @@ }, "end": { "line": 23, - "column": 54 + "column": 105 } } } @@ -1230,7 +1684,7 @@ }, "end": { "line": 23, - "column": 55 + "column": 106 } } }, @@ -1843,8 +2297,235 @@ } }, "init": { - "type": "NullLiteral", - "value": null, + "type": "CallExpression", + "callee": { + "type": "ArrowFunctionExpression", + "function": { + "type": "ScriptFunction", + "id": null, + "generator": false, + "async": false, + "expression": false, + "params": [], + "returnType": { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Promise", + "decorators": [], + "loc": { + "start": { + "line": 29, + "column": 54 + }, + "end": { + "line": 29, + "column": 61 + } + } + }, + "typeParams": { + "type": "TSTypeParameterInstantiation", + "params": [ + { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 29, + "column": 62 + }, + "end": { + "line": 29, + "column": 68 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 62 + }, + "end": { + "line": 29, + "column": 70 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 62 + }, + "end": { + "line": 29, + "column": 70 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 29, + "column": 71 + }, + "end": { + "line": 29, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 29, + "column": 62 + }, + "end": { + "line": 29, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 29, + "column": 61 + }, + "end": { + "line": 29, + "column": 76 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 54 + }, + "end": { + "line": 29, + "column": 78 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 54 + }, + "end": { + "line": 29, + "column": 78 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 29, + "column": 79 + }, + "end": { + "line": 29, + "column": 83 + } + } + } + ], + "loc": { + "start": { + "line": 29, + "column": 54 + }, + "end": { + "line": 29, + "column": 83 + } + } + }, + "body": { + "type": "BlockStatement", + "statements": [ + { + "type": "ReturnStatement", + "argument": { + "type": "NullLiteral", + "value": null, + "loc": { + "start": { + "line": 29, + "column": 96 + }, + "end": { + "line": 29, + "column": 100 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 89 + }, + "end": { + "line": 29, + "column": 101 + } + } + } + ], + "loc": { + "start": { + "line": 29, + "column": 87 + }, + "end": { + "line": 29, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 50 + }, + "end": { + "line": 29, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 29, + "column": 50 + }, + "end": { + "line": 29, + "column": 103 + } + } + }, + "arguments": [], + "optional": false, "loc": { "start": { "line": 29, @@ -1852,7 +2533,7 @@ }, "end": { "line": 29, - "column": 54 + "column": 105 } } }, @@ -1863,7 +2544,7 @@ }, "end": { "line": 29, - "column": 54 + "column": 105 } } } @@ -1876,7 +2557,7 @@ }, "end": { "line": 29, - "column": 55 + "column": 106 } } }, @@ -2297,8 +2978,235 @@ } }, "init": { - "type": "NullLiteral", - "value": null, + "type": "CallExpression", + "callee": { + "type": "ArrowFunctionExpression", + "function": { + "type": "ScriptFunction", + "id": null, + "generator": false, + "async": false, + "expression": false, + "params": [], + "returnType": { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Promise", + "decorators": [], + "loc": { + "start": { + "line": 34, + "column": 54 + }, + "end": { + "line": 34, + "column": 61 + } + } + }, + "typeParams": { + "type": "TSTypeParameterInstantiation", + "params": [ + { + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 34, + "column": 62 + }, + "end": { + "line": 34, + "column": 68 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 62 + }, + "end": { + "line": 34, + "column": 70 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 62 + }, + "end": { + "line": 34, + "column": 70 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 34, + "column": 71 + }, + "end": { + "line": 34, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 34, + "column": 62 + }, + "end": { + "line": 34, + "column": 75 + } + } + } + ], + "loc": { + "start": { + "line": 34, + "column": 61 + }, + "end": { + "line": 34, + "column": 76 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 54 + }, + "end": { + "line": 34, + "column": 78 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 54 + }, + "end": { + "line": 34, + "column": 78 + } + } + }, + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 34, + "column": 79 + }, + "end": { + "line": 34, + "column": 83 + } + } + } + ], + "loc": { + "start": { + "line": 34, + "column": 54 + }, + "end": { + "line": 34, + "column": 83 + } + } + }, + "body": { + "type": "BlockStatement", + "statements": [ + { + "type": "ReturnStatement", + "argument": { + "type": "NullLiteral", + "value": null, + "loc": { + "start": { + "line": 34, + "column": 96 + }, + "end": { + "line": 34, + "column": 100 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 89 + }, + "end": { + "line": 34, + "column": 101 + } + } + } + ], + "loc": { + "start": { + "line": 34, + "column": 87 + }, + "end": { + "line": 34, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 50 + }, + "end": { + "line": 34, + "column": 103 + } + } + }, + "loc": { + "start": { + "line": 34, + "column": 50 + }, + "end": { + "line": 34, + "column": 103 + } + } + }, + "arguments": [], + "optional": false, "loc": { "start": { "line": 34, @@ -2306,7 +3214,7 @@ }, "end": { "line": 34, - "column": 54 + "column": 105 } } }, @@ -2317,7 +3225,7 @@ }, "end": { "line": 34, - "column": 54 + "column": 105 } } } @@ -2330,7 +3238,7 @@ }, "end": { "line": 34, - "column": 55 + "column": 106 } } }, diff --git a/ets2panda/test/parser/ets/await_keyword.ets b/ets2panda/test/parser/ets/await_keyword.ets index e6b9b973138e6b6a31b1e7062db3d10d059a9753..5da8c039ce444b3af27a8c460c73c78ecdc99242 100644 --- a/ets2panda/test/parser/ets/await_keyword.ets +++ b/ets2panda/test/parser/ets/await_keyword.ets @@ -14,24 +14,24 @@ */ async function asyncFoo(): Promise { - let promise: Promise | null = null; + let promise: Promise | null = (): Promise | null => { return null; }(); let obj: Object | null = await promise!; return promise; } let asyncLambda: () => Promise = async (): Promise => { - let promise: Promise | null = null; + let promise: Promise | null = (): Promise | null => { return null; }(); let obj: Object | null = await promise!; return promise; } function foo(): void { - let promise: Promise | null = null; + let promise: Promise | null = (): Promise | null => { return null; }(); let obj: Object | null = await promise!; } let lambda: () => void = (): void => { - let promise: Promise | null = null; + let promise: Promise | null = (): Promise | null => { return null; }(); let obj: Object | null = await promise!; } diff --git a/ets2panda/test/parser/ets/n_callFunctionWithNullableParam-expected.txt b/ets2panda/test/parser/ets/callFunctionWithNullableParam-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_callFunctionWithNullableParam-expected.txt rename to ets2panda/test/parser/ets/callFunctionWithNullableParam-expected.txt index 694a48ec19be82e96fd2d287b5e6a68cc1203c34..6a59b3c3a86aa50dce6855e52433eef5fa42b33f 100644 --- a/ets2panda/test/parser/ets/n_callFunctionWithNullableParam-expected.txt +++ b/ets2panda/test/parser/ets/callFunctionWithNullableParam-expected.txt @@ -800,4 +800,3 @@ } } } -TypeError: Type 'A|null' is not compatible with type 'A' at index 1 [n_callFunctionWithNullableParam.ets:23:9] diff --git a/ets2panda/test/parser/ets/n_callFunctionWithNullableParam.ets b/ets2panda/test/parser/ets/callFunctionWithNullableParam.ets similarity index 92% rename from ets2panda/test/parser/ets/n_callFunctionWithNullableParam.ets rename to ets2panda/test/parser/ets/callFunctionWithNullableParam.ets index abc2725fb877c6c410b07257424c0c8c03bc3613..62508403f0fd3338d7a3239abfd86b11418f623b 100644 --- a/ets2panda/test/parser/ets/n_callFunctionWithNullableParam.ets +++ b/ets2panda/test/parser/ets/callFunctionWithNullableParam.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 diff --git a/ets2panda/test/parser/ets/n_callInterfaceMethodWithNullableParam-expected.txt b/ets2panda/test/parser/ets/callInterfaceMethodWithNullableParam-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_callInterfaceMethodWithNullableParam-expected.txt rename to ets2panda/test/parser/ets/callInterfaceMethodWithNullableParam-expected.txt index aac8b849d7ff26940d00963f1eb886ef12c723f3..e4a3fa62ef719bd0c96a993df3d2746d0ea2ab31 100644 --- a/ets2panda/test/parser/ets/n_callInterfaceMethodWithNullableParam-expected.txt +++ b/ets2panda/test/parser/ets/callInterfaceMethodWithNullableParam-expected.txt @@ -1133,4 +1133,3 @@ } } } -TypeError: Type 'I|null' is not compatible with type 'I' at index 1 [n_callInterfaceMethodWithNullableParam.ets:27:17] diff --git a/ets2panda/test/parser/ets/n_callInterfaceMethodWithNullableParam.ets b/ets2panda/test/parser/ets/callInterfaceMethodWithNullableParam.ets similarity index 100% rename from ets2panda/test/parser/ets/n_callInterfaceMethodWithNullableParam.ets rename to ets2panda/test/parser/ets/callInterfaceMethodWithNullableParam.ets diff --git a/ets2panda/test/parser/ets/n_callMethodWithNullableParam-expected.txt b/ets2panda/test/parser/ets/callMethodWithNullableParam-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_callMethodWithNullableParam-expected.txt rename to ets2panda/test/parser/ets/callMethodWithNullableParam-expected.txt index 833d0450d035fd13829edb5b92cc763b0b35dcad..31ec76213c21cf0ad0b082dd1b0d6966ffc06e20 100644 --- a/ets2panda/test/parser/ets/n_callMethodWithNullableParam-expected.txt +++ b/ets2panda/test/parser/ets/callMethodWithNullableParam-expected.txt @@ -870,4 +870,3 @@ } } } -TypeError: Type 'A|null' is not compatible with type 'A' at index 1 [n_callMethodWithNullableParam.ets:23:17] diff --git a/ets2panda/test/parser/ets/n_callMethodWithNullableParam.ets b/ets2panda/test/parser/ets/callMethodWithNullableParam.ets similarity index 100% rename from ets2panda/test/parser/ets/n_callMethodWithNullableParam.ets rename to ets2panda/test/parser/ets/callMethodWithNullableParam.ets diff --git a/ets2panda/test/parser/ets/generic_function-expected.txt b/ets2panda/test/parser/ets/generic_function-expected.txt index d2bf999932ab34ce3b4324afe17055b6bf70db50..51bffc58e1591c18902c7e6559ae276d2b94e953 100644 --- a/ets2panda/test/parser/ets/generic_function-expected.txt +++ b/ets2panda/test/parser/ets/generic_function-expected.txt @@ -57,13 +57,38 @@ "type": "Identifier", "name": "other", "typeAnnotation": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "Object", - "decorators": [], + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "Object", + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 24 + }, + "end": { + "line": 17, + "column": 30 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 24 + }, + "end": { + "line": 17, + "column": 31 + } + } + }, "loc": { "start": { "line": 17, @@ -71,21 +96,24 @@ }, "end": { "line": 17, - "column": 30 + "column": 31 } } }, - "loc": { - "start": { - "line": 17, - "column": 24 - }, - "end": { - "line": 17, - "column": 31 + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 17, + "column": 31 + }, + "end": { + "line": 17, + "column": 35 + } } } - }, + ], "loc": { "start": { "line": 17, @@ -93,7 +121,7 @@ }, "end": { "line": 17, - "column": 31 + "column": 35 } } }, @@ -105,7 +133,7 @@ }, "end": { "line": 17, - "column": 31 + "column": 35 } } }, @@ -116,7 +144,7 @@ }, "end": { "line": 17, - "column": 31 + "column": 35 } } } @@ -126,11 +154,11 @@ "loc": { "start": { "line": 17, - "column": 33 + "column": 38 }, "end": { "line": 17, - "column": 36 + "column": 41 } } }, @@ -142,7 +170,7 @@ }, "end": { "line": 17, - "column": 36 + "column": 41 } } }, @@ -153,7 +181,7 @@ }, "end": { "line": 17, - "column": 36 + "column": 41 } } }, @@ -166,7 +194,7 @@ }, "end": { "line": 17, - "column": 37 + "column": 42 } } } @@ -375,13 +403,38 @@ "type": "Identifier", "name": "x", "typeAnnotation": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "T", - "decorators": [], + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "T", + "decorators": [], + "loc": { + "start": { + "line": 20, + "column": 44 + }, + "end": { + "line": 20, + "column": 45 + } + } + }, + "loc": { + "start": { + "line": 20, + "column": 44 + }, + "end": { + "line": 20, + "column": 46 + } + } + }, "loc": { "start": { "line": 20, @@ -389,21 +442,24 @@ }, "end": { "line": 20, - "column": 45 + "column": 46 } } }, - "loc": { - "start": { - "line": 20, - "column": 44 - }, - "end": { - "line": 20, - "column": 46 + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 20, + "column": 46 + }, + "end": { + "line": 20, + "column": 50 + } } } - }, + ], "loc": { "start": { "line": 20, @@ -411,7 +467,7 @@ }, "end": { "line": 20, - "column": 46 + "column": 50 } } }, @@ -423,7 +479,7 @@ }, "end": { "line": 20, - "column": 46 + "column": 50 } } }, @@ -434,7 +490,7 @@ }, "end": { "line": 20, - "column": 46 + "column": 50 } } }, @@ -444,43 +500,71 @@ "type": "Identifier", "name": "y", "typeAnnotation": { - "type": "ETSTypeReference", - "part": { - "type": "ETSTypeReferencePart", - "name": { - "type": "Identifier", - "name": "T", - "decorators": [], + "type": "ETSUnionType", + "types": [ + { + "type": "ETSTypeReference", + "part": { + "type": "ETSTypeReferencePart", + "name": { + "type": "Identifier", + "name": "T", + "decorators": [], + "loc": { + "start": { + "line": 20, + "column": 55 + }, + "end": { + "line": 20, + "column": 56 + } + } + }, + "loc": { + "start": { + "line": 20, + "column": 55 + }, + "end": { + "line": 20, + "column": 57 + } + } + }, "loc": { "start": { "line": 20, - "column": 50 + "column": 55 }, "end": { "line": 20, - "column": 51 + "column": 57 } } }, - "loc": { - "start": { - "line": 20, - "column": 50 - }, - "end": { - "line": 20, - "column": 52 + { + "type": "ETSNullType", + "loc": { + "start": { + "line": 20, + "column": 57 + }, + "end": { + "line": 20, + "column": 61 + } } } - }, + ], "loc": { "start": { "line": 20, - "column": 50 + "column": 55 }, "end": { "line": 20, - "column": 52 + "column": 61 } } }, @@ -488,22 +572,22 @@ "loc": { "start": { "line": 20, - "column": 47 + "column": 52 }, "end": { "line": 20, - "column": 52 + "column": 61 } } }, "loc": { "start": { "line": 20, - "column": 47 + "column": 52 }, "end": { "line": 20, - "column": 52 + "column": 61 } } } @@ -513,11 +597,11 @@ "loc": { "start": { "line": 20, - "column": 54 + "column": 64 }, "end": { "line": 20, - "column": 57 + "column": 67 } } }, @@ -954,7 +1038,7 @@ "loc": { "start": { "line": 20, - "column": 58 + "column": 68 }, "end": { "line": 24, diff --git a/ets2panda/test/parser/ets/generic_function.ets b/ets2panda/test/parser/ets/generic_function.ets index 3f7aebabb1cf3eb7510f44001b193fe9f935d003..d6844f4e03cfe5155f68d7374d4423a96a431e78 100644 --- a/ets2panda/test/parser/ets/generic_function.ets +++ b/ets2panda/test/parser/ets/generic_function.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022 - 2024 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 @@ -14,10 +14,10 @@ */ interface Comparable2 { - localeCompare(other: Object): int; + localeCompare(other: Object|null): int; } -function compare(x: T, y: T): int { +function compare(x: T|null, y: T|null): int { if (x == null) return y == null ? 0 : -1; if (y == null) return 1; return x.localeCompare(y); diff --git a/ets2panda/test/parser/ets/interface_static_function_2-expected.txt b/ets2panda/test/parser/ets/interface_static_function_2-expected.txt index e344174ceb1b2583d4935a45a5f8a0e5cbc5dcc6..10b5f003e5d74ba2d5bab0c557319450721f60b0 100644 --- a/ets2panda/test/parser/ets/interface_static_function_2-expected.txt +++ b/ets2panda/test/parser/ets/interface_static_function_2-expected.txt @@ -1115,4 +1115,4 @@ } } } -TypeError: 'xyz' is a static property of 'I' [interface_static_function_2.ets:34:7] +TypeError: 'xyz' is a static property of 'C' [interface_static_function_2.ets:34:7] diff --git a/ets2panda/test/parser/ets/n_returnNullableFromFunction-expected.txt b/ets2panda/test/parser/ets/returnNullableFromFunction-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_returnNullableFromFunction-expected.txt rename to ets2panda/test/parser/ets/returnNullableFromFunction-expected.txt index 06b59c0b1592e9f1bc1fa55a36b8fa81e783bef0..5ce6d7762921f0f37e6e393e87333787571a873e 100644 --- a/ets2panda/test/parser/ets/n_returnNullableFromFunction-expected.txt +++ b/ets2panda/test/parser/ets/returnNullableFromFunction-expected.txt @@ -620,4 +620,3 @@ } } } -TypeError: Type 'A|null' is not compatible with the enclosing method's return type 'A' [n_returnNullableFromFunction.ets:21:12] diff --git a/ets2panda/test/parser/ets/n_returnNullableFromFunction.ets b/ets2panda/test/parser/ets/returnNullableFromFunction.ets similarity index 92% rename from ets2panda/test/parser/ets/n_returnNullableFromFunction.ets rename to ets2panda/test/parser/ets/returnNullableFromFunction.ets index a372f06016305f4de869c32512d03ecf1b51d33b..685510d0ce8c1b5b1d339d2ce2be949c2c549075 100644 --- a/ets2panda/test/parser/ets/n_returnNullableFromFunction.ets +++ b/ets2panda/test/parser/ets/returnNullableFromFunction.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 diff --git a/ets2panda/test/parser/ets/n_returnNullableFromMethod-expected.txt b/ets2panda/test/parser/ets/returnNullableFromMethod-expected.txt similarity index 99% rename from ets2panda/test/parser/ets/n_returnNullableFromMethod-expected.txt rename to ets2panda/test/parser/ets/returnNullableFromMethod-expected.txt index bde21ce9dd5924dcbe74e8544242537f4c995100..4d294d63adaf059fa828cac187e3f690616dc265 100644 --- a/ets2panda/test/parser/ets/n_returnNullableFromMethod-expected.txt +++ b/ets2panda/test/parser/ets/returnNullableFromMethod-expected.txt @@ -620,4 +620,3 @@ } } } -TypeError: Type 'A|null' is not compatible with the enclosing method's return type 'A' [n_returnNullableFromMethod.ets:20:16] diff --git a/ets2panda/test/parser/ets/n_returnNullableFromMethod.ets b/ets2panda/test/parser/ets/returnNullableFromMethod.ets similarity index 92% rename from ets2panda/test/parser/ets/n_returnNullableFromMethod.ets rename to ets2panda/test/parser/ets/returnNullableFromMethod.ets index e6dafb1313db0089bd81da61a592d1f9ea04e270..0f764580d92b216014f88cf3e8494aa5d15efc9d 100644 --- a/ets2panda/test/parser/ets/n_returnNullableFromMethod.ets +++ b/ets2panda/test/parser/ets/returnNullableFromMethod.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 diff --git a/ets2panda/test/runtime/ets/ClassMemberAccess.ets b/ets2panda/test/runtime/ets/ClassMemberAccess.ets index 79cd0a1071ce9fe9d74fcc066ea991a7b11878f8..7fc3b37205c1c176ae4199b0e6c4bcf89251a180 100644 --- a/ets2panda/test/runtime/ets/ClassMemberAccess.ets +++ b/ets2panda/test/runtime/ets/ClassMemberAccess.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -86,7 +86,7 @@ function main() : void { { let b_as_a: A = new B(); - assert b_as_a.name == c'A'; + assert b_as_a.name == c'B'; assert b_as_a.get_name() == c'B'; assert b_as_a.get_name_a() == c'A'; } @@ -103,14 +103,14 @@ function main() : void { { let c_as_a: A = new C(); - assert c_as_a.name == c'A'; + assert c_as_a.name == c'C'; assert c_as_a.get_name() == c'C'; assert c_as_a.get_name_a() == c'A'; } { let c_as_b: B = new C(); - assert c_as_b.name == c'B'; + assert c_as_b.name == c'C'; assert c_as_b.get_name() == c'C'; assert c_as_b.super_name() == c'B'; assert c_as_b.get_name_a() == c'A'; diff --git a/ets2panda/test/runtime/ets/DefaultParam_2.ets b/ets2panda/test/runtime/ets/DefaultParam_2.ets index c82f36b460aab51764a5f0cb312c113e8ea06503..3df17dc6957da78497cedc5aff3375a83d424e80 100644 --- a/ets2panda/test/runtime/ets/DefaultParam_2.ets +++ b/ets2panda/test/runtime/ets/DefaultParam_2.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -86,7 +86,7 @@ function foo9(a? : Int, b? : Int) : Int { if(b == undefined){ return -1; } - return a! + b!; + return a! + b; } function foo10(a? : Int, b? : Int, c? : Int) : Int { @@ -98,14 +98,14 @@ function foo10(a? : Int, b? : Int, c? : Int) : Int { return -1; } - return a! + b! + c!; + return a! + b! + c; } function foo11(a : Int = 5, b? : Int) : Int { if(b == undefined){ return 0; } - return a + b!; + return a + b; } function foo12(a? : Int, b : Int = 5, c? : Int) : Int { diff --git a/ets2panda/test/runtime/ets/DefaultParam_3.ets b/ets2panda/test/runtime/ets/DefaultParam_3.ets index 51f74ab3dc2faf11e82e480ef343dbdf6e8c6d7c..f1892db1ef187ea2bbc31549970f0bcba7e54e64 100644 --- a/ets2panda/test/runtime/ets/DefaultParam_3.ets +++ b/ets2panda/test/runtime/ets/DefaultParam_3.ets @@ -48,7 +48,7 @@ function main(): void { assert foo12(new MyType(10), new MyType(5)) == 30; } -function foo1(a: MyType = new MyType(8)): int { +function foo1(a: MyType|null = new MyType(8)): int { if (a == null) { return -1; } @@ -69,7 +69,7 @@ function foo4(a?: MyType): int { if (a == null) { return 0; } - return a!.x; + return a.x; } function foo5(a?: MyType, b?: MyType): int { @@ -79,7 +79,7 @@ function foo5(a?: MyType, b?: MyType): int { if (b == null) { return -1; } - return a!.x + b!.x; + return a!.x + b.x; } function foo6(a?: MyType, b?: MyType, c?: MyType): int { @@ -91,14 +91,14 @@ function foo6(a?: MyType, b?: MyType, c?: MyType): int { return -1; } - return a!.x + b!.x + c!.x; + return a!.x + b!.x + c.x; } function foo7(a: MyType = new MyType(5), b?: MyType): int { if (b == null) { return 0; } - return a.x + b!.x; + return a.x + b.x; } function foo8(a?: MyType, b: MyType = new MyType(5), c?: MyType): int { diff --git a/ets2panda/test/runtime/ets/DefaultParam_4.ets b/ets2panda/test/runtime/ets/DefaultParam_4.ets index 38a37046f5d2a4536f7e35464002c74a2b7cfac5..aa72510c63f91740869429c8a33856e9bf9ba1c8 100644 --- a/ets2panda/test/runtime/ets/DefaultParam_4.ets +++ b/ets2panda/test/runtime/ets/DefaultParam_4.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -89,7 +89,7 @@ class bar { if(b == undefined){ return -1; } - return a! + b!; + return a! + b; } foo10(a? : Int, b? : Int, c? : Int) : Int { @@ -101,14 +101,14 @@ class bar { return -1; } - return a! + b! + c!; + return a! + b! + c; } foo11(a : Int = 5, b? : Int) : Int { if(b == undefined){ return 0; } - return a + b!; + return a + b; } foo12(a? : Int, b : Int = 5, c? : Int) : Int { diff --git a/ets2panda/test/runtime/ets/SmartCast_01.ets b/ets2panda/test/runtime/ets/SmartCast_01.ets new file mode 100644 index 0000000000000000000000000000000000000000..596cdc9b448b84c2095ac43b6d37c9b806322b1a --- /dev/null +++ b/ets2panda/test/runtime/ets/SmartCast_01.ets @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 - 2024 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 C { + bar(): string { + return "Class C"; + } +} + +function foo(c: Object|null|undefined): string { + if (c instanceof string) { + assert(c.length == 11); + c = "Case 1"; + } else if (c instanceof C) { + assert(c.bar() == "Class C") + c = "Case 2"; + } else if (c instanceof Int) { + assert(c * 7 == 49); + c = "Case 3"; + } else if (c instanceof null) { + assert(c == null); + c = "Case 4"; + } else { + c = "Case 5"; + } + + assert(c.length == 6); + return c; +} + + +function main(): void { + assert(foo("Test string") == "Case 1"); + assert(foo(new Int(7)) == "Case 3"); + assert(foo(new C()) == "Case 2"); + assert(foo(null) == "Case 4"); + assert(foo(undefined) == "Case 5"); + assert(foo(new Number(3.0)) == "Case 5"); +} diff --git a/ets2panda/test/runtime/ets/SmartCast_02.ets b/ets2panda/test/runtime/ets/SmartCast_02.ets new file mode 100644 index 0000000000000000000000000000000000000000..331bdd3a56da073b4519d894c217810146715806 --- /dev/null +++ b/ets2panda/test/runtime/ets/SmartCast_02.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 - 2024 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 A { + bar(): string { + return "Class A"; + } +} + +class B extends A {} + +class C extends B { + bar(): string { + return "Class C"; + } +} + +function foo(c: Int|String|A|null|undefined): void { + if (c instanceof String) { + assert(c.length == 11); + } else if (c instanceof C) { + assert(c.bar() == "Class C"); + } else if (c instanceof Int) { + assert(c * c = 49); + } else if (c === undefined) { + assert(c == undefined); + } else { + assert(c == null); + } +} + +function main(): void { + foo("Test string"); + foo(new Int(7)); + foo(new C()); + foo(null); + foo(undefined); +} diff --git a/ets2panda/test/runtime/ets/SmartCast_03.ets b/ets2panda/test/runtime/ets/SmartCast_03.ets new file mode 100644 index 0000000000000000000000000000000000000000..d43ad97349320f3232d1bc266895f45d8d1c5b6a --- /dev/null +++ b/ets2panda/test/runtime/ets/SmartCast_03.ets @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 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 C { + constructor() {} + + constructor(a: int) { + this.x = a; + } + + bar(): string { + return "Class C"; + } + + baz(): int { + return this.x; + } + + private x: int = 7; +} + +function foo(c: Object|null|undefined): string { + if (c instanceof string && (c.length == 11 || c == "Test")) { + c = "Case 1"; + } else if (c instanceof C && c.baz() == 7) { + assert(c.bar() == "Class C") + c = "Case 2"; + } else if (c instanceof Int && c >= 0) { + assert(c >= 0); + c = "Case 3"; + } else if (c instanceof null) { + assert(c == null); + c = "Case 4"; + } else { + c = "Case 5"; + } + + assert(c.length == 6); + return c; +} + +function main(): void { + assert(foo("Test string") == "Case 1"); + assert(foo("Test") == "Case 1"); + assert(foo("Test string 2") == "Case 5"); + assert(foo("test") == "Case 5"); + + assert(foo(new Int(5)) == "Case 3"); + assert(foo(new Int(0)) == "Case 3"); + assert(foo(new Int(-5)) == "Case 5"); + + assert(foo(new C(7)) == "Case 2"); + assert(foo(new C()) == "Case 2"); + assert(foo(new C(17)) == "Case 5"); + + assert(foo(null) == "Case 4"); + + assert(foo(undefined) == "Case 5"); + assert(foo(new Number(3.0)) == "Case 5"); +} diff --git a/ets2panda/test/runtime/ets/SmartCast_04.ets b/ets2panda/test/runtime/ets/SmartCast_04.ets new file mode 100644 index 0000000000000000000000000000000000000000..dfae48ef9b330fbf12741e2127fc1781a78160d5 --- /dev/null +++ b/ets2panda/test/runtime/ets/SmartCast_04.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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 fooAnd(x: String|null, y: String|null): string { + if (x != null && y != null) { + return x + " " + y; + } else if (x == null && y == null) { + return "null"; + } else if (x != null && y == null) { + return x; + } else if (x == null && y != null) { + return y; + } else { + throw new Error("Unreachable"); + } +} + +function fooOr1(x: String|null, y: String|null): string { + if (x != null || y != null) { + return "case 1"; + } else if (x == null && y == null) { + return "null"; + } else { + throw new Error("Unreachable"); + } +} + +function fooOr2(x: String|null, y: String|null): string { + if (x == null || y == null) { + return "case 1"; + } else if (x != null && y != null) { + return x + " " + y; + } else { + throw new Error("Unreachable"); + } +} + +function main(): void { + assert(fooAnd("Test", "string") == "Test string"); + assert(fooAnd("Test", null) == "Test"); + assert(fooAnd(null, "string") == "string"); + assert(fooAnd(null, null) == "null"); + + assert(fooOr1("Test", "string") == "case 1"); + assert(fooOr1("Test", null) == "case 1"); + assert(fooOr1(null, "string") == "case 1"); + assert(fooOr1(null, null) == "null"); + + assert(fooOr2("Test", "string") == "Test string"); + assert(fooOr2("Test", null) == "case 1"); + assert(fooOr2(null, "string") == "case 1"); + assert(fooOr2(null, null) == "case 1"); +} diff --git a/ets2panda/test/runtime/ets/SmartCast_05.ets b/ets2panda/test/runtime/ets/SmartCast_05.ets new file mode 100644 index 0000000000000000000000000000000000000000..db535d2a9cc3a7b4bddfb37c00fc3663c3396e82 --- /dev/null +++ b/ets2panda/test/runtime/ets/SmartCast_05.ets @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 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 C { + readonly a: boolean + constructor(a_: boolean = false) { + this.a = a_; + } +} + +function foo1(x: C|null|undefined): string { + if (x == null || !x.a) { + return x != null ? "false1" : "null"; + } else { + return x.a ? "true2" : "false2"; + } +} + +function foo2(x: C|null|undefined): string { + if (x != null && x.a) { + return "true"; + } else { + return x != null ? "false" : "null"; + } +} + +function bar(x: C|null|undefined, y: boolean, z: boolean): string { + if ((x instanceof C && y) || (x instanceof C && z)) { + return (x.a ? "true1" : "false1") + y + z; + } else { + return (x != null ? (x.a ? "true2" : "false2") : "null") + y + z; + } +} + +function main(): void { + assert(foo1(null) == "null"); + assert(foo2(null) == "null"); + assert(bar(null, true, true) == "nulltruetrue"); + assert(bar(null, true, false) == "nulltruefalse"); + assert(bar(null, false, true) == "nullfalsetrue"); + assert(bar(null, false, false) == "nullfalsefalse"); + + assert(foo1(undefined) == "null"); + assert(foo2(undefined) == "null"); + assert(bar(undefined, true, true) == "nulltruetrue"); + assert(bar(undefined, true, false) == "nulltruefalse"); + assert(bar(undefined, false, true) == "nullfalsetrue"); + assert(bar(undefined, false, false) == "nullfalsefalse"); + + let c = new C(); + assert(foo1(c) == "false1"); + assert(foo2(c) == "false"); + assert(bar(c, true, true) == "false1truetrue"); + assert(bar(c, true, false) == "false1truefalse"); + assert(bar(c, false, true) == "false1falsetrue"); + assert(bar(c, false, false) == "false2falsefalse"); + + c = new C(true); + assert(foo1(c) == "true2"); + assert(foo2(c) == "true"); + assert(bar(c, true, true) == "true1truetrue"); + assert(bar(c, true, false) == "true1truefalse"); + assert(bar(c, false, true) == "true1falsetrue"); + assert(bar(c, false, false) == "true2falsefalse"); +} diff --git a/ets2panda/test/runtime/ets/conditionalExpression2.ets b/ets2panda/test/runtime/ets/conditionalExpression2.ets index 1680ccd5baa973efb13d24f1ccc7b3125c4f5777..a5752efeeb7a8db52bd5fa827efc2989d7f149ed 100644 --- a/ets2panda/test/runtime/ets/conditionalExpression2.ets +++ b/ets2panda/test/runtime/ets/conditionalExpression2.ets @@ -14,7 +14,7 @@ */ function foo(form?: String) { - const f = (form == undefined) ? "abc" : form! + const f = (form == undefined) ? "abc" : form switch (f) { case "abc": return 41 diff --git a/ets2panda/test/runtime/ets/member-expression-nullptr-via-function-param.ets b/ets2panda/test/runtime/ets/member-expression-nullptr-via-function-param.ets index af51cc1ba6df8d36dc28630547972d34edcb8c12..3eff3e6fe89e1df4b11d85cacc317f2885fcec55 100644 --- a/ets2panda/test/runtime/ets/member-expression-nullptr-via-function-param.ets +++ b/ets2panda/test/runtime/ets/member-expression-nullptr-via-function-param.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -26,6 +26,6 @@ function hasResidence(person: Person | null) : boolean { } function main(): void { - let john: Person | null = null; + let john = null; assert(hasResidence(john) == false); } diff --git a/ets2panda/test/runtime/ets/member-expression-nullptr.ets b/ets2panda/test/runtime/ets/member-expression-nullptr.ets new file mode 100644 index 0000000000000000000000000000000000000000..b068ab74486e71d8b5d3ec9b6f68bab9fbeeeb07 --- /dev/null +++ b/ets2panda/test/runtime/ets/member-expression-nullptr.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 - 2024 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 = 0; + +function foo(): int { + a++; + return 1; +} + +function bar(): int { + a++; + return 2; +} + +class Residence { + numberOfRooms: int = 1; +} + +class Person { + residence: Residence = new Residence(); +} + +function aux(): Person | null { + return null; +} + +function main(): void { + a = 0; + let test = false; + let john: Person | null = aux(); + + try { + let residence = (john as Person).residence; + } catch (e: ClassCastException) { + test = true; + } + assert(test == true); + + test = false; + assert(test == false); + + try { + let numbers: int = john!.residence.numberOfRooms; + } catch (e: NullPointerException) { + test = true; + } + assert(test == true); + + test = false; + assert(test == false); + try { + let numbers: int = foo() + bar() + john!.residence.numberOfRooms; + } catch (e: NullPointerException) { + test = true; + } + assert(test == true); + assert(a == 2); // foo and bar were evaluated + + john = new Person(); + + let numbers: int = john.residence.numberOfRooms; + assert(numbers == 1); + + numbers = foo() + bar() + john.residence.numberOfRooms; + assert(numbers == 4); +} diff --git a/ets2panda/test/runtime/ets/notNull.ets b/ets2panda/test/runtime/ets/notNull.ets index 4abdfe0b621dc72b928f852131326ac9e982c3e8..dff4c16ab414a1b2aa34f5f94b157fa923598b27 100644 --- a/ets2panda/test/runtime/ets/notNull.ets +++ b/ets2panda/test/runtime/ets/notNull.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -30,9 +30,12 @@ function typeOf(o : Object) : int { return -1; } +function dummy(): Int | null { + return 5; +} + function testLocalIdentifier() : void { - let i : Int = 5 - let a : Int | null = i; + let a : Int | null = dummy(); let b = a!; assert typeOf(b) == 1: "b must be type Int"; @@ -87,12 +90,16 @@ function baz() : Byte | null { return null; } +function aux(): Object | null { + return null; +} + function testNPE() : void { - let o : Object | null = null; + let o : Object | null = aux(); let npe_caught = false; try { - o!; + (o as Object | null)!; assert false : "this must not be executed"; } catch (ex: NullPointerException) { npe_caught = true; diff --git a/ets2panda/test/runtime/ets/nullishTypeCodesamples.ets b/ets2panda/test/runtime/ets/nullishTypeCodesamples.ets index f056ee2e984ba7d1b5fd00f151925e0bdc77b15e..31858b0f88be2e6b23078e1a88a625c98677a720 100644 --- a/ets2panda/test/runtime/ets/nullishTypeCodesamples.ets +++ b/ets2panda/test/runtime/ets/nullishTypeCodesamples.ets @@ -59,8 +59,14 @@ class DTree { class B { public b: boolean = true; }; + + +function dummy(): B | undefined { + return new B(); +} + function testb(): void { - let b: B | undefined = new B(); + let b: B | undefined = dummy(); if (b?.b == false) { b!.b = true; } @@ -71,14 +77,19 @@ class Test { } type int32 = int; + +function dummy1(): int32 | null { + return null; +} + function testf(a: int32 | undefined): int32 { - let x: int32 | null = null; + let x: int32 | null = dummy1(); return a! + x!; } function testcond() { let _a: null | undefined = null, options_x: null | undefined = null; - return (_a = options_x) !== null && _a !== undefined ? _a : 1; + return (_a = options_x) !== null && _a != undefined ? _a : 1; } type float32 = float; @@ -116,4 +127,4 @@ function main() { foo(123); foo(undefined); new Matrix33().rotate(1, 2, 3) -} \ No newline at end of file +} diff --git a/ets2panda/test/runtime/ets/opt-chaining.ets b/ets2panda/test/runtime/ets/opt-chaining.ets index 0fbfc67a30c8b4d4ed6f26e1e2ff7979f82cfe98..aa2dcf8d0d2879659a83add57dc31fc2309d42d0 100644 --- a/ets2panda/test/runtime/ets/opt-chaining.ets +++ b/ets2panda/test/runtime/ets/opt-chaining.ets @@ -21,8 +21,12 @@ class Person { residence: Residence = new Residence(); } +function dummy(): Person | null { + return null; +} + function main(): void { - let john: Person | null = null; + let john: Person | null = dummy(); let test = john?.residence?.numberOfRooms; assert (test == null); let nullsafety_test = john?.residence?.numberOfRooms ?? "unknown"; diff --git a/ets2panda/test/runtime/ets/optional-chaining-function-call.ets b/ets2panda/test/runtime/ets/optional-chaining-function-call.ets index ed0f5cd685fd8b1f2659b9ea96752181b063be10..ec9cee9a9ec5239b7dea650c05f65b28a96e4f2f 100644 --- a/ets2panda/test/runtime/ets/optional-chaining-function-call.ets +++ b/ets2panda/test/runtime/ets/optional-chaining-function-call.ets @@ -17,10 +17,14 @@ type funcType = () => String; function getMelon(): String { return "melon" } +function dummy(): funcType | null { + return (() => { return "peach"; }) as funcType; // #15577 +} + function foo(a: funcType | null) { let fruit: String = a?.() ?? "apple"; - let getPeach: funcType | null = (() => { return "peach"; }) as funcType // #15577; + let getPeach: funcType | null = dummy(); fruit = getPeach?.() ?? "banana"; assert(fruit == "peach"); @@ -38,10 +42,10 @@ function foo(a: funcType | null) { } function main(): void { - let getFruit: funcType | null = null; + let getFruit = null; foo(getFruit); - let getPeach: funcType | null = (() => { return "peach" }) as funcType // #15577; + let getPeach: funcType | null = dummy(); let b = getPeach?.(); assert(b == "peach"); } diff --git a/ets2panda/test/runtime/ets/optional-chaining-lazy-evaluation.ets b/ets2panda/test/runtime/ets/optional-chaining-lazy-evaluation.ets index a88d8af72b761385c123dbb2b3683ef9a33cdc6a..345e64c81dd191599113fc9448d3e6ef66574b67 100644 --- a/ets2panda/test/runtime/ets/optional-chaining-lazy-evaluation.ets +++ b/ets2panda/test/runtime/ets/optional-chaining-lazy-evaluation.ets @@ -13,16 +13,24 @@ * limitations under the License. */ +function foo1(): Int[] | null { + return null; +} + +function foo2(): Int[] | null { + let obj_tmp: Int[] = [11, 21, 31]; + return obj_tmp; +} + function main(): void { let x: int = 0; - let potentiallyNullObj: Int[] | null = null; + let potentiallyNullObj: Int[] | null = foo1(); let number: Int | undefined = potentiallyNullObj?.[x++] ?? 2; assert(number == 2); assert(x == 0); // 0 as x was not incremented - let obj_tmp: Int[] = [11, 21, 31]; - let obj: Int[] | null = obj_tmp; + let obj: Int[] | null = foo2(); number = obj?.[x++]; let a : Int = 11; diff --git a/ets2panda/test/runtime/ets/optional-chaining-null-array.ets b/ets2panda/test/runtime/ets/optional-chaining-null-array.ets index 2fcbc2c208fdf62b64cdd4c32703cedec57f29d7..3c1fb8a3ab31bf7e0049f89262c20dde2877f0b3 100644 --- a/ets2panda/test/runtime/ets/optional-chaining-null-array.ets +++ b/ets2panda/test/runtime/ets/optional-chaining-null-array.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 - 2024 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 @@ -13,8 +13,12 @@ * limitations under the License. */ +function dummy(): String[] | null { + return null; +} + function main(): void { - let a: String[] | null = null; + let a: String[] | null = dummy(); let b = a?.[1]; let d: String = "foo"; let c: String = b ?? d; diff --git a/ets2panda/test/runtime/ets/optional-chaining-string-check.ets b/ets2panda/test/runtime/ets/optional-chaining-string-check.ets index 427b895225591f92b90308552cd4766d9e23a1ff..1570c086223eaa458dbe05f0cbc3eb0aca922db6 100644 --- a/ets2panda/test/runtime/ets/optional-chaining-string-check.ets +++ b/ets2panda/test/runtime/ets/optional-chaining-string-check.ets @@ -17,6 +17,10 @@ class Person { name: String = "Bill"; } +function dummy(): Person | null { + return null; +} + function main(): void { let bill : Person = new Person(); let name = bill.name; @@ -27,7 +31,7 @@ function main(): void { name = bill.name; assert(name == "Bill"); - let juan: Person | null = null; + let juan: Person | null = dummy(); name = juan?.name ?? "Juan"; assert (name.length == 4); diff --git a/ets2panda/test/runtime/ets/tuple_types_runtime.ets b/ets2panda/test/runtime/ets/tuple_types_runtime.ets index eefcf68e81b095623466db9e42c5b5956fd6b502..3fb1b70ffc368f8e82481df788e268ac75d28295 100644 --- a/ets2panda/test/runtime/ets/tuple_types_runtime.ets +++ b/ets2panda/test/runtime/ets/tuple_types_runtime.ets @@ -88,9 +88,9 @@ function main(): void { prim_num_arr = tup_4; let test_arr_2: number[] = [11, 2]; - for(let idx = 0; idx < prim_num_arr.length; idx++){ - assert(prim_num_arr[idx] == test_arr_2[idx]); - } + assert(prim_num_arr[0] == test_arr_2[0]); + assert(prim_num_arr[1] == test_arr_2[1]); + let tup_8: [number, string][]; tup_8 = [[1, "E"], [2, "F"], [3, "G"]]; diff --git a/ets2panda/test/unit/union_normalization_test.cpp b/ets2panda/test/unit/union_normalization_test.cpp index 72224aa6fa4642997f91d8975c17410933f85b9d..12228180cdec4ccf0534da713a6adaea8b965d1e 100644 --- a/ets2panda/test/unit/union_normalization_test.cpp +++ b/ets2panda/test/unit/union_normalization_test.cpp @@ -32,6 +32,7 @@ #include "util/generateBin.h" #include "varbinder/ETSBinder.h" #include "test/utils/panda_executable_path_getter.h" +#include "checker/types/globalTypesHolder.h" namespace ark::es2panda {