1/**
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "stringConstructorLowering.h"
17
18#include "checker/ETSchecker.h"
19#include "compiler/lowering/util.h"
20#include "compiler/lowering/scopesInit/scopesInitPhase.h"
21#include "parser/ETSparser.h"
22#include "varbinder/ETSBinder.h"
23#include "varbinder/scope.h"
24
25namespace ark::es2panda::compiler {
26
27std::string_view StringConstructorLowering::Name() const
28{
29    return "StringConstructorLowering";
30}
31
32// NOLINTBEGIN(modernize-avoid-c-arrays)
33static constexpr char const FORMAT_CHECK_NULL_EXPRESSION[] =
34    "let @@I1 = (@@E2);"
35    "(@@I3 === null ? \"null\" : @@I4 as String)";
36
37static constexpr char const FORMAT_CHECK_UNDEFINED_EXPRESSION[] =
38    "let @@I1 = (@@E2);"
39    "(@@I3 === undefined ? \"undefined\" : @@I4 as String)";
40
41static constexpr char const FORMAT_CHECK_NULLISH_EXPRESSION[] =
42    "let @@I1 = (@@E2);"
43    "(@@I3 instanceof null ? \"null\" : (@@I4 instanceof undefined ? \"undefined\" : @@I5 as String))";
44
45static constexpr char const FORMAT_TO_STRING_EXPRESSION[] = "((@@E1 as Object).toString())";
46// NOLINTEND(modernize-avoid-c-arrays)
47
48ir::Expression *ReplaceStringConstructor(public_lib::Context *const ctx,
49                                         ir::ETSNewClassInstanceExpression *newClassInstExpr)
50{
51    auto *checker = ctx->checker->AsETSChecker();
52    auto *parser = ctx->parser->AsETSParser();
53
54    // Skip missing signatures
55    if (newClassInstExpr->GetSignature() == nullptr || newClassInstExpr->GetSignature()->InternalName() == nullptr) {
56        return newClassInstExpr;
57    }
58
59    // Case for the constructor: new String(str: string)
60    if (newClassInstExpr->GetSignature()->InternalName() == Signatures::BUILTIN_STRING_FROM_STRING_CTOR) {
61        auto *arg = newClassInstExpr->GetArguments()[0];
62        arg->SetParent(newClassInstExpr->Parent());
63        return arg;
64    }
65
66    // Case for the constructor: new String(str: NullishType)
67    if (newClassInstExpr->GetSignature()->InternalName() == Signatures::BUILTIN_STRING_FROM_NULLISH_CTOR) {
68        auto *arg = newClassInstExpr->GetArguments()[0];
69        auto *argType = arg->TsType();
70
71        // For the case when the constructor parameter is "null" or "undefined"
72        if (argType->IsETSNullType() || argType->IsETSUndefinedType()) {
73            auto *literal = argType->IsETSNullType() ? checker->AllocNode<ir::StringLiteral>("null")
74                                                     : checker->AllocNode<ir::StringLiteral>("undefined");
75            literal->SetParent(newClassInstExpr->Parent());
76
77            // Run checker
78            literal->Check(checker);
79            return literal;
80        }
81
82        // Enter to the old scope
83        auto *scope = NearestScope(newClassInstExpr);
84        auto exprCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
85
86        // Generate temporary variable
87        auto const tmpIdentName = GenName(checker->Allocator());
88
89        // Create BlockExpression
90        ir::Expression *blockExpr = nullptr;
91        if (argType->PossiblyETSNull() && !argType->PossiblyETSUndefined()) {
92            blockExpr = parser->CreateFormattedExpression(FORMAT_CHECK_NULL_EXPRESSION, tmpIdentName, arg, tmpIdentName,
93                                                          tmpIdentName);
94        } else if (argType->PossiblyETSUndefined() && !argType->PossiblyETSNull()) {
95            blockExpr = parser->CreateFormattedExpression(FORMAT_CHECK_UNDEFINED_EXPRESSION, tmpIdentName, arg,
96                                                          tmpIdentName, tmpIdentName);
97        } else if (argType->PossiblyETSNullish()) {
98            blockExpr = parser->CreateFormattedExpression(FORMAT_CHECK_NULLISH_EXPRESSION, tmpIdentName, arg,
99                                                          tmpIdentName, tmpIdentName, tmpIdentName);
100        } else {
101            blockExpr = parser->CreateFormattedExpression(FORMAT_TO_STRING_EXPRESSION, arg);
102        }
103
104        blockExpr->SetParent(newClassInstExpr->Parent());
105
106        // Run VarBinder for new BlockExpression
107        InitScopesPhaseETS::RunExternalNode(blockExpr, checker->VarBinder());
108        checker->VarBinder()->AsETSBinder()->ResolveReferencesForScope(blockExpr, NearestScope(blockExpr));
109
110        // Run checker
111        blockExpr->Check(checker);
112        return blockExpr;
113    }
114
115    return newClassInstExpr;
116}
117
118bool StringConstructorLowering::Perform(public_lib::Context *const ctx, parser::Program *const program)
119{
120    for (const auto &[_, ext_programs] : program->ExternalSources()) {
121        (void)_;
122        for (auto *const extProg : ext_programs) {
123            Perform(ctx, extProg);
124        }
125    }
126
127    program->Ast()->TransformChildrenRecursively(
128        [ctx](ir::AstNode *ast) -> ir::AstNode * {
129            if (ast->IsETSNewClassInstanceExpression()) {
130                return ReplaceStringConstructor(ctx, ast->AsETSNewClassInstanceExpression());
131            }
132
133            return ast;
134        },
135        Name());
136
137    return true;
138}
139
140}  // namespace ark::es2panda::compiler
141