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