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 "interfaceObjectLiteralLowering.h" 17#include "checker/ETSchecker.h" 18#include "checker/ets/typeRelationContext.h" 19#include "ir/expressions/assignmentExpression.h" 20#include "util/helpers.h" 21 22namespace ark::es2panda::compiler { 23 24std::string_view InterfaceObjectLiteralLowering::Name() const 25{ 26 return "InterfaceObjectLiteralLowering"; 27} 28 29static inline bool IsInterfaceType(const checker::Type *type) 30{ 31 return type != nullptr && type->IsETSObjectType() && 32 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::INTERFACE) && 33 !type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC); 34} 35 36static ir::AstNode *CreateAnonClassImplCtor(checker::ETSChecker *checker) 37{ 38 checker::ETSChecker::ClassInitializerBuilder initBuilder = 39 [checker]([[maybe_unused]] ArenaVector<ir::Statement *> *statements, 40 [[maybe_unused]] ArenaVector<ir::Expression *> *params) { 41 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr); 42 }; 43 44 return checker->CreateClassInstanceInitializer(initBuilder); 45} 46 47static ir::ClassProperty *CreateAnonClassField(ir::MethodDefinition *ifaceMethod, checker::ETSChecker *checker) 48{ 49 auto *const allocator = checker->Allocator(); 50 51 // Field type annotation 52 auto *fieldType = ifaceMethod->Function()->Signature()->ReturnType(); 53 if (IsInterfaceType(fieldType)) { 54 auto *anonClass = fieldType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration()->GetAnonClass(); 55 ASSERT(anonClass != nullptr); 56 fieldType = anonClass->Definition()->TsType(); 57 } 58 ASSERT(fieldType != nullptr); 59 auto *fieldTypeNode = checker->AllocNode<ir::OpaqueTypeNode>(fieldType); 60 61 // Field identifier 62 util::UString fieldName(std::string("_"), allocator); 63 fieldName.Append(ifaceMethod->Id()->Name()); 64 auto *fieldId = checker->AllocNode<ir::Identifier>(fieldName.View(), nullptr, allocator); 65 66 // Field modifiers flags 67 ir::ModifierFlags fieldMF = ir::ModifierFlags::PRIVATE; 68 69 // No overloads means no setter function with the same name, so the field is readonly 70 if (ifaceMethod->Overloads().empty()) { 71 fieldMF |= ir::ModifierFlags::READONLY; 72 } 73 74 // Create synthetic class property node 75 auto *field = checker->AllocNode<ir::ClassProperty>(fieldId, nullptr, fieldTypeNode->Clone(allocator, nullptr), 76 fieldMF, allocator, false); 77 field->SetRange(ifaceMethod->Range()); 78 79 return field; 80} 81 82static ir::MethodDefinition *CreateAnonClassFieldGetterSetter(checker::ETSChecker *checker, 83 ir::MethodDefinition *ifaceMethod, bool isSetter) 84{ 85 checker::ETSChecker::MethodBuilder methodBuilder = [checker, ifaceMethod, 86 isSetter](ArenaVector<ir::Statement *> *statements, 87 ArenaVector<ir::Expression *> *params, 88 checker::Type **returnType) { 89 auto *const allocator = checker->Allocator(); 90 91 // Adding mandatory 'this' parameter 92 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr); 93 94 // ifaceMethod is getter, so it should have return type 95 auto *retType = ifaceMethod->Function()->Signature()->ReturnType(); 96 if (IsInterfaceType(retType)) { 97 auto *anonClass = retType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration()->GetAnonClass(); 98 ASSERT(anonClass != nullptr); 99 retType = anonClass->Definition()->TsType(); 100 } 101 ASSERT(retType != nullptr); 102 103 // Field identifier 104 util::UString fieldName(std::string("_"), allocator); 105 fieldName.Append(ifaceMethod->Id()->Name()); 106 auto *fieldId = checker->AllocNode<ir::Identifier>(fieldName.View(), nullptr, allocator); 107 108 if (isSetter) { 109 // Setter call params 110 ir::ETSParameterExpression *param = 111 checker->AddParam(ifaceMethod->Id()->Name(), checker->AllocNode<ir::OpaqueTypeNode>(retType)); 112 params->push_back(param); 113 114 // Setter body: 115 // this.<fieldName> = <callParam>; 116 auto *thisExpr = checker->AllocNode<ir::ThisExpression>(); 117 auto *lhs = checker->AllocNode<ir::MemberExpression>( 118 thisExpr, fieldId->Clone(allocator, nullptr), ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); 119 auto *rhs = param->Ident()->Clone(allocator, nullptr); 120 121 auto *assignment = 122 checker->AllocNode<ir::AssignmentExpression>(lhs, rhs, lexer::TokenType::PUNCTUATOR_SUBSTITUTION); 123 auto *statement = checker->AllocNode<ir::ExpressionStatement>(assignment); 124 statements->push_back(statement); 125 126 // Setter return type 127 *returnType = checker->GlobalVoidType(); 128 } else { 129 // Getter call params are empty 130 131 // Getter body: 132 // Just return this.<fieldName>; 133 auto *thisExpr = checker->AllocNode<ir::ThisExpression>(); 134 auto *argument = checker->AllocNode<ir::MemberExpression>( 135 thisExpr, fieldId->Clone(allocator, nullptr), ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); 136 137 auto *statement = checker->AllocNode<ir::ReturnStatement>(argument); 138 statements->push_back(statement); 139 140 // Getter return type 141 *returnType = retType; 142 } 143 }; 144 145 ir::ModifierFlags modifierFlags = ir::ModifierFlags::PUBLIC; 146 modifierFlags |= isSetter ? ir::ModifierFlags::SETTER : ir::ModifierFlags::GETTER; 147 ir::ScriptFunctionFlags funcFlags = ir::ScriptFunctionFlags::METHOD; 148 funcFlags |= isSetter ? ir::ScriptFunctionFlags::SETTER : ir::ScriptFunctionFlags::GETTER; 149 150 return checker->CreateClassMethod(ifaceMethod->Id()->Name().Utf8(), funcFlags, modifierFlags, methodBuilder); 151} 152 153static void FillClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody, 154 const ArenaVector<ir::AstNode *> &ifaceBody, ir::ObjectExpression *objExpr) 155{ 156 for (auto *it : ifaceBody) { 157 ASSERT(it->IsMethodDefinition()); 158 auto *ifaceMethod = it->AsMethodDefinition(); 159 160 if (!ifaceMethod->Function()->IsGetter() && !ifaceMethod->Function()->IsSetter()) { 161 checker->ThrowTypeError("Interface has methods", objExpr->Start()); 162 } 163 164 if (!ifaceMethod->Function()->IsGetter()) { 165 continue; 166 } 167 168 auto *field = CreateAnonClassField(ifaceMethod, checker); 169 classBody->push_back(field); 170 171 auto *getter = CreateAnonClassFieldGetterSetter(checker, ifaceMethod, false); 172 classBody->push_back(getter); 173 174 if (ifaceMethod->Overloads().size() == 1 && ifaceMethod->Overloads()[0]->Function()->IsSetter()) { 175 auto *setter = CreateAnonClassFieldGetterSetter(checker, ifaceMethod, true); 176 classBody->push_back(setter); 177 } 178 } 179} 180 181static void FillAnonClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody, 182 ir::TSInterfaceDeclaration *ifaceNode, ir::ObjectExpression *objExpr) 183{ 184 for (auto *extendedIface : ifaceNode->TsType()->AsETSObjectType()->Interfaces()) { 185 auto extendedIfaceBody = extendedIface->GetDeclNode()->AsTSInterfaceDeclaration()->Body()->Body(); 186 FillClassBody(checker, classBody, extendedIfaceBody, objExpr); 187 } 188 189 FillClassBody(checker, classBody, ifaceNode->Body()->Body(), objExpr); 190} 191 192static checker::ETSObjectType *GenerateAnonClassTypeFromInterface(checker::ETSChecker *checker, 193 ir::TSInterfaceDeclaration *ifaceNode, 194 ir::ObjectExpression *objExpr) 195{ 196 if (ifaceNode->GetAnonClass() != nullptr) { 197 return ifaceNode->GetAnonClass()->Definition()->TsType()->AsETSObjectType(); 198 } 199 200 auto classBodyBuilder = [checker, ifaceNode, objExpr](ArenaVector<ir::AstNode *> *classBody) { 201 if (ifaceNode->TsType() == nullptr) { 202 ifaceNode->Check(checker); 203 } 204 205 FillAnonClassBody(checker, classBody, ifaceNode, objExpr); 206 classBody->push_back(CreateAnonClassImplCtor(checker)); 207 }; 208 209 util::UString className(util::StringView("$anonymous_class$"), checker->Allocator()); 210 className.Append(ifaceNode->Id()->Name()); 211 auto *classDecl = checker->BuildClass(className.View(), classBodyBuilder); 212 auto *classDef = classDecl->Definition(); 213 auto *classType = classDef->TsType()->AsETSObjectType(); 214 215 // Class type params 216 if (ifaceNode->TypeParams() != nullptr) { 217 // NOTE: to be done 218 checker->ThrowTypeError("Object literal cannot be of typed interface type", objExpr->Start()); 219 } 220 221 // Class implements 222 auto *classImplements = 223 checker->AllocNode<ir::TSClassImplements>(checker->AllocNode<ir::OpaqueTypeNode>(ifaceNode->TsType())); 224 classImplements->SetParent(classDef); 225 classDef->Implements().emplace_back(classImplements); 226 classType->RemoveObjectFlag(checker::ETSObjectFlags::RESOLVED_INTERFACES); 227 checker->GetInterfacesOfClass(classType); 228 229 ifaceNode->SetAnonClass(classDecl); 230 return classType; 231} 232 233static void HandleInterfaceLowering(checker::ETSChecker *checker, ir::ObjectExpression *objExpr) 234{ 235 const auto *const targetType = objExpr->TsType(); 236 ASSERT(targetType->AsETSObjectType()->GetDeclNode()->IsTSInterfaceDeclaration()); 237 auto *ifaceNode = targetType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration(); 238 auto *resultType = GenerateAnonClassTypeFromInterface(checker, ifaceNode, objExpr); 239 240 if (const auto *const parent = objExpr->Parent(); parent->IsArrayExpression()) { 241 for (auto *elem : parent->AsArrayExpression()->Elements()) { 242 if (!elem->IsObjectExpression()) { 243 continue; 244 } 245 // Adjusting ts types of other object literals in array 246 elem->AsObjectExpression()->SetTsType(resultType); 247 } 248 } 249 objExpr->SetTsType(resultType); 250} 251 252bool InterfaceObjectLiteralLowering::Perform(public_lib::Context *ctx, parser::Program *program) 253{ 254 for (auto &[_, extPrograms] : program->ExternalSources()) { 255 (void)_; 256 for (auto *extProg : extPrograms) { 257 Perform(ctx, extProg); 258 } 259 } 260 261 auto *checker = ctx->checker->AsETSChecker(); 262 263 program->Ast()->IterateRecursivelyPostorder([checker](ir::AstNode *ast) -> void { 264 if (ast->IsObjectExpression() && IsInterfaceType(ast->AsObjectExpression()->TsType())) { 265 HandleInterfaceLowering(checker, ast->AsObjectExpression()); 266 } 267 }); 268 269 return true; 270} 271 272bool InterfaceObjectLiteralLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program) 273{ 274 for (auto &[_, extPrograms] : program->ExternalSources()) { 275 (void)_; 276 for (auto *extProg : extPrograms) { 277 if (!Postcondition(ctx, extProg)) { 278 return false; 279 } 280 } 281 } 282 283 return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) -> bool { 284 return ast->IsObjectExpression() && IsInterfaceType(ast->AsObjectExpression()->TsType()); 285 }); 286} 287} // namespace ark::es2panda::compiler 288