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