1/*
2 * Copyright (c) 2021-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 "unionLowering.h"
17#include "varbinder/variableFlags.h"
18#include "varbinder/ETSBinder.h"
19#include "checker/ETSchecker.h"
20#include "checker/ets/conversion.h"
21#include "checker/ets/boxingConverter.h"
22#include "checker/ets/unboxingConverter.h"
23#include "compiler/lowering/util.h"
24#include "compiler/lowering/scopesInit/scopesInitPhase.h"
25#include "ir/base/classDefinition.h"
26#include "ir/base/classProperty.h"
27#include "ir/astNode.h"
28#include "ir/expression.h"
29#include "ir/opaqueTypeNode.h"
30#include "ir/expressions/literals/nullLiteral.h"
31#include "ir/expressions/literals/undefinedLiteral.h"
32#include "ir/expressions/binaryExpression.h"
33#include "ir/expressions/identifier.h"
34#include "ir/expressions/memberExpression.h"
35#include "ir/statements/blockStatement.h"
36#include "ir/statements/classDeclaration.h"
37#include "ir/statements/variableDeclaration.h"
38#include "ir/ts/tsAsExpression.h"
39#include "type_helper.h"
40#include "public/public.h"
41
42namespace ark::es2panda::compiler {
43static ir::ClassDefinition *GetUnionFieldClass(checker::ETSChecker *checker, varbinder::VarBinder *varbinder)
44{
45    // Create the name for the synthetic class node
46    util::UString unionFieldClassName(util::StringView(panda_file::GetDummyClassName()), checker->Allocator());
47    varbinder::Variable *foundVar = nullptr;
48    if ((foundVar = checker->Scope()->FindLocal(unionFieldClassName.View(),
49                                                varbinder::ResolveBindingOptions::BINDINGS)) != nullptr) {
50        return foundVar->Declaration()->Node()->AsClassDeclaration()->Definition();
51    }
52    auto *ident = checker->AllocNode<ir::Identifier>(unionFieldClassName.View(), checker->Allocator());
53    auto [decl, var] = varbinder->NewVarDecl<varbinder::ClassDecl>(ident->Start(), ident->Name());
54    ident->SetVariable(var);
55
56    auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>(varbinder);
57    auto *classDef =
58        checker->AllocNode<ir::ClassDefinition>(checker->Allocator(), ident, ir::ClassDefinitionModifiers::GLOBAL,
59                                                ir::ModifierFlags::FINAL, Language(Language::Id::ETS));
60    classDef->SetScope(classCtx.GetScope());
61    auto *classDecl = checker->AllocNode<ir::ClassDeclaration>(classDef, checker->Allocator());
62    classDef->Scope()->BindNode(classDecl);
63    classDef->SetTsType(checker->GlobalETSObjectType());
64    decl->BindNode(classDecl);
65    var->SetScope(classDef->Scope());
66
67    varbinder->AsETSBinder()->BuildClassDefinition(classDef);
68    return classDef;
69}
70
71static varbinder::LocalVariable *CreateUnionFieldClassProperty(checker::ETSChecker *checker,
72                                                               varbinder::VarBinder *varbinder,
73                                                               checker::Type *fieldType,
74                                                               const util::StringView &propName)
75{
76    auto *const allocator = checker->Allocator();
77    auto *const dummyClass = GetUnionFieldClass(checker, varbinder);
78    auto *classScope = dummyClass->Scope()->AsClassScope();
79
80    // Enter the union filed class instance field scope
81    auto fieldCtx = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varbinder, classScope->InstanceFieldScope());
82
83    if (auto *var = classScope->FindLocal(propName, varbinder::ResolveBindingOptions::VARIABLES); var != nullptr) {
84        return var->AsLocalVariable();
85    }
86
87    // Create field name for synthetic class
88    auto *fieldIdent = checker->AllocNode<ir::Identifier>(propName, allocator);
89
90    // Create the synthetic class property node
91    auto *field =
92        checker->AllocNode<ir::ClassProperty>(fieldIdent, nullptr, nullptr, ir::ModifierFlags::NONE, allocator, false);
93
94    // Add the declaration to the scope
95    auto [decl, var] = varbinder->NewVarDecl<varbinder::LetDecl>(fieldIdent->Start(), fieldIdent->Name());
96    var->AddFlag(varbinder::VariableFlags::PROPERTY);
97    var->SetTsType(fieldType);
98    fieldIdent->SetVariable(var);
99    field->SetTsType(fieldType);
100    decl->BindNode(field);
101
102    ArenaVector<ir::AstNode *> fieldDecl {allocator->Adapter()};
103    fieldDecl.push_back(field);
104    dummyClass->AddProperties(std::move(fieldDecl));
105    return var->AsLocalVariable();
106}
107
108static void HandleUnionPropertyAccess(checker::ETSChecker *checker, varbinder::VarBinder *vbind,
109                                      ir::MemberExpression *expr)
110{
111    if (expr->PropVar() != nullptr) {
112        return;
113    }
114    [[maybe_unused]] auto parent = expr->Parent();
115    ASSERT(!(parent->IsCallExpression() && parent->AsCallExpression()->Callee() == expr &&
116             parent->AsCallExpression()->Signature()->HasSignatureFlag(checker::SignatureFlags::TYPE)));
117    expr->SetPropVar(
118        CreateUnionFieldClassProperty(checker, vbind, expr->TsType(), expr->Property()->AsIdentifier()->Name()));
119    ASSERT(expr->PropVar() != nullptr);
120}
121
122static ir::TSAsExpression *GenAsExpression(checker::ETSChecker *checker, checker::Type *const opaqueType,
123                                           ir::Expression *const node, ir::AstNode *const parent)
124{
125    auto *const typeNode = checker->AllocNode<ir::OpaqueTypeNode>(opaqueType);
126    auto *const asExpression = checker->AllocNode<ir::TSAsExpression>(node, typeNode, false);
127    asExpression->SetParent(parent);
128    asExpression->Check(checker);
129    return asExpression;
130}
131
132/*
133 *  Function that generates conversion from (union) to (primitive) type as to `as` expressions:
134 *      (union) as (prim) => ((union) as (ref)) as (prim),
135 *      where (ref) is some unboxable type from union constituent types.
136 *  Finally, `(union) as (prim)` expression replaces union_node that came above.
137 */
138static ir::TSAsExpression *UnionCastToPrimitive(checker::ETSChecker *checker, checker::ETSObjectType *unboxableRef,
139                                                checker::Type *unboxedPrim, ir::Expression *unionNode)
140{
141    auto *const unionAsRefExpression = GenAsExpression(checker, unboxableRef, unionNode, nullptr);
142    return GenAsExpression(checker, unboxedPrim, unionAsRefExpression, unionNode->Parent());
143}
144
145static ir::TSAsExpression *HandleUnionCastToPrimitive(checker::ETSChecker *checker, ir::TSAsExpression *expr)
146{
147    auto *const unionType = expr->Expr()->TsType()->AsETSUnionType();
148    auto *sourceType = unionType->FindExactOrBoxedType(checker, expr->TsType());
149    if (sourceType == nullptr) {
150        sourceType = unionType->AsETSUnionType()->FindTypeIsCastableToSomeType(expr->Expr(), checker->Relation(),
151                                                                               expr->TsType());
152    }
153    if (sourceType != nullptr && expr->Expr()->GetBoxingUnboxingFlags() != ir::BoxingUnboxingFlags::NONE) {
154        if (expr->TsType()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) {
155            auto *const asExpr = GenAsExpression(checker, sourceType, expr->Expr(), expr);
156            asExpr->SetBoxingUnboxingFlags(
157                checker->GetUnboxingFlag(checker->ETSBuiltinTypeAsPrimitiveType(sourceType)));
158            expr->Expr()->SetBoxingUnboxingFlags(ir::BoxingUnboxingFlags::NONE);
159            expr->SetExpr(asExpr);
160        }
161        return expr;
162    }
163    auto *const unboxableUnionType = sourceType != nullptr ? sourceType : unionType->FindUnboxableType();
164    auto *const unboxedUnionType = checker->ETSBuiltinTypeAsPrimitiveType(unboxableUnionType);
165    auto *const node =
166        UnionCastToPrimitive(checker, unboxableUnionType->AsETSObjectType(), unboxedUnionType, expr->Expr());
167    node->SetParent(expr->Parent());
168
169    return node;
170}
171
172bool UnionLowering::Perform(public_lib::Context *ctx, parser::Program *program)
173{
174    for (auto &[_, ext_programs] : program->ExternalSources()) {
175        (void)_;
176        for (auto *extProg : ext_programs) {
177            Perform(ctx, extProg);
178        }
179    }
180
181    checker::ETSChecker *checker = ctx->checker->AsETSChecker();
182
183    program->Ast()->TransformChildrenRecursively(
184        [checker](ir::AstNode *ast) -> ir::AstNode * {
185            if (ast->IsMemberExpression() && ast->AsMemberExpression()->Object()->TsType() != nullptr) {
186                auto *objType =
187                    checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType()));
188                if (objType->IsETSUnionType()) {
189                    HandleUnionPropertyAccess(checker, checker->VarBinder(), ast->AsMemberExpression());
190                    return ast;
191                }
192            }
193
194            if (ast->IsTSAsExpression() && ast->AsTSAsExpression()->Expr()->TsType() != nullptr &&
195                ast->AsTSAsExpression()->Expr()->TsType()->IsETSUnionType() &&
196                ast->AsTSAsExpression()->TsType() != nullptr &&
197                ast->AsTSAsExpression()->TsType()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) {
198                return HandleUnionCastToPrimitive(checker, ast->AsTSAsExpression());
199            }
200
201            return ast;
202        },
203        Name());
204
205    return true;
206}
207
208bool UnionLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
209{
210    bool current = !program->Ast()->IsAnyChild([checker = ctx->checker->AsETSChecker()](ir::AstNode *ast) {
211        if (!ast->IsMemberExpression() || ast->AsMemberExpression()->Object()->TsType() == nullptr) {
212            return false;
213        }
214        auto *objType =
215            checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType()));
216        auto *parent = ast->Parent();
217        if (!(parent->IsCallExpression() &&
218              parent->AsCallExpression()->Signature()->HasSignatureFlag(checker::SignatureFlags::TYPE))) {
219            return false;
220        }
221        return objType->IsETSUnionType() && ast->AsMemberExpression()->PropVar() == nullptr;
222    });
223    if (!current || ctx->config->options->CompilerOptions().compilationMode != CompilationMode::GEN_STD_LIB) {
224        return current;
225    }
226
227    for (auto &[_, ext_programs] : program->ExternalSources()) {
228        (void)_;
229        for (auto *extProg : ext_programs) {
230            if (!Postcondition(ctx, extProg)) {
231                return false;
232            }
233        }
234    }
235    return true;
236}
237
238}  // namespace ark::es2panda::compiler
239