1 /**
2 * Copyright (c) 2023-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 "interfacePropertyDeclarations.h"
17
18 #include "checker/ETSchecker.h"
19 #include "checker/types/type.h"
20 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
21 #include "compiler/lowering/util.h"
22 #include "ir/astNode.h"
23 #include "ir/expression.h"
24 #include "ir/expressions/identifier.h"
25 #include "ir/opaqueTypeNode.h"
26 #include "ir/statements/blockStatement.h"
27 #include "ir/ts/tsInterfaceBody.h"
28 #include "ir/base/classProperty.h"
29 #include "ir/ets/etsUnionType.h"
30 #include "ir/ets/etsNullishTypes.h"
31
32 namespace ark::es2panda::compiler {
33
TransformOptionalFieldTypeAnnotation(public_lib::Context * ctx,ir::ClassProperty * const field,bool isInterface)34 void InterfacePropertyDeclarationsPhase::TransformOptionalFieldTypeAnnotation(public_lib::Context *ctx,
35 ir::ClassProperty *const field,
36 bool isInterface)
37 {
38 if (!field->IsOptionalDeclaration()) {
39 return;
40 }
41
42 if (field->IsETSUnionType()) {
43 bool alreadyHasUndefined = false;
44 auto unionTypes = field->AsETSUnionType()->Types();
45 for (const auto &type : unionTypes) {
46 if (type->IsETSUndefinedType()) {
47 alreadyHasUndefined = true;
48 break;
49 }
50 }
51 if (!alreadyHasUndefined) {
52 ArenaVector<ir::TypeNode *> types(field->AsETSUnionType()->Types(), ctx->Allocator()->Adapter());
53 types.push_back(ctx->AllocNode<ir::ETSUndefinedType>(ctx->Allocator()));
54 auto *const unionType = ctx->AllocNode<ir::ETSUnionType>(std::move(types), ctx->Allocator());
55 field->SetTypeAnnotation(unionType);
56 }
57 } else {
58 ArenaVector<ir::TypeNode *> types(ctx->Allocator()->Adapter());
59 types.push_back(field->TypeAnnotation());
60 types.push_back(ctx->AllocNode<ir::ETSUndefinedType>(ctx->Allocator()));
61 auto *const unionType = ctx->AllocNode<ir::ETSUnionType>(std::move(types), ctx->Allocator());
62 field->SetTypeAnnotation(unionType);
63 }
64 field->ClearModifier(ir::ModifierFlags::OPTIONAL);
65
66 if (isInterface) {
67 GetPropCollector().InsertInterfaceProperty(field->Key()->ToString());
68 }
69 }
70
GenerateGetterOrSetterSignature(public_lib::Context * ctx,varbinder::ETSBinder * varbinder,ir::ClassProperty * const field,bool isSetter,varbinder::FunctionParamScope * paramScope)71 ir::FunctionSignature InterfacePropertyDeclarationsPhase::GenerateGetterOrSetterSignature(
72 public_lib::Context *ctx, varbinder::ETSBinder *varbinder, ir::ClassProperty *const field, bool isSetter,
73 varbinder::FunctionParamScope *paramScope)
74 {
75 TransformOptionalFieldTypeAnnotation(ctx, field, true);
76 ArenaVector<ir::Expression *> params(ctx->Allocator()->Adapter());
77
78 if (isSetter) {
79 auto paramIdent = field->Key()->AsIdentifier()->Clone(ctx->Allocator(), nullptr);
80 paramIdent->SetTsTypeAnnotation(field->TypeAnnotation()->Clone(ctx->Allocator(), nullptr));
81 paramIdent->TypeAnnotation()->SetParent(paramIdent);
82
83 ClearTypesVariablesAndScopes(paramIdent);
84 auto classCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, paramScope);
85 InitScopesPhaseETS::RunExternalNode(paramIdent, varbinder);
86
87 auto *const paramExpression = ctx->AllocNode<ir::ETSParameterExpression>(paramIdent, false, ctx->Allocator());
88 ES2PANDA_ASSERT(paramExpression != nullptr);
89 paramExpression->SetRange(paramIdent->Range());
90 auto [paramVar, node] = paramScope->AddParamDecl(ctx->Allocator(), varbinder, paramExpression);
91 if (node != nullptr) {
92 varbinder->ThrowRedeclaration(node->Start(), paramVar->Name(), paramVar->Declaration()->Type());
93 }
94
95 paramIdent->SetVariable(paramVar);
96 paramExpression->SetVariable(paramVar);
97
98 params.push_back(paramExpression);
99 }
100
101 return ir::FunctionSignature(nullptr, std::move(params), isSetter ? nullptr : field->TypeAnnotation());
102 }
103
GenerateGetterOrSetter(public_lib::Context * ctx,varbinder::ETSBinder * varbinder,ir::ClassProperty * const field,bool isSetter)104 ir::MethodDefinition *InterfacePropertyDeclarationsPhase::GenerateGetterOrSetter(public_lib::Context *ctx,
105 varbinder::ETSBinder *varbinder,
106 ir::ClassProperty *const field,
107 bool isSetter)
108 {
109 auto classScope = NearestScope(field);
110 auto *paramScope = ctx->Allocator()->New<varbinder::FunctionParamScope>(ctx->Allocator(), classScope);
111 auto *functionScope = ctx->Allocator()->New<varbinder::FunctionScope>(ctx->Allocator(), paramScope);
112 ES2PANDA_ASSERT(functionScope != nullptr);
113
114 functionScope->BindParamScope(paramScope);
115 paramScope->BindFunctionScope(functionScope);
116
117 auto flags = ir::ModifierFlags::PUBLIC;
118 flags |= ir::ModifierFlags::ABSTRACT;
119
120 ir::FunctionSignature signature = GenerateGetterOrSetterSignature(ctx, varbinder, field, isSetter, paramScope);
121
122 auto *func = ctx->AllocNode<ir::ScriptFunction>(
123 ctx->Allocator(), ir::ScriptFunction::ScriptFunctionData {
124 nullptr, std::move(signature), // CC-OFF(G.FMT.02) project code style
125 // CC-OFFNXT(G.FMT.02) project code style
126 isSetter ? ir::ScriptFunctionFlags::SETTER : ir::ScriptFunctionFlags::GETTER, flags});
127
128 func->SetRange(field->Range());
129 func->SetScope(functionScope);
130
131 auto const &name = field->Key()->AsIdentifier()->Name();
132 auto methodIdent = ctx->AllocNode<ir::Identifier>(name, ctx->Allocator());
133
134 auto *decl = ctx->Allocator()->New<varbinder::VarDecl>(name);
135 auto var = functionScope->AddDecl(ctx->Allocator(), decl, ScriptExtension::ETS);
136 ES2PANDA_ASSERT(var != nullptr);
137 methodIdent->SetVariable(var);
138
139 auto *funcExpr = ctx->AllocNode<ir::FunctionExpression>(func);
140 funcExpr->SetRange(func->Range());
141 func->AddFlag(ir::ScriptFunctionFlags::METHOD);
142
143 auto *method =
144 ctx->AllocNode<ir::MethodDefinition>(isSetter ? ir::MethodDefinitionKind::SET : ir::MethodDefinitionKind::GET,
145 methodIdent, funcExpr, flags, ctx->Allocator(), false);
146
147 method->Id()->SetMutator();
148 method->SetRange(field->Range());
149 method->Function()->SetIdent(method->Id()->Clone(ctx->Allocator(), nullptr));
150 method->Function()->AddModifier(method->Modifiers());
151 paramScope->BindNode(func);
152 functionScope->BindNode(func);
153
154 if (!field->Annotations().empty()) {
155 ArenaVector<ir::AnnotationUsage *> functionAnnotations(ctx->Allocator()->Adapter());
156 for (auto *annotationUsage : field->Annotations()) {
157 functionAnnotations.push_back(annotationUsage->Clone(ctx->Allocator(), method)->AsAnnotationUsage());
158 }
159 method->Function()->SetAnnotations(std::move(functionAnnotations));
160 }
161
162 return method;
163 }
164
CollectPropertiesAndSuperInterfaces(ir::TSInterfaceBody * const interface)165 void InterfacePropertyDeclarationsPhase::CollectPropertiesAndSuperInterfaces(ir::TSInterfaceBody *const interface)
166 {
167 ES2PANDA_ASSERT(interface->Parent()->IsTSInterfaceDeclaration());
168 auto *interfaceDecl = interface->Parent()->AsTSInterfaceDeclaration();
169 GetPropCollector().SetInterfaceId(interfaceDecl->Id()->ToString());
170 GetPropCollector().InitInterfacePropertyMap();
171 for (const auto &superInterface : interfaceDecl->Extends()) {
172 std::string superId = superInterface->Expr()->AsETSTypeReference()->Part()->Name()->ToString();
173 if (!GetPropCollector().IsParentExists(GetPropCollector().GetInterfaceId())) {
174 GetPropCollector().InitInterfaceParentMap();
175 }
176 GetPropCollector().InsertInterfaceParent(superId);
177 }
178 }
179
HandleInternalGetterOrSetterMethod(ir::AstNode * const ast)180 void InterfacePropertyDeclarationsPhase::HandleInternalGetterOrSetterMethod(ir::AstNode *const ast)
181 {
182 if (!ast->IsMethodDefinition()) {
183 return;
184 }
185 auto *method = ast->AsMethodDefinition();
186 if (method->Kind() == ir::MethodDefinitionKind::GET || method->Kind() == ir::MethodDefinitionKind::SET) {
187 GetPropCollector().InsertInterfaceProperty(method->Key()->ToString());
188 }
189 }
190
191 // Extracted form 'UpdateInterfaceProperties(...)' to reduce its size.
AddOverload(ir::MethodDefinition * method,ir::MethodDefinition * overload,varbinder::Variable * variable)192 static void AddOverload(ir::MethodDefinition *method, ir::MethodDefinition *overload, varbinder::Variable *variable)
193 {
194 method->AddOverload(overload);
195 overload->SetParent(method);
196 ES2PANDA_ASSERT(overload->Function());
197 overload->Function()->AddFlag(ir::ScriptFunctionFlags::OVERLOAD);
198 overload->Function()->Id()->SetVariable(variable);
199 }
200
UpdateInterfaceProperties(public_lib::Context * ctx,varbinder::ETSBinder * varbinder,ir::TSInterfaceBody * const interface)201 ir::Expression *InterfacePropertyDeclarationsPhase::UpdateInterfaceProperties(public_lib::Context *ctx,
202 varbinder::ETSBinder *varbinder,
203 ir::TSInterfaceBody *const interface)
204 {
205 if (interface->Body().empty()) {
206 return interface;
207 }
208
209 CollectPropertiesAndSuperInterfaces(interface);
210
211 auto propertyList = interface->Body();
212 ArenaVector<ir::AstNode *> newPropertyList(ctx->Allocator()->Adapter());
213
214 auto scope = NearestScope(interface);
215 ES2PANDA_ASSERT(scope->IsClassScope());
216
217 for (const auto &prop : propertyList) {
218 if (!prop->IsClassProperty()) {
219 newPropertyList.emplace_back(prop);
220 HandleInternalGetterOrSetterMethod(prop);
221 continue;
222 }
223 auto *originProp = prop->Clone(ctx->allocator, nullptr);
224 auto getter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), false);
225 getter->SetOriginalNode(originProp);
226
227 auto methodScope = scope->AsClassScope()->InstanceMethodScope();
228 auto name = getter->Key()->AsIdentifier()->Name();
229
230 auto *decl = ctx->Allocator()->New<varbinder::FunctionDecl>(ctx->Allocator(), name, getter);
231 auto *variable = methodScope->AddDecl(ctx->Allocator(), decl, ScriptExtension::ETS);
232
233 if (variable == nullptr) {
234 auto prevDecl = methodScope->FindDecl(name);
235 ES2PANDA_ASSERT(prevDecl->IsFunctionDecl());
236
237 auto *const method = prevDecl->Node()->AsMethodDefinition();
238 auto *const var = methodScope->FindLocal(name, varbinder::ResolveBindingOptions::BINDINGS);
239
240 AddOverload(method, getter, var);
241
242 if (!prop->AsClassProperty()->IsReadonly()) {
243 auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true);
244 AddOverload(method, setter, var);
245 }
246 continue;
247 }
248
249 getter->Function()->Id()->SetVariable(variable);
250 newPropertyList.emplace_back(getter);
251
252 if (!prop->AsClassProperty()->IsReadonly()) {
253 auto setter = GenerateGetterOrSetter(ctx, varbinder, prop->AsClassProperty(), true);
254 AddOverload(getter, setter, variable);
255 }
256 scope->AsClassScope()->InstanceFieldScope()->EraseBinding(name);
257 }
258
259 auto newInterface = ctx->AllocNode<ir::TSInterfaceBody>(std::move(newPropertyList));
260 ES2PANDA_ASSERT(newInterface != nullptr);
261 newInterface->SetRange(interface->Range());
262 newInterface->SetParent(interface->Parent());
263
264 return newInterface;
265 }
266
CollectSuperInterfaceProperties(InterfacePropertyType & implInterfaceProperties,const std::string & interId)267 void InterfacePropertyDeclarationsPhase::CollectSuperInterfaceProperties(InterfacePropertyType &implInterfaceProperties,
268 const std::string &interId)
269 {
270 if (GetPropCollector().IsVisitedInterface(interId)) {
271 return;
272 }
273
274 if (GetPropCollector().IsInterfaceHasProperty(interId)) {
275 InterfacePropertyType &properties = GetPropCollector().GetInterfaceProperty(interId);
276 implInterfaceProperties.insert(properties.begin(), properties.end());
277 }
278 if (GetPropCollector().IsParentExists(interId)) {
279 for (auto &superId : GetPropCollector().GetInterfaceParent(interId)) {
280 CollectSuperInterfaceProperties(implInterfaceProperties, superId);
281 }
282 }
283 }
284
UpdateClassProperties(public_lib::Context * ctx,ir::ClassDefinition * const klass)285 void InterfacePropertyDeclarationsPhase::UpdateClassProperties(public_lib::Context *ctx,
286 ir::ClassDefinition *const klass)
287 {
288 if (klass->Body().empty()) {
289 return;
290 }
291
292 InterfacePropertyType implInterfaceProperties = {};
293
294 GetPropCollector().InitVisitedInterfaces();
295 for (const auto &implement : klass->Implements()) {
296 std::string interId = implement->Expr()->IsOpaqueTypeNode()
297 ? implement->Expr()->TsType()->AsETSObjectType()->Name().Mutf8()
298 : implement->Expr()->AsETSTypeReference()->Part()->Name()->ToString();
299 CollectSuperInterfaceProperties(implInterfaceProperties, interId);
300 }
301
302 for (auto *elem : klass->Body()) {
303 if (elem->IsClassProperty() &&
304 (implInterfaceProperties.count(elem->AsClassProperty()->Key()->ToString()) != 0U)) {
305 TransformOptionalFieldTypeAnnotation(ctx, elem->AsClassProperty());
306 }
307 }
308 }
309
PerformForModule(public_lib::Context * ctx,parser::Program * program)310 bool InterfacePropertyDeclarationsPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program)
311 {
312 varbinder::ETSBinder *const varbinder = ctx->parserProgram->VarBinder()->AsETSBinder();
313
314 ir::NodeTransformer handleInterfacePropertyDecl = [this, ctx, varbinder](ir::AstNode *const ast) {
315 return ast->IsTSInterfaceBody() ? UpdateInterfaceProperties(ctx, varbinder, ast->AsTSInterfaceBody()) : ast;
316 };
317
318 ir::NodeTransformer handleClassPropertyDecl = [this, ctx](ir::AstNode *const ast) {
319 if (ast->IsClassDefinition() && !ast->AsClassDefinition()->Implements().empty()) {
320 UpdateClassProperties(ctx, ast->AsClassDefinition());
321 }
322 return ast;
323 };
324
325 program->Ast()->TransformChildrenRecursively(handleInterfacePropertyDecl, Name());
326 program->Ast()->TransformChildrenRecursively(handleClassPropertyDecl, Name());
327
328 return true;
329 }
330
331 } // namespace ark::es2panda::compiler
332