1/* 2 * Copyright (c) 2021-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 "unionLowering.h" 17#include "varbinder/variableFlags.h" 18#include "varbinder/ETSBinder.h" 19#include "checker/ETSchecker.h" 20#include "checker/ets/conversion.h" 21#include "checker/ets/boxingConverter.h" 22#include "checker/ets/unboxingConverter.h" 23#include "compiler/lowering/util.h" 24#include "compiler/lowering/scopesInit/scopesInitPhase.h" 25#include "ir/base/classDefinition.h" 26#include "ir/base/classProperty.h" 27#include "ir/astNode.h" 28#include "ir/expression.h" 29#include "ir/opaqueTypeNode.h" 30#include "ir/expressions/literals/nullLiteral.h" 31#include "ir/expressions/literals/undefinedLiteral.h" 32#include "ir/expressions/binaryExpression.h" 33#include "ir/expressions/identifier.h" 34#include "ir/expressions/memberExpression.h" 35#include "ir/statements/blockStatement.h" 36#include "ir/statements/classDeclaration.h" 37#include "ir/statements/variableDeclaration.h" 38#include "ir/ts/tsAsExpression.h" 39#include "type_helper.h" 40#include "public/public.h" 41 42namespace ark::es2panda::compiler { 43static ir::ClassDefinition *GetUnionFieldClass(checker::ETSChecker *checker, varbinder::VarBinder *varbinder) 44{ 45 // Create the name for the synthetic class node 46 util::UString unionFieldClassName(util::StringView(panda_file::GetDummyClassName()), checker->Allocator()); 47 varbinder::Variable *foundVar = nullptr; 48 if ((foundVar = checker->Scope()->FindLocal(unionFieldClassName.View(), 49 varbinder::ResolveBindingOptions::BINDINGS)) != nullptr) { 50 return foundVar->Declaration()->Node()->AsClassDeclaration()->Definition(); 51 } 52 auto *ident = checker->AllocNode<ir::Identifier>(unionFieldClassName.View(), checker->Allocator()); 53 auto [decl, var] = varbinder->NewVarDecl<varbinder::ClassDecl>(ident->Start(), ident->Name()); 54 ident->SetVariable(var); 55 56 auto classCtx = varbinder::LexicalScope<varbinder::ClassScope>(varbinder); 57 auto *classDef = 58 checker->AllocNode<ir::ClassDefinition>(checker->Allocator(), ident, ir::ClassDefinitionModifiers::GLOBAL, 59 ir::ModifierFlags::FINAL, Language(Language::Id::ETS)); 60 classDef->SetScope(classCtx.GetScope()); 61 auto *classDecl = checker->AllocNode<ir::ClassDeclaration>(classDef, checker->Allocator()); 62 classDef->Scope()->BindNode(classDecl); 63 classDef->SetTsType(checker->GlobalETSObjectType()); 64 decl->BindNode(classDecl); 65 var->SetScope(classDef->Scope()); 66 67 varbinder->AsETSBinder()->BuildClassDefinition(classDef); 68 return classDef; 69} 70 71static varbinder::LocalVariable *CreateUnionFieldClassProperty(checker::ETSChecker *checker, 72 varbinder::VarBinder *varbinder, 73 checker::Type *fieldType, 74 const util::StringView &propName) 75{ 76 auto *const allocator = checker->Allocator(); 77 auto *const dummyClass = GetUnionFieldClass(checker, varbinder); 78 auto *classScope = dummyClass->Scope()->AsClassScope(); 79 80 // Enter the union filed class instance field scope 81 auto fieldCtx = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varbinder, classScope->InstanceFieldScope()); 82 83 if (auto *var = classScope->FindLocal(propName, varbinder::ResolveBindingOptions::VARIABLES); var != nullptr) { 84 return var->AsLocalVariable(); 85 } 86 87 // Create field name for synthetic class 88 auto *fieldIdent = checker->AllocNode<ir::Identifier>(propName, allocator); 89 90 // Create the synthetic class property node 91 auto *field = 92 checker->AllocNode<ir::ClassProperty>(fieldIdent, nullptr, nullptr, ir::ModifierFlags::NONE, allocator, false); 93 94 // Add the declaration to the scope 95 auto [decl, var] = varbinder->NewVarDecl<varbinder::LetDecl>(fieldIdent->Start(), fieldIdent->Name()); 96 var->AddFlag(varbinder::VariableFlags::PROPERTY); 97 var->SetTsType(fieldType); 98 fieldIdent->SetVariable(var); 99 field->SetTsType(fieldType); 100 decl->BindNode(field); 101 102 ArenaVector<ir::AstNode *> fieldDecl {allocator->Adapter()}; 103 fieldDecl.push_back(field); 104 dummyClass->AddProperties(std::move(fieldDecl)); 105 return var->AsLocalVariable(); 106} 107 108static void HandleUnionPropertyAccess(checker::ETSChecker *checker, varbinder::VarBinder *vbind, 109 ir::MemberExpression *expr) 110{ 111 if (expr->PropVar() != nullptr) { 112 return; 113 } 114 [[maybe_unused]] auto parent = expr->Parent(); 115 ASSERT(!(parent->IsCallExpression() && parent->AsCallExpression()->Callee() == expr && 116 parent->AsCallExpression()->Signature()->HasSignatureFlag(checker::SignatureFlags::TYPE))); 117 expr->SetPropVar( 118 CreateUnionFieldClassProperty(checker, vbind, expr->TsType(), expr->Property()->AsIdentifier()->Name())); 119 ASSERT(expr->PropVar() != nullptr); 120} 121 122static ir::TSAsExpression *GenAsExpression(checker::ETSChecker *checker, checker::Type *const opaqueType, 123 ir::Expression *const node, ir::AstNode *const parent) 124{ 125 auto *const typeNode = checker->AllocNode<ir::OpaqueTypeNode>(opaqueType); 126 auto *const asExpression = checker->AllocNode<ir::TSAsExpression>(node, typeNode, false); 127 asExpression->SetParent(parent); 128 asExpression->Check(checker); 129 return asExpression; 130} 131 132/* 133 * Function that generates conversion from (union) to (primitive) type as to `as` expressions: 134 * (union) as (prim) => ((union) as (ref)) as (prim), 135 * where (ref) is some unboxable type from union constituent types. 136 * Finally, `(union) as (prim)` expression replaces union_node that came above. 137 */ 138static ir::TSAsExpression *UnionCastToPrimitive(checker::ETSChecker *checker, checker::ETSObjectType *unboxableRef, 139 checker::Type *unboxedPrim, ir::Expression *unionNode) 140{ 141 auto *const unionAsRefExpression = GenAsExpression(checker, unboxableRef, unionNode, nullptr); 142 return GenAsExpression(checker, unboxedPrim, unionAsRefExpression, unionNode->Parent()); 143} 144 145static ir::TSAsExpression *HandleUnionCastToPrimitive(checker::ETSChecker *checker, ir::TSAsExpression *expr) 146{ 147 auto *const unionType = expr->Expr()->TsType()->AsETSUnionType(); 148 auto *sourceType = unionType->FindExactOrBoxedType(checker, expr->TsType()); 149 if (sourceType == nullptr) { 150 sourceType = unionType->AsETSUnionType()->FindTypeIsCastableToSomeType(expr->Expr(), checker->Relation(), 151 expr->TsType()); 152 } 153 if (sourceType != nullptr && expr->Expr()->GetBoxingUnboxingFlags() != ir::BoxingUnboxingFlags::NONE) { 154 if (expr->TsType()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) { 155 auto *const asExpr = GenAsExpression(checker, sourceType, expr->Expr(), expr); 156 asExpr->SetBoxingUnboxingFlags( 157 checker->GetUnboxingFlag(checker->ETSBuiltinTypeAsPrimitiveType(sourceType))); 158 expr->Expr()->SetBoxingUnboxingFlags(ir::BoxingUnboxingFlags::NONE); 159 expr->SetExpr(asExpr); 160 } 161 return expr; 162 } 163 auto *const unboxableUnionType = sourceType != nullptr ? sourceType : unionType->FindUnboxableType(); 164 auto *const unboxedUnionType = checker->ETSBuiltinTypeAsPrimitiveType(unboxableUnionType); 165 auto *const node = 166 UnionCastToPrimitive(checker, unboxableUnionType->AsETSObjectType(), unboxedUnionType, expr->Expr()); 167 node->SetParent(expr->Parent()); 168 169 return node; 170} 171 172bool UnionLowering::Perform(public_lib::Context *ctx, parser::Program *program) 173{ 174 for (auto &[_, ext_programs] : program->ExternalSources()) { 175 (void)_; 176 for (auto *extProg : ext_programs) { 177 Perform(ctx, extProg); 178 } 179 } 180 181 checker::ETSChecker *checker = ctx->checker->AsETSChecker(); 182 183 program->Ast()->TransformChildrenRecursively( 184 [checker](ir::AstNode *ast) -> ir::AstNode * { 185 if (ast->IsMemberExpression() && ast->AsMemberExpression()->Object()->TsType() != nullptr) { 186 auto *objType = 187 checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType())); 188 if (objType->IsETSUnionType()) { 189 HandleUnionPropertyAccess(checker, checker->VarBinder(), ast->AsMemberExpression()); 190 return ast; 191 } 192 } 193 194 if (ast->IsTSAsExpression() && ast->AsTSAsExpression()->Expr()->TsType() != nullptr && 195 ast->AsTSAsExpression()->Expr()->TsType()->IsETSUnionType() && 196 ast->AsTSAsExpression()->TsType() != nullptr && 197 ast->AsTSAsExpression()->TsType()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) { 198 return HandleUnionCastToPrimitive(checker, ast->AsTSAsExpression()); 199 } 200 201 return ast; 202 }, 203 Name()); 204 205 return true; 206} 207 208bool UnionLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program) 209{ 210 bool current = !program->Ast()->IsAnyChild([checker = ctx->checker->AsETSChecker()](ir::AstNode *ast) { 211 if (!ast->IsMemberExpression() || ast->AsMemberExpression()->Object()->TsType() == nullptr) { 212 return false; 213 } 214 auto *objType = 215 checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType())); 216 auto *parent = ast->Parent(); 217 if (!(parent->IsCallExpression() && 218 parent->AsCallExpression()->Signature()->HasSignatureFlag(checker::SignatureFlags::TYPE))) { 219 return false; 220 } 221 return objType->IsETSUnionType() && ast->AsMemberExpression()->PropVar() == nullptr; 222 }); 223 if (!current || ctx->config->options->CompilerOptions().compilationMode != CompilationMode::GEN_STD_LIB) { 224 return current; 225 } 226 227 for (auto &[_, ext_programs] : program->ExternalSources()) { 228 (void)_; 229 for (auto *extProg : ext_programs) { 230 if (!Postcondition(ctx, extProg)) { 231 return false; 232 } 233 } 234 } 235 return true; 236} 237 238} // namespace ark::es2panda::compiler 239