1 /*
2 * Copyright (c) 2021-2025 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 <algorithm>
17 #include "unionLowering.h"
18 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
19 #include "compiler/lowering/util.h"
20 #include "varbinder/ETSBinder.h"
21 #include "checker/ETSchecker.h"
22
23 namespace ark::es2panda::compiler {
24
25 static constexpr std::string_view PREFIX = "$NamedAccessMeta-";
26
ReplaceAll(std::string & str,std::string_view substr,std::string_view replacement)27 static void ReplaceAll(std::string &str, std::string_view substr, std::string_view replacement)
28 {
29 for (size_t pos = str.find(substr, 0); pos != std::string::npos; pos = str.find(substr, pos)) {
30 str.replace(pos, substr.size(), replacement);
31 pos += replacement.size();
32 }
33 }
34
GetAccessClassName(const checker::ETSUnionType * unionType)35 static std::string GetAccessClassName(const checker::ETSUnionType *unionType)
36 {
37 std::stringstream ss;
38 ss << PREFIX;
39 unionType->ToString(ss, false);
40 std::string res(ss.str());
41 std::replace(res.begin(), res.end(), '.', '-');
42 std::replace(res.begin(), res.end(), '|', '_');
43 ReplaceAll(res, "[]", "[$]$");
44 return res;
45 }
46
GetUnionAccessClass(public_lib::Context * ctx,varbinder::VarBinder * varbinder,std::string const & name)47 static ir::ClassDefinition *GetUnionAccessClass(public_lib::Context *ctx, varbinder::VarBinder *varbinder,
48 std::string const &name)
49 {
50 auto *checker = ctx->checker->AsETSChecker();
51 auto *allocator = ctx->Allocator();
52 // Create the name for the synthetic class node
53 if (auto foundVar = checker->Scope()->FindLocal(util::StringView(name), varbinder::ResolveBindingOptions::BINDINGS);
54 foundVar != nullptr) {
55 return foundVar->Declaration()->Node()->AsClassDefinition();
56 }
57 util::UString unionFieldClassName(util::StringView(name), allocator);
58 auto *ident = ctx->AllocNode<ir::Identifier>(unionFieldClassName.View(), allocator);
59 auto [decl, var] = varbinder->NewVarDecl<varbinder::ClassDecl>(ident->Start(), ident->Name());
60 ident->SetVariable(var);
61
62 auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>(varbinder);
63 auto *classDef = ctx->AllocNode<ir::ClassDefinition>(ctx->Allocator(), ident, ir::ClassDefinitionModifiers::GLOBAL,
64 ir::ModifierFlags::ABSTRACT, Language(Language::Id::ETS));
65 ES2PANDA_ASSERT(classDef != nullptr);
66 classDef->SetScope(classCtx.GetScope());
67 auto *classDecl = ctx->AllocNode<ir::ClassDeclaration>(classDef, allocator);
68 classDef->Scope()->BindNode(classDecl->Definition());
69 decl->BindNode(classDef);
70 var->SetScope(classDef->Scope());
71
72 varbinder->AsETSBinder()->BuildClassDefinition(classDef);
73
74 auto globalBlock = varbinder->Program()->Ast();
75 classDecl->SetParent(globalBlock);
76 globalBlock->Statements().push_back(classDecl);
77 classDecl->Check(checker);
78 return classDef;
79 }
80
CreateNamedAccessMethod(public_lib::Context * ctx,varbinder::VarBinder * varbinder,ir::MemberExpression * expr,checker::Signature * signature)81 static std::tuple<varbinder::LocalVariable *, checker::Signature *> CreateNamedAccessMethod(
82 public_lib::Context *ctx, varbinder::VarBinder *varbinder, ir::MemberExpression *expr,
83 checker::Signature *signature)
84 {
85 auto *allocator = ctx->Allocator();
86 auto *checker = ctx->checker->AsETSChecker();
87
88 auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType();
89 auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType));
90 auto methodName = expr->TsType()->AsETSFunctionType()->Name();
91
92 // Create method name for synthetic class
93 auto *methodIdent = ctx->AllocNode<ir::Identifier>(methodName, allocator);
94
95 ArenaVector<ir::Expression *> params {allocator->Adapter()};
96 for (auto param : signature->Function()->Params()) {
97 params.emplace_back(param->Clone(allocator, nullptr)->AsETSParameterExpression());
98 }
99 auto returnTypeAnno = ctx->AllocNode<ir::OpaqueTypeNode>(signature->ReturnType(), allocator);
100
101 auto *func = ctx->AllocNode<ir::ScriptFunction>(
102 allocator, ir::ScriptFunction::ScriptFunctionData {
103 // CC-OFFNXT(G.FMT.02-CPP) project code style
104 nullptr, ir::FunctionSignature(nullptr, std::move(params), returnTypeAnno),
105 // CC-OFFNXT(G.FMT.02-CPP) project code style
106 ir::ScriptFunctionFlags::METHOD, ir::ModifierFlags::PUBLIC});
107 ES2PANDA_ASSERT(func != nullptr);
108 func->SetIdent(methodIdent->Clone(allocator, nullptr));
109
110 // Create the synthetic function node
111 auto *funcExpr = ctx->AllocNode<ir::FunctionExpression>(func);
112
113 // Create the synthetic method definition node
114 auto *method =
115 ctx->AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::METHOD, methodIdent, funcExpr,
116 ir::ModifierFlags::PUBLIC | ir::ModifierFlags::ABSTRACT, allocator, false);
117 ArenaVector<ir::AstNode *> methodDecl {allocator->Adapter()};
118 methodDecl.push_back(method);
119 accessClass->AddProperties(std::move(methodDecl));
120
121 {
122 auto clsCtx =
123 varbinder::LexicalScope<varbinder::ClassScope>::Enter(varbinder, accessClass->Scope()->AsClassScope());
124 auto boundCtx = varbinder::BoundContext(varbinder->AsETSBinder()->GetRecordTable(), accessClass, true);
125 CheckLoweredNode(varbinder->AsETSBinder(), checker, method);
126 }
127
128 return {method->Id()->Variable()->AsLocalVariable(),
129 method->TsType()->AsETSFunctionType()->CallSignatures().front()};
130 }
131
CreateNamedAccessProperty(public_lib::Context * ctx,varbinder::VarBinder * varbinder,ir::MemberExpression * expr)132 static varbinder::LocalVariable *CreateNamedAccessProperty(public_lib::Context *ctx, varbinder::VarBinder *varbinder,
133 ir::MemberExpression *expr)
134 {
135 auto *const allocator = ctx->Allocator();
136 auto *checker = ctx->checker->AsETSChecker();
137
138 auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType();
139 auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType));
140 auto propName = expr->Property()->AsIdentifier()->Name();
141 auto fieldType = expr->TsType();
142 auto uncheckedType = expr->UncheckedType();
143 auto *typeToSet = uncheckedType == nullptr ? fieldType : uncheckedType;
144
145 // Create field name for synthetic class
146 auto *fieldIdent = ctx->AllocNode<ir::Identifier>(propName, allocator);
147
148 // Create the synthetic class property node
149 auto *field =
150 ctx->AllocNode<ir::ClassProperty>(fieldIdent, nullptr, nullptr, ir::ModifierFlags::NONE, allocator, false);
151 ES2PANDA_ASSERT(field != nullptr);
152 // Add the declaration to the scope
153 auto [decl, var] = varbinder->NewVarDecl<varbinder::LetDecl>(fieldIdent->Start(), fieldIdent->Name());
154 var->AddFlag(varbinder::VariableFlags::PROPERTY);
155 var->SetTsType(typeToSet);
156 fieldIdent->SetVariable(var);
157 field->SetTsType(typeToSet);
158 decl->BindNode(field);
159
160 ArenaVector<ir::AstNode *> fieldDecl {allocator->Adapter()};
161 fieldDecl.push_back(field);
162 accessClass->AddProperties(std::move(fieldDecl));
163 return var->AsLocalVariable();
164 }
165
CreateNamedAccess(public_lib::Context * ctx,varbinder::VarBinder * varbinder,ir::MemberExpression * expr)166 static varbinder::LocalVariable *CreateNamedAccess(public_lib::Context *ctx, varbinder::VarBinder *varbinder,
167 ir::MemberExpression *expr)
168 {
169 auto type = expr->TsType();
170 auto name = expr->Property()->AsIdentifier()->Name();
171 auto *checker = ctx->checker->AsETSChecker();
172 ES2PANDA_ASSERT(checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType())) != nullptr);
173 auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType();
174 auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType));
175 auto *classScope = accessClass->Scope()->AsClassScope();
176
177 if (auto *var = classScope->FindLocal(name, varbinder::ResolveBindingOptions::ALL_NON_STATIC); var != nullptr) {
178 return var->AsLocalVariable();
179 }
180
181 // arrow type fields should be processed as property access not method invocation
182 if (type->IsETSMethodType() && !type->IsETSArrowType()) {
183 auto parent = expr->Parent()->AsCallExpression();
184 ES2PANDA_ASSERT(parent->Callee() == expr && parent->Signature()->HasFunction());
185
186 auto [var, sig] = CreateNamedAccessMethod(ctx, varbinder, expr, parent->Signature());
187 parent->AsCallExpression()->SetSignature(sig);
188 return var;
189 }
190
191 // Enter the union filed class instance field scope
192 auto fieldCtx = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varbinder, classScope->InstanceFieldScope());
193 return CreateNamedAccessProperty(ctx, varbinder, expr);
194 }
195
HandleUnionPropertyAccess(public_lib::Context * ctx,varbinder::VarBinder * vbind,ir::MemberExpression * expr)196 static void HandleUnionPropertyAccess(public_lib::Context *ctx, varbinder::VarBinder *vbind, ir::MemberExpression *expr)
197 {
198 if (expr->PropVar() != nullptr) {
199 return;
200 }
201
202 [[maybe_unused]] auto const *const parent = expr->Parent();
203 expr->SetPropVar(CreateNamedAccess(ctx, vbind, expr));
204 ES2PANDA_ASSERT(expr->PropVar() != nullptr);
205 }
206
GenAsExpression(public_lib::Context * ctx,checker::Type * const opaqueType,ir::Expression * const node,ir::AstNode * const parent)207 static ir::TSAsExpression *GenAsExpression(public_lib::Context *ctx, checker::Type *const opaqueType,
208 ir::Expression *const node, ir::AstNode *const parent)
209 {
210 auto *const typeNode = ctx->AllocNode<ir::OpaqueTypeNode>(opaqueType, ctx->Allocator());
211 auto *const asExpression = ctx->AllocNode<ir::TSAsExpression>(node, typeNode, false);
212 ES2PANDA_ASSERT(asExpression != nullptr);
213 asExpression->SetParent(parent);
214 asExpression->Check(ctx->checker->AsETSChecker());
215 return asExpression;
216 }
217
218 /*
219 * Function that generates conversion from (union) to (primitive) type as to `as` expressions:
220 * (union) as (prim) => ((union) as (ref)) as (prim),
221 * where (ref) is some unboxable type from union constituent types.
222 * Finally, `(union) as (prim)` expression replaces union_node that came above.
223 */
UnionCastToPrimitive(public_lib::Context * ctx,checker::ETSObjectType * unboxableRef,checker::Type * unboxedPrim,ir::Expression * unionNode)224 static ir::TSAsExpression *UnionCastToPrimitive(public_lib::Context *ctx, checker::ETSObjectType *unboxableRef,
225 checker::Type *unboxedPrim, ir::Expression *unionNode)
226 {
227 auto *const unionAsRefExpression = GenAsExpression(ctx, unboxableRef, unionNode, nullptr);
228 return GenAsExpression(ctx, unboxedPrim, unionAsRefExpression, unionNode->Parent());
229 }
230
HandleUnionCastToPrimitive(public_lib::Context * ctx,ir::TSAsExpression * expr)231 static ir::TSAsExpression *HandleUnionCastToPrimitive(public_lib::Context *ctx, ir::TSAsExpression *expr)
232 {
233 checker::ETSChecker *checker = ctx->checker->AsETSChecker();
234 auto *const unionType = expr->Expr()->TsType()->AsETSUnionType();
235 auto *sourceType = unionType->FindExactOrBoxedType(checker, expr->TsType());
236 if (sourceType == nullptr) {
237 sourceType = unionType->AsETSUnionType()->FindTypeIsCastableToSomeType(expr->Expr(), checker->Relation(),
238 expr->TsType());
239 }
240
241 if (sourceType != nullptr && expr->Expr()->GetBoxingUnboxingFlags() != ir::BoxingUnboxingFlags::NONE) {
242 auto *maybeUnboxingType = checker->MaybeUnboxInRelation(sourceType);
243 // when sourceType get `object`, it could cast to any primitive type but can't be unboxed;
244 if (maybeUnboxingType != nullptr && expr->TsType()->IsETSPrimitiveType()) {
245 auto *const asExpr = GenAsExpression(ctx, sourceType, expr->Expr(), expr);
246 ES2PANDA_ASSERT(asExpr != nullptr);
247 asExpr->SetBoxingUnboxingFlags(checker->GetUnboxingFlag(maybeUnboxingType));
248 expr->Expr()->SetBoxingUnboxingFlags(ir::BoxingUnboxingFlags::NONE);
249 expr->SetExpr(asExpr);
250 }
251
252 return expr;
253 }
254
255 auto *const unboxableUnionType = sourceType != nullptr ? sourceType : unionType->FindUnboxableType();
256 auto *const unboxedUnionType = checker->MaybeUnboxInRelation(unboxableUnionType);
257 if (unboxableUnionType == nullptr || !unboxableUnionType->IsETSObjectType() || unboxedUnionType == nullptr) {
258 return expr;
259 }
260
261 auto *const node = UnionCastToPrimitive(ctx, unboxableUnionType->AsETSObjectType(), unboxedUnionType, expr->Expr());
262 node->SetParent(expr->Parent());
263
264 return node;
265 }
266
PerformForModule(public_lib::Context * ctx,parser::Program * program)267 bool UnionLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program)
268 {
269 checker::ETSChecker *checker = ctx->checker->AsETSChecker();
270
271 program->Ast()->TransformChildrenRecursively(
272 [ctx, checker](checker::AstNodePtr ast) -> checker::AstNodePtr {
273 if (ast->IsMemberExpression() && ast->AsMemberExpression()->Object()->TsType() != nullptr) {
274 auto *objType =
275 checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType()));
276 if (objType->IsETSUnionType()) {
277 HandleUnionPropertyAccess(ctx, checker->VarBinder(), ast->AsMemberExpression());
278 return ast;
279 }
280 }
281 if (ast->IsTSAsExpression() && ast->AsTSAsExpression()->Expr()->TsType() != nullptr &&
282 ast->AsTSAsExpression()->Expr()->TsType()->IsETSUnionType() &&
283 ast->AsTSAsExpression()->TsType() != nullptr &&
284 ast->AsTSAsExpression()->TsType()->IsETSPrimitiveType()) {
285 return HandleUnionCastToPrimitive(ctx, ast->AsTSAsExpression());
286 }
287
288 return ast;
289 },
290 Name());
291
292 return true;
293 }
294
PostconditionForModule(public_lib::Context * ctx,const parser::Program * program)295 bool UnionLowering::PostconditionForModule(public_lib::Context *ctx, const parser::Program *program)
296 {
297 auto *checker = ctx->checker->AsETSChecker();
298 bool current = !program->Ast()->IsAnyChild([checker](ir::AstNode *ast) {
299 if (!ast->IsMemberExpression() || ast->AsMemberExpression()->Object()->TsType() == nullptr) {
300 return false;
301 }
302 auto *objType =
303 checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType()));
304 auto *parent = ast->Parent();
305 if (!parent->IsCallExpression() || parent->AsCallExpression()->Signature() == nullptr ||
306 parent->AsCallExpression()->Signature()->HasFunction()) {
307 return false;
308 }
309 return objType->IsETSUnionType() && ast->AsMemberExpression()->PropVar() == nullptr;
310 });
311 if (!current || ctx->config->options->GetCompilationMode() != CompilationMode::GEN_STD_LIB) {
312 return current;
313 }
314
315 return true;
316 }
317
318 } // namespace ark::es2panda::compiler
319