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 
42 namespace ark::es2panda::compiler {
GetUnionFieldClass(checker::ETSChecker *checker, varbinder::VarBinder *varbinder)43 static 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 
CreateUnionFieldClassProperty(checker::ETSChecker *checker, varbinder::VarBinder *varbinder, checker::Type *fieldType, const util::StringView &propName)71 static 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 
HandleUnionPropertyAccess(checker::ETSChecker *checker, varbinder::VarBinder *vbind, ir::MemberExpression *expr)108 static 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 
GenAsExpression(checker::ETSChecker *checker, checker::Type *const opaqueType, ir::Expression *const node, ir::AstNode *const parent)122 static 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  */
UnionCastToPrimitive(checker::ETSChecker *checker, checker::ETSObjectType *unboxableRef, checker::Type *unboxedPrim, ir::Expression *unionNode)138 static 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 
HandleUnionCastToPrimitive(checker::ETSChecker *checker, ir::TSAsExpression *expr)145 static 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 
Perform(public_lib::Context *ctx, parser::Program *program)172 bool 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 
Postcondition(public_lib::Context *ctx, const parser::Program *program)208 bool 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