1 /*
2 * Copyright (c) 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 "checker/ETSchecker.h"
17
18 #include "ir/ets/etsNullishTypes.h"
19 #include "ir/ets/etsUnionType.h"
20 #include "ir/expressions/literals/undefinedLiteral.h"
21 #include "varbinder/ETSBinder.h"
22
23 namespace ark::es2panda::checker {
24
GetUtilityTypeTypeParamNode(const ir::TSTypeParameterInstantiation * const typeParams,const std::string_view & utilityTypeName)25 ir::TypeNode *ETSChecker::GetUtilityTypeTypeParamNode(const ir::TSTypeParameterInstantiation *const typeParams,
26 const std::string_view &utilityTypeName)
27 {
28 if (typeParams->Params().size() != 1) {
29 ThrowTypeError({"Invalid number of type parameters for ", utilityTypeName, " type"}, typeParams->Start());
30 }
31
32 return typeParams->Params().front();
33 }
34
HandleUtilityTypeParameterNode(const ir::TSTypeParameterInstantiation * const typeParams,const std::string_view & utilityType)35 Type *ETSChecker::HandleUtilityTypeParameterNode(const ir::TSTypeParameterInstantiation *const typeParams,
36 const std::string_view &utilityType)
37 {
38 auto *const bareType = GetUtilityTypeTypeParamNode(typeParams, utilityType)->Check(this);
39
40 if (utilityType == compiler::Signatures::PARTIAL_TYPE_NAME) {
41 return HandlePartialType(bareType);
42 }
43
44 if (utilityType == compiler::Signatures::READONLY_TYPE_NAME) {
45 return GetReadonlyType(bareType);
46 }
47
48 if (utilityType == compiler::Signatures::REQUIRED_TYPE_NAME) {
49 return HandleRequiredType(bareType);
50 }
51
52 ThrowTypeError("This utility type is not yet implemented.", typeParams->Start());
53 }
54
55 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56 // Partial utility type
57 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
HandlePartialType(Type * const typeToBePartial)58 Type *ETSChecker::HandlePartialType(Type *const typeToBePartial)
59 {
60 if (typeToBePartial->IsETSTypeParameter()) {
61 // NOTE (mmartin): this is not type safe!
62 return HandlePartialType(GetApparentType(typeToBePartial));
63 }
64
65 if (typeToBePartial->IsETSUnionType()) {
66 return HandleUnionForPartialType(typeToBePartial->AsETSUnionType());
67 }
68
69 if (!typeToBePartial->Variable()->Declaration()->Node()->IsClassDefinition()) {
70 // NOTE (mmartin): there is a bug, that modifies the declaration of the variable of a type. That could make the
71 // declaration node of an ETSObjectType eg. a ClassProperty, instead of the actual class declaration. When it's
72 // fixed, remove this.
73 return typeToBePartial;
74 }
75
76 auto *const classDef = typeToBePartial->Variable()->Declaration()->Node()->AsClassDefinition();
77
78 // Partial class name for class 'T' will be 'T$partial'
79 const auto partialClassName = util::UString(classDef->Ident()->Name().Mutf8() + "$partial", Allocator()).View();
80
81 auto *const classDefProgram = classDef->GetTopStatement()->AsETSScript()->Program();
82 const bool isClassDeclaredInCurrentFile = classDefProgram == VarBinder()->Program();
83 auto *const programToUse = isClassDeclaredInCurrentFile ? VarBinder()->Program() : classDefProgram;
84 const auto qualifiedClassName = GetQualifiedClassName(programToUse, partialClassName);
85
86 // Check if we've already generated the partial class, then don't do it again
87 const auto classNameToFind =
88 isClassDeclaredInCurrentFile || VarBinder()->IsGenStdLib() ? partialClassName : qualifiedClassName;
89 if (const auto *const var =
90 SearchNamesInMultiplePrograms({programToUse, VarBinder()->Program()}, {classNameToFind, partialClassName});
91 var != nullptr) {
92 return var->TsType();
93 }
94
95 // Create only the 'header' of the class
96 auto *const partialClassDef = CreateClassPrototype(partialClassName, programToUse);
97
98 auto *const recordTableToUse = isClassDeclaredInCurrentFile
99 ? VarBinder()->AsETSBinder()->GetGlobalRecordTable()
100 : VarBinder()->AsETSBinder()->GetExternalRecordTable().at(programToUse);
101
102 const varbinder::BoundContext boundCtx(recordTableToUse, partialClassDef);
103 partialClassDef->SetInternalName(qualifiedClassName);
104
105 // If class prototype was created before, then we cached it's type. In that case return it.
106 // This handles cases where a Partial<T> presents in class T, because during generating T$partial we'd need the
107 // complete class T$partial which is not present at the time. Binding it's own type for it however will make it
108 // possible to resolve member references later, when the full T$partial class was created.
109 if (const auto found = NamedTypeStack().find(partialClassDef->TsType()); found != NamedTypeStack().end()) {
110 return *found;
111 }
112
113 NamedTypeStackElement ntse(this, partialClassDef->TsType());
114
115 // If class is external, put partial of it in global scope for the varbinder
116 if (!isClassDeclaredInCurrentFile) {
117 VarBinder()->Program()->GlobalScope()->InsertBinding(partialClassDef->Ident()->Name(),
118 partialClassDef->Variable());
119 }
120
121 return CreatePartialTypeClassDef(partialClassDef, classDef, typeToBePartial, recordTableToUse);
122 }
123
HandlePartialTypeNode(ir::TypeNode * const typeParamNode)124 Type *ETSChecker::HandlePartialTypeNode(ir::TypeNode *const typeParamNode)
125 {
126 auto *const bareTypeToBePartial = typeParamNode->Check(this);
127 return HandlePartialType(bareTypeToBePartial);
128 }
129
CreateNullishProperty(ir::ClassProperty * const prop,ir::ClassDefinition * const newClassDefinition)130 ir::ClassProperty *ETSChecker::CreateNullishProperty(ir::ClassProperty *const prop,
131 ir::ClassDefinition *const newClassDefinition)
132 {
133 auto *const classProp = prop->AsClassProperty();
134 auto *const propSavedValue = classProp->Value();
135
136 // Set value to nullptr to prevent cloning it (as for arrow functions that is not possible yet), we set it
137 // to 'undefined' anyway
138 classProp->SetValue(nullptr);
139 auto *const propClone = prop->Clone(Allocator(), newClassDefinition)->AsClassProperty();
140
141 // Revert original property value
142 classProp->SetValue(propSavedValue);
143 propClone->AsClassProperty()->SetValue(Allocator()->New<ir::UndefinedLiteral>());
144
145 auto *propTypeAnn = propClone->TypeAnnotation();
146 ArenaVector<ir::TypeNode *> types(Allocator()->Adapter());
147
148 // Handle implicit type annotation
149 if (propTypeAnn == nullptr) {
150 propTypeAnn = Allocator()->New<ir::OpaqueTypeNode>(classProp->TsType());
151 }
152
153 // NOTE (mmartin): Union type check fails if it contains ETSEnum type, workaround until fix
154 if ((classProp->TsType() != nullptr) && classProp->TsType()->IsETSIntEnumType()) {
155 propTypeAnn = Allocator()->New<ir::OpaqueTypeNode>(GlobalETSObjectType());
156 }
157
158 // Create new nullish type
159 types.push_back(propTypeAnn);
160 types.push_back(AllocNode<ir::ETSUndefinedType>());
161 auto *const unionType = AllocNode<ir::ETSUnionType>(std::move(types));
162 propClone->SetTypeAnnotation(unionType);
163
164 // Set new parents
165 unionType->SetParent(propClone);
166 propClone->SetParent(newClassDefinition);
167
168 // Handle bindings, variables
169 varbinder::Decl *const newDecl =
170 classProp->IsConst()
171 ? static_cast<varbinder::Decl *>(Allocator()->New<varbinder::ConstDecl>(propClone->Id()->Name()))
172 : Allocator()->New<varbinder::LetDecl>(propClone->Id()->Name());
173
174 propClone->SetVariable(Allocator()->New<varbinder::LocalVariable>(newDecl, varbinder::VariableFlags::PROPERTY));
175
176 propClone->Variable()->SetScope(classProp->IsStatic()
177 ? newClassDefinition->Scope()->AsClassScope()->StaticFieldScope()
178 : newClassDefinition->Scope()->AsClassScope()->InstanceFieldScope());
179
180 propClone->Variable()->Declaration()->BindNode(propClone);
181
182 return propClone;
183 }
184
185 template <typename T>
CloneNodeIfNotNullptr(T * node,ArenaAllocator * allocator)186 static T *CloneNodeIfNotNullptr(T *node, ArenaAllocator *allocator)
187 {
188 return node != nullptr ? node->Clone(allocator, nullptr) : nullptr;
189 }
190
CreatePartialClassDeclaration(ir::ClassDefinition * const newClassDefinition,const ir::ClassDefinition * const classDef)191 ir::ClassDefinition *ETSChecker::CreatePartialClassDeclaration(ir::ClassDefinition *const newClassDefinition,
192 const ir::ClassDefinition *const classDef)
193 {
194 // Build the new Partial class based on the 'T' type parameter of 'Partial<T>'
195
196 for (auto *const prop : classDef->Body()) {
197 // Only handle class properties (members)
198 // Method calls on partial classes will make the class not type safe, so we don't copy any methods
199 if (prop->IsClassProperty()) {
200 auto *const newProp = CreateNullishProperty(prop->AsClassProperty(), newClassDefinition);
201
202 newClassDefinition->Scope()->AddBinding(Allocator(), nullptr, newProp->Variable()->Declaration(),
203 ScriptExtension::ETS);
204
205 // Put the new property into the class declaration
206 newClassDefinition->Body().emplace_back(newProp);
207 }
208 }
209
210 newClassDefinition->SetVariable(newClassDefinition->Ident()->Variable());
211 newClassDefinition->AddModifier(static_cast<const ir::AstNode *>(classDef)->Modifiers());
212
213 if (classDef->TypeParams() != nullptr) {
214 ArenaVector<ir::TSTypeParameter *> typeParams(Allocator()->Adapter());
215 for (auto *const classDefTypeParam : classDef->TypeParams()->Params()) {
216 auto *const newTypeParam =
217 AllocNode<ir::TSTypeParameter>(CloneNodeIfNotNullptr(classDefTypeParam->Name(), Allocator()),
218 CloneNodeIfNotNullptr(classDefTypeParam->Constraint(), Allocator()),
219 CloneNodeIfNotNullptr(classDefTypeParam->DefaultType(), Allocator()));
220 typeParams.emplace_back(newTypeParam);
221 }
222
223 auto *const newTypeParams =
224 AllocNode<ir::TSTypeParameterDeclaration>(std::move(typeParams), classDef->TypeParams()->RequiredParams());
225
226 newClassDefinition->SetTypeParams(newTypeParams);
227 newTypeParams->SetParent(newClassDefinition);
228 newTypeParams->SetScope(newClassDefinition->Scope());
229 }
230
231 newClassDefinition->SetTsType(nullptr);
232
233 return newClassDefinition;
234 }
235
CreateConstructorForPartialType(ir::ClassDefinition * const partialClassDef,checker::ETSObjectType * const partialType,varbinder::RecordTable * const recordTable)236 void ETSChecker::CreateConstructorForPartialType(ir::ClassDefinition *const partialClassDef,
237 checker::ETSObjectType *const partialType,
238 varbinder::RecordTable *const recordTable)
239 {
240 // Create scopes
241 auto *const scope = partialClassDef->Scope()->AsClassScope();
242 const auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>::Enter(VarBinder(), scope);
243
244 // Create ctor
245 auto *const ctor = CreateNonStaticClassInitializer(classCtx.GetScope(), recordTable);
246 auto *const ctorFunc = ctor->Function();
247 auto *const ctorId = ctor->Function()->Id();
248
249 // Handle bindings, create method decl for ctor
250 ctorFunc->Scope()->Find(varbinder::VarBinder::MANDATORY_PARAM_THIS).variable->SetTsType(partialType);
251 partialType->AddConstructSignature(ctorFunc->Signature());
252 ctorFunc->Signature()->SetOwner(partialType);
253 ctor->SetParent(partialClassDef);
254 ctorId->SetVariable(Allocator()->New<varbinder::LocalVariable>(
255 Allocator()->New<varbinder::MethodDecl>(ctorId->Name()), varbinder::VariableFlags::METHOD));
256 ctor->Id()->SetVariable(ctorId->Variable());
257
258 // Put ctor in partial class body
259 partialClassDef->Body().emplace_back(ctor);
260 }
261
CreateClassPrototype(util::StringView name,parser::Program * const classDeclProgram)262 ir::ClassDefinition *ETSChecker::CreateClassPrototype(util::StringView name, parser::Program *const classDeclProgram)
263 {
264 const auto globalCtx =
265 varbinder::LexicalScope<varbinder::GlobalScope>::Enter(VarBinder(), classDeclProgram->GlobalScope());
266
267 // Create class name, and declaration variable
268 auto *const classId = AllocNode<ir::Identifier>(name, Allocator());
269 const auto [decl, var] = VarBinder()->NewVarDecl<varbinder::ClassDecl>(classId->Start(), classId->Name());
270 classId->SetVariable(var);
271
272 // Create class definition node
273 const auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>(VarBinder());
274 auto *const classDef =
275 AllocNode<ir::ClassDefinition>(Allocator(), classId, ir::ClassDefinitionModifiers::DECLARATION,
276 ir::ModifierFlags::NONE, Language(Language::Id::ETS));
277 classDef->SetScope(classCtx.GetScope());
278 classDef->SetVariable(var);
279
280 // Create class declaration node
281 auto *const classDecl = AllocNode<ir::ClassDeclaration>(classDef, Allocator());
282 classDecl->SetParent(classDeclProgram->Ast());
283 classDef->Scope()->BindNode(classDecl);
284 decl->BindNode(classDef);
285
286 // Put class declaration in global scope, and in program AST
287 classDeclProgram->Ast()->Statements().push_back(classDecl);
288 classDeclProgram->GlobalScope()->InsertBinding(name, var);
289
290 // Create only class 'header' (no properties and methods, but base type created)
291 BuildBasicClassProperties(classDef);
292
293 return classDef;
294 }
295
SearchNamesInMultiplePrograms(const std::set<const parser::Program * > & programs,const std::set<util::StringView> & classNamesToFind)296 varbinder::Variable *ETSChecker::SearchNamesInMultiplePrograms(const std::set<const parser::Program *> &programs,
297 const std::set<util::StringView> &classNamesToFind)
298 {
299 for (const auto *const program : programs) {
300 for (const auto &className : classNamesToFind) {
301 auto *const var = program->GlobalScope()->Find(className, varbinder::ResolveBindingOptions::ALL).variable;
302 if (var == nullptr) {
303 continue;
304 }
305
306 if (var->TsType() == nullptr) {
307 var->Declaration()->Node()->Check(this);
308 }
309
310 return var;
311 }
312 }
313
314 return nullptr;
315 }
316
GetQualifiedClassName(const parser::Program * const classDefProgram,const util::StringView className)317 util::StringView ETSChecker::GetQualifiedClassName(const parser::Program *const classDefProgram,
318 const util::StringView className)
319 {
320 auto packageName = classDefProgram->ModuleName().Mutf8();
321 if (!packageName.empty()) {
322 packageName.append(".");
323 }
324
325 return util::UString(packageName + className.Mutf8(), Allocator()).View();
326 }
327
HandleUnionForPartialType(ETSUnionType * const typeToBePartial)328 Type *ETSChecker::HandleUnionForPartialType(ETSUnionType *const typeToBePartial)
329 {
330 // Convert a union type to partial, by converting all types in it to partial, and making a new union
331 // type out of them
332 const auto *const unionTypeNode = typeToBePartial->AsETSUnionType();
333 ArenaVector<checker::Type *> newTypesForUnion(Allocator()->Adapter());
334
335 for (auto *const typeFromUnion : unionTypeNode->ConstituentTypes()) {
336 if (typeFromUnion->IsETSIntEnumType()) {
337 // NOTE (mmartin): Union type check fails if it contains ETSEnum type, workaround until fix
338 return typeFromUnion;
339 }
340
341 if ((typeFromUnion->Variable() != nullptr) && (typeFromUnion->Variable()->Declaration() != nullptr)) {
342 newTypesForUnion.emplace_back(HandlePartialType(typeFromUnion));
343 } else {
344 newTypesForUnion.emplace_back(typeFromUnion);
345 }
346 }
347
348 return CreateETSUnionType(std::move(newTypesForUnion));
349 }
350
CreatePartialTypeClassDef(ir::ClassDefinition * const partialClassDef,ir::ClassDefinition * const classDef,const Type * const typeToBePartial,varbinder::RecordTable * const recordTableToUse)351 Type *ETSChecker::CreatePartialTypeClassDef(ir::ClassDefinition *const partialClassDef,
352 ir::ClassDefinition *const classDef, const Type *const typeToBePartial,
353 varbinder::RecordTable *const recordTableToUse)
354 {
355 // Create nullish properties of the partial class
356 CreatePartialClassDeclaration(partialClassDef, classDef);
357 partialClassDef->Check(this);
358
359 auto *const partialType = partialClassDef->TsType()->AsETSObjectType();
360 partialType->SetAssemblerName(partialClassDef->InternalName());
361
362 CreateConstructorForPartialType(partialClassDef, partialType, recordTableToUse);
363
364 // Create partial type for super type
365 if (typeToBePartial != GlobalETSObjectType()) {
366 auto *const partialSuper =
367 HandlePartialType(classDef->Super() == nullptr ? GlobalETSObjectType() : classDef->Super()->TsType());
368
369 partialType->SetSuperType(partialSuper->AsETSObjectType());
370 }
371
372 return partialType;
373 }
374
CreateScriptFunctionForConstructor(varbinder::FunctionScope * const scope)375 std::pair<ir::ScriptFunction *, ir::Identifier *> ETSChecker::CreateScriptFunctionForConstructor(
376 varbinder::FunctionScope *const scope)
377 {
378 ArenaVector<ir::Statement *> statements(Allocator()->Adapter());
379 ArenaVector<ir::Expression *> params(Allocator()->Adapter());
380
381 ir::ScriptFunction *func {};
382 ir::Identifier *id {};
383
384 auto *const body = AllocNode<ir::BlockStatement>(Allocator(), std::move(statements));
385 body->SetScope(scope);
386 id = AllocNode<ir::Identifier>(util::UString(std::string("constructor"), Allocator()).View(), Allocator());
387 auto funcSignature = ir::FunctionSignature(nullptr, std::move(params), nullptr);
388 func = AllocNode<ir::ScriptFunction>(Allocator(),
389 ir::ScriptFunction::ScriptFunctionData {
390 body, std::move(funcSignature),
391 ir::ScriptFunctionFlags::CONSTRUCTOR | ir::ScriptFunctionFlags::EXPRESSION,
392 ir::ModifierFlags::PUBLIC});
393
394 func->SetScope(scope);
395 scope->BindNode(func);
396 func->SetIdent(id);
397 VarBinder()->AsETSBinder()->AddFunctionThisParam(func);
398
399 return std::make_pair(func, id);
400 }
401
CreateNonStaticClassInitializer(varbinder::ClassScope * classScope,varbinder::RecordTable * const recordTable)402 ir::MethodDefinition *ETSChecker::CreateNonStaticClassInitializer(varbinder::ClassScope *classScope,
403 varbinder::RecordTable *const recordTable)
404 {
405 const auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>::Enter(VarBinder(), classScope);
406
407 auto *paramScope = Allocator()->New<varbinder::FunctionParamScope>(Allocator(), classScope);
408 auto *const functionScope = Allocator()->New<varbinder::FunctionScope>(Allocator(), paramScope);
409 functionScope->BindParamScope(paramScope);
410 paramScope->BindFunctionScope(functionScope);
411
412 const auto funcParamCtx = varbinder::LexicalScope<varbinder::FunctionParamScope>::Enter(VarBinder(), paramScope);
413
414 auto [func, id] = CreateScriptFunctionForConstructor(functionScope);
415
416 paramScope->BindNode(func);
417 functionScope->BindNode(func);
418
419 auto *const signatureInfo = CreateSignatureInfo();
420 auto *const signature = CreateSignature(signatureInfo, GlobalVoidType(), func);
421 func->SetSignature(signature);
422
423 VarBinder()->AsETSBinder()->BuildInternalNameWithCustomRecordTable(func, recordTable);
424 VarBinder()->AsETSBinder()->BuildFunctionName(func);
425 VarBinder()->Functions().push_back(functionScope);
426
427 auto *funcExpr = AllocNode<ir::FunctionExpression>(func);
428 auto *const ctor = AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::CONSTRUCTOR,
429 id->Clone(Allocator(), classScope->Node()), funcExpr,
430 ir::ModifierFlags::NONE, Allocator(), false);
431
432 auto *const funcType = CreateETSFunctionType(signature, id->Name());
433 ctor->SetTsType(funcType);
434
435 return ctor;
436 }
437
438 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
439 // Readonly utility type
440 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
HandleReadonlyType(const ir::TSTypeParameterInstantiation * const typeParams)441 Type *ETSChecker::HandleReadonlyType(const ir::TSTypeParameterInstantiation *const typeParams)
442 {
443 auto *const typeParamNode = GetUtilityTypeTypeParamNode(typeParams, compiler::Signatures::READONLY_TYPE_NAME);
444 auto *typeToBeReadonly = typeParamNode->Check(this);
445
446 if (auto found = NamedTypeStack().find(typeToBeReadonly); found != NamedTypeStack().end()) {
447 return *found;
448 }
449
450 NamedTypeStackElement ntse(this, typeToBeReadonly);
451 return GetReadonlyType(typeToBeReadonly);
452 }
453
GetReadonlyType(Type * type)454 Type *ETSChecker::GetReadonlyType(Type *type)
455 {
456 if (const auto found = NamedTypeStack().find(type); found != NamedTypeStack().end()) {
457 return *found;
458 }
459
460 NamedTypeStackElement ntse(this, type);
461
462 if (type->IsETSObjectType()) {
463 type->AsETSObjectType()->InstanceFields();
464 auto *clonedType = type->Clone(this)->AsETSObjectType();
465 MakePropertiesReadonly(clonedType);
466 return clonedType;
467 }
468 if (type->IsETSTypeParameter()) {
469 return Allocator()->New<ETSReadonlyType>(type->AsETSTypeParameter());
470 }
471 if (type->IsETSUnionType()) {
472 ArenaVector<Type *> unionTypes(Allocator()->Adapter());
473 for (auto *t : type->AsETSUnionType()->ConstituentTypes()) {
474 unionTypes.emplace_back(t->IsETSObjectType() ? GetReadonlyType(t) : t->Clone(this));
475 }
476 return CreateETSUnionType(std::move(unionTypes));
477 }
478 return type;
479 }
480
MakePropertiesReadonly(ETSObjectType * const classType)481 void ETSChecker::MakePropertiesReadonly(ETSObjectType *const classType)
482 {
483 classType->UpdateTypeProperties(this, [this](auto *property, auto *propType) {
484 auto *newDecl = Allocator()->New<varbinder::ReadonlyDecl>(property->Name(), property->Declaration()->Node());
485 auto *const propCopy = property->Copy(Allocator(), newDecl);
486 propCopy->AddFlag(varbinder::VariableFlags::READONLY);
487 propCopy->SetTsType(propType);
488 return propCopy;
489 });
490 }
491
492 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
493 // Required utility type
494 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
HandleRequiredType(Type * typeToBeRequired)495 Type *ETSChecker::HandleRequiredType(Type *typeToBeRequired)
496 {
497 if (const auto found = NamedTypeStack().find(typeToBeRequired); found != NamedTypeStack().end()) {
498 return *found;
499 }
500
501 NamedTypeStackElement ntse(this, typeToBeRequired);
502
503 if (typeToBeRequired->IsETSTypeParameter()) {
504 auto *const requiredClone = typeToBeRequired->Clone(this);
505 requiredClone->AddTypeFlag(TypeFlag::ETS_REQUIRED_TYPE_PARAMETER);
506 return requiredClone;
507 }
508
509 if (typeToBeRequired->IsETSUnionType()) {
510 ArenaVector<Type *> unionTypes(Allocator()->Adapter());
511 for (auto *type : typeToBeRequired->AsETSUnionType()->ConstituentTypes()) {
512 if (type->IsETSObjectType()) {
513 type = type->Clone(this);
514 MakePropertiesNonNullish(type->AsETSObjectType());
515 }
516
517 if (type->IsETSNullType() || type->IsETSUndefinedType()) {
518 continue;
519 }
520
521 unionTypes.emplace_back(type);
522 }
523
524 return CreateETSUnionType(std::move(unionTypes));
525 }
526
527 if (typeToBeRequired->IsETSObjectType()) {
528 typeToBeRequired->AsETSObjectType()->InstanceFields(); // call to instantiate properties
529 }
530
531 typeToBeRequired = typeToBeRequired->Clone(this);
532
533 MakePropertiesNonNullish(typeToBeRequired->AsETSObjectType());
534
535 return typeToBeRequired;
536 }
537
HandleRequiredTypeNode(ir::TypeNode * const typeParamNode)538 Type *ETSChecker::HandleRequiredTypeNode(ir::TypeNode *const typeParamNode)
539 {
540 auto *const bareTypeToBeRequired = typeParamNode->Check(this);
541 return HandleRequiredType(bareTypeToBeRequired);
542 }
543
MakePropertiesNonNullish(ETSObjectType * const classType)544 void ETSChecker::MakePropertiesNonNullish(ETSObjectType *const classType)
545 {
546 classType->AddObjectFlag(ETSObjectFlags::REQUIRED);
547 classType->InstanceFields();
548
549 for (const auto &[_, propVar] : classType->InstanceFields()) {
550 MakePropertyNonNullish<PropertyType::INSTANCE_FIELD>(classType, propVar);
551 }
552
553 for (const auto &[_, propVar] : classType->StaticFields()) {
554 MakePropertyNonNullish<PropertyType::STATIC_FIELD>(classType, propVar);
555 }
556
557 if (classType->SuperType() != nullptr) {
558 auto *const superRequired = classType->SuperType()->Clone(this)->AsETSObjectType();
559 MakePropertiesNonNullish(superRequired);
560 classType->SetSuperType(superRequired);
561 }
562 }
563
564 template <PropertyType PROP_TYPE>
MakePropertyNonNullish(ETSObjectType * const classType,varbinder::LocalVariable * const prop)565 void ETSChecker::MakePropertyNonNullish(ETSObjectType *const classType, varbinder::LocalVariable *const prop)
566 {
567 auto *const propType = prop->TsType();
568 auto *const nonNullishPropType = GetNonNullishType(propType);
569
570 auto *const propCopy = prop->Copy(Allocator(), prop->Declaration());
571
572 propCopy->SetTsType(nonNullishPropType);
573 classType->RemoveProperty<PROP_TYPE>(prop);
574 classType->AddProperty<PROP_TYPE>(propCopy);
575 }
576
StringEqualsPropertyName(const util::StringView pname1,const ir::Expression * const prop2Key)577 static bool StringEqualsPropertyName(const util::StringView pname1, const ir::Expression *const prop2Key)
578 {
579 util::StringView pname2;
580 if (prop2Key->IsStringLiteral()) {
581 pname2 = prop2Key->AsStringLiteral()->Str();
582 } else if (prop2Key->IsIdentifier()) {
583 pname2 = prop2Key->AsIdentifier()->Name();
584 }
585
586 return pname1.Is(pname2.Mutf8());
587 }
588
ValidateObjectLiteralForRequiredType(const ETSObjectType * const requiredType,const ir::ObjectExpression * const initObjExpr)589 void ETSChecker::ValidateObjectLiteralForRequiredType(const ETSObjectType *const requiredType,
590 const ir::ObjectExpression *const initObjExpr)
591 {
592 const std::size_t classPropertyCount = requiredType->InstanceFields().size() + requiredType->StaticFields().size();
593
594 auto initObjExprContainsField = [&initObjExpr](const util::StringView pname1) {
595 return std::find_if(initObjExpr->Properties().begin(), initObjExpr->Properties().end(),
596 [&pname1](const ir::Expression *const initProp) {
597 return StringEqualsPropertyName(pname1, initProp->AsProperty()->Key());
598 }) != initObjExpr->Properties().end();
599 };
600
601 if (classPropertyCount > initObjExpr->Properties().size()) {
602 std::string_view missingProp;
603
604 for (const auto &[propName, _] : requiredType->InstanceFields()) {
605 if (!initObjExprContainsField(propName)) {
606 missingProp = propName.Utf8();
607 }
608 }
609
610 for (const auto &[propName, _] : requiredType->StaticFields()) {
611 if (!initObjExprContainsField(propName)) {
612 missingProp = propName.Utf8();
613 }
614 }
615
616 if (!missingProp.empty()) {
617 ThrowTypeError({"Class property '", missingProp, "' needs to be initialized for required type."},
618 initObjExpr->Start());
619 }
620 }
621 }
622 } // namespace ark::es2panda::checker
623