• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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