* Copyright (c) 2021-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "opAssignment.h"
#include "parser/ETSparser.h"
#include "varbinder/ETSBinder.h"
#include "checker/ETSchecker.h"
#include "compiler/lowering/util.h"
#include "compiler/lowering/scopesInit/scopesInitPhase.h"
#include "ir/opaqueTypeNode.h"
#include "ir/expressions/assignmentExpression.h"
#include "ir/expressions/identifier.h"
#include "ir/expressions/memberExpression.h"
#include "ir/expressions/blockExpression.h"
#include "ir/statements/blockStatement.h"
#include "ir/statements/expressionStatement.h"
#include "util/helpers.h"
namespace ark::es2panda::compiler {
static constexpr size_t TEMP_VARIABLE_PAIR_INDEX_INCREMENT = 2;
struct Conversion {
lexer::TokenType from;
lexer::TokenType to;
};
static constexpr std::array<Conversion, 18> OP_TRANSLATION {{
{lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT},
{lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT},
{lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_LEFT_SHIFT},
{lexer::TokenType::PUNCTUATOR_PLUS_EQUAL, lexer::TokenType::PUNCTUATOR_PLUS},
{lexer::TokenType::PUNCTUATOR_MINUS_EQUAL, lexer::TokenType::PUNCTUATOR_MINUS},
{lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL, lexer::TokenType::PUNCTUATOR_MULTIPLY},
{lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL, lexer::TokenType::PUNCTUATOR_DIVIDE},
{lexer::TokenType::PUNCTUATOR_MOD_EQUAL, lexer::TokenType::PUNCTUATOR_MOD},
{lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_AND},
{lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_OR},
{lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_XOR},
{lexer::TokenType::PUNCTUATOR_LOGICAL_AND_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_AND},
{lexer::TokenType::PUNCTUATOR_LOGICAL_OR_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_OR},
{lexer::TokenType::PUNCTUATOR_LOGICAL_NULLISH_EQUAL, lexer::TokenType::PUNCTUATOR_NULLISH_COALESCING},
{lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL, lexer::TokenType::PUNCTUATOR_EXPONENTIATION},
{lexer::TokenType::PUNCTUATOR_PLUS_PLUS, lexer::TokenType::PUNCTUATOR_PLUS},
{lexer::TokenType::PUNCTUATOR_MINUS_MINUS, lexer::TokenType::PUNCTUATOR_MINUS},
}};
static lexer::TokenType CombinedOpToOp(const lexer::TokenType combinedOp)
{
for (const auto &conv : OP_TRANSLATION) {
if (conv.from == combinedOp) {
return conv.to;
}
}
ES2PANDA_UNREACHABLE();
}
static ir::OpaqueTypeNode *CreateProxyTypeNode(checker::ETSChecker *checker, ir::Expression *expr)
{
auto *lcType = expr->TsType();
if (checker->IsExtensionETSFunctionType(lcType) && expr->IsMemberExpression() &&
expr->AsMemberExpression()->HasMemberKind(ir::MemberExpressionKind::EXTENSION_ACCESSOR)) {
lcType = expr->AsMemberExpression()->ExtensionAccessorType();
}
return checker->AllocNode<ir::OpaqueTypeNode>(lcType, checker->Allocator());
}
static std::string GenFormatForExpression(ir::Expression *expr, size_t ix1, size_t ix2)
{
std::string res = "@@I" + std::to_string(ix1);
if (expr->IsTSNonNullExpression()) {
expr = expr->AsTSNonNullExpression()->Expr();
}
if (expr->IsMemberExpression()) {
auto const kind = expr->AsMemberExpression()->Kind();
if ((kind & ir::MemberExpressionKind::PROPERTY_ACCESS) != 0) {
res += ".@@I" + std::to_string(ix2);
} else if (kind == ir::MemberExpressionKind::ELEMENT_ACCESS) {
res += "[@@E" + std::to_string(ix2) + "]";
}
}
return res;
}
static ir::Expression *GetClone(ArenaAllocator *allocator, ir::Expression *node)
{
return node == nullptr ? nullptr : node->Clone(allocator, nullptr)->AsExpression();
}
static std::string GetFormatPlaceholder(const ir::Expression *expr, const size_t counter)
{
if (expr->IsIdentifier()) {
return "@@I" + std::to_string(counter);
}
return "@@E" + std::to_string(counter);
}
static std::string UpdateStatementToAccessPropertyOrElement(const ir::MemberExpression *expr, std::string statement)
{
if (auto const kind = expr->Kind();
kind != ir::MemberExpressionKind::NONE && kind != ir::MemberExpressionKind::ELEMENT_ACCESS) {
statement = "." + statement;
} else if (kind == ir::MemberExpressionKind::ELEMENT_ACCESS) {
statement = "[" + statement + "]";
}
return statement;
}
static std::tuple<std::string, std::vector<ir::Expression *>> GenerateNestedMemberAccess(
ir::MemberExpression *expr, ArenaAllocator *const allocator, size_t counter = 1)
{
auto member = expr->Object();
std::vector<ir::Expression *> newAssignmentExpressions {};
newAssignmentExpressions.push_back(expr->Property());
while (member->IsMemberExpression()) {
newAssignmentExpressions.push_back(member->AsMemberExpression()->Property());
member = member->AsMemberExpression()->Object();
}
newAssignmentExpressions.push_back(member);
std::reverse(newAssignmentExpressions.begin(), newAssignmentExpressions.end());
std::string newAssignmentStatements = GetFormatPlaceholder(newAssignmentExpressions[0], counter);
newAssignmentExpressions[0] = newAssignmentExpressions[0]->Clone(allocator, nullptr)->AsExpression();
for (size_t i = 1; i < newAssignmentExpressions.size(); i++) {
const std::string statement = GetFormatPlaceholder(newAssignmentExpressions[i], i + counter);
newAssignmentStatements += UpdateStatementToAccessPropertyOrElement(
newAssignmentExpressions[i]->Parent()->AsMemberExpression(), statement);
newAssignmentExpressions[i] = newAssignmentExpressions[i]->Clone(allocator, nullptr)->AsExpression();
}
return {newAssignmentStatements, newAssignmentExpressions};
}
static std::tuple<std::string, std::vector<ir::Expression *>> GenerateStringForAssignment(
const lexer::TokenType opEqual, ir::MemberExpression *expr, ArenaAllocator *const allocator, size_t counter)
{
auto [retStr, retVec] = GenerateNestedMemberAccess(expr, allocator, counter);
counter += retVec.size();
auto result = GenerateNestedMemberAccess(expr, allocator, counter);
counter += std::get<1>(result).size();
retStr += " = ( " + std::get<0>(result) + ' ' + std::string {lexer::TokenToString(CombinedOpToOp(opEqual))} +
" (@@E" + std::to_string(counter) + "))";
retVec.insert(retVec.end(), std::get<1>(result).begin(), std::get<1>(result).end());
return {retStr, retVec};
}
static std::string GetCastString(checker::ETSChecker *checker, ir::Expression *expr, std::vector<ir::Expression *> &vec,
size_t placeholderOffset = 0)
{
auto type = expr->TsType();
if (type->IsETSObjectType() && type->AsETSObjectType()->IsBoxedPrimitive()) {
return ".to" + type->ToString() + "()";
}
vec.push_back(CreateProxyTypeNode(checker, expr));
return " as @@T" + std::to_string(placeholderOffset + vec.size());
}
static std::string GetCastString(ir::Expression *expr)
{
auto type = expr->TsType();
if (type->IsETSObjectType() && type->AsETSObjectType()->IsBoxedPrimitive()) {
return ".to" + type->ToString() + "()";
}
return "";
}
static bool IsSimpleIdentifierOrNamespaceAccess(ir::Expression *expr)
{
if (expr->IsIdentifier() || expr->IsThisExpression() || expr->IsSuperExpression()) {
return true;
}
if (expr->IsMemberExpression()) {
auto *memberExpr = expr->AsMemberExpression();
if (memberExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS) {
ir::Expression *objectExpr = memberExpr->Object();
ir::Expression *propertyExpr = memberExpr->Property();
bool isSimpleElementAccess =
IsSimpleIdentifierOrNamespaceAccess(objectExpr) &&
(propertyExpr->IsLiteral() || IsSimpleIdentifierOrNamespaceAccess(propertyExpr));
return isSimpleElementAccess;
}
return IsSimpleIdentifierOrNamespaceAccess(memberExpr->Object());
}
return false;
}
static ir::Identifier *CreateTempVarIfNeeded(ir::Expression *expr, ArenaAllocator *allocator,
std::vector<ir::Expression *> &tempDeclExpressions,
std::string &tempDeclStr, size_t &counter)
{
auto *tempId = Gensym(allocator);
tempDeclStr += "const @@I" + std::to_string(counter) + " = @@E" + std::to_string(counter + 1) + ";\n";
tempDeclExpressions.emplace_back(tempId);
auto *clonedExpr = expr->Clone(allocator, nullptr)->AsExpression();
tempDeclExpressions.emplace_back(clonedExpr);
ClearTypesVariablesAndScopes(clonedExpr);
counter += TEMP_VARIABLE_PAIR_INDEX_INCREMENT;
return tempId;
}
static void ReplaceExpressionInMember(ir::MemberExpression *expr, ir::Expression *oldExpr, ir::Identifier *tempId,
bool isProperty, ArenaAllocator *allocator)
{
oldExpr->SetParent(nullptr);
if (isProperty) {
auto *newPropertyExpr = tempId->Clone(allocator, expr)->AsExpression();
expr->SetProperty(newPropertyExpr);
newPropertyExpr->SetParent(expr);
} else {
expr->SetObject(tempId->Clone(allocator, expr)->AsExpression());
}
}
static ir::Expression *GenerateElementAccessLowering(const lexer::TokenType opEqual, ir::MemberExpression *expr,
checker::ETSChecker *const checker, parser::ETSParser *parser,
ir::Expression *additionalAssignmentExpression)
{
auto *allocator = checker->Allocator();
std::vector<ir::Expression *> tempDeclExpressions {};
std::string tempDeclStr;
size_t counter = 1;
ir::Expression *objectExpr = expr->Object();
ir::Expression *propertyExpr = expr->Property();
if (!IsSimpleIdentifierOrNamespaceAccess(objectExpr)) {
ir::Identifier *objectId =
CreateTempVarIfNeeded(objectExpr, allocator, tempDeclExpressions, tempDeclStr, counter);
ReplaceExpressionInMember(expr, objectExpr, objectId, false, allocator);
}
if (!propertyExpr->IsLiteral() && !IsSimpleIdentifierOrNamespaceAccess(propertyExpr)) {
ir::Identifier *propertyId =
CreateTempVarIfNeeded(propertyExpr, allocator, tempDeclExpressions, tempDeclStr, counter);
ReplaceExpressionInMember(expr, propertyExpr, propertyId, true, allocator);
}
auto [retStr, retVec] = GenerateStringForAssignment(opEqual, expr, allocator, counter);
retVec.push_back(additionalAssignmentExpression);
retStr += GetCastString(checker, expr, retVec, tempDeclExpressions.size());
retVec.insert(retVec.begin(), tempDeclExpressions.begin(), tempDeclExpressions.end());
retStr = tempDeclStr + retStr;
return parser->CreateFormattedExpression(retStr, retVec);
}
static ir::Expression *GeneratePropertyAccessLowering(const lexer::TokenType opEqual, ir::MemberExpression *expr,
checker::ETSChecker *const checker, parser::ETSParser *parser,
ir::Expression *additionalAssignmentExpression)
{
auto *allocator = checker->Allocator();
std::vector<ir::Expression *> tempDeclExpressions {};
std::string tempDeclStr;
size_t counter = 1;
ir::Expression *objectExpr = expr->Object();
if (!IsSimpleIdentifierOrNamespaceAccess(objectExpr)) {
ir::Identifier *objectId =
CreateTempVarIfNeeded(objectExpr, allocator, tempDeclExpressions, tempDeclStr, counter);
ReplaceExpressionInMember(expr, objectExpr, objectId, false, allocator);
}
auto [retStr, retVec] = GenerateStringForAssignment(opEqual, expr, allocator, counter);
retVec.push_back(additionalAssignmentExpression);
retStr += GetCastString(checker, expr, retVec, tempDeclExpressions.size());
retVec.insert(retVec.begin(), tempDeclExpressions.begin(), tempDeclExpressions.end());
retStr = tempDeclStr + retStr;
return parser->CreateFormattedExpression(retStr, retVec);
}
static ir::Expression *GenerateLoweredResultForLoweredAssignment(const lexer::TokenType opEqual,
ir::MemberExpression *expr,
checker::ETSChecker *const checker,
parser::ETSParser *parser,
ir::Expression *additionalAssignmentExpression)
{
if (expr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS) {
return GenerateElementAccessLowering(opEqual, expr, checker, parser, additionalAssignmentExpression);
}
return GeneratePropertyAccessLowering(opEqual, expr, checker, parser, additionalAssignmentExpression);
}
static ir::Expression *ConstructOpAssignmentResult(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
{
auto *allocator = ctx->allocator;
auto *parser = ctx->parser->AsETSParser();
auto *checker = ctx->GetChecker()->AsETSChecker();
const auto opEqual = assignment->OperatorType();
ES2PANDA_ASSERT(opEqual != lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
auto *const left = assignment->Left();
auto *const right = assignment->Right();
ir::Expression *retVal = nullptr;
if (left->IsIdentifier()) {
std::string formatString =
"@@I1 = (@@I2 " + std::string(lexer::TokenToString(CombinedOpToOp(opEqual))) + " (@@E3))";
std::vector<ir::Expression *> retVec {};
retVec.push_back(GetClone(allocator, left->AsIdentifier()));
retVec.push_back(GetClone(allocator, left->AsIdentifier()));
retVec.push_back(right);
formatString += GetCastString(checker, left, retVec);
retVal = parser->CreateFormattedExpression(formatString, retVec);
} else if (left->IsMemberExpression()) {
retVal = GenerateLoweredResultForLoweredAssignment(opEqual, left->AsMemberExpression(), checker, parser, right);
} else {
ES2PANDA_UNREACHABLE();
}
return retVal;
}
static ir::AstNode *HandleOpAssignment(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *loweringResult = ConstructOpAssignmentResult(ctx, assignment);
loweringResult->SetParent(assignment->Parent());
auto rng = assignment->Range();
loweringResult->SetRange(rng);
loweringResult->TransformChildrenRecursively(
[rng](auto *node) {
node->SetRange(rng);
return node;
},
"");
auto *const scope = NearestScope(assignment);
auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->parserProgram->VarBinder());
checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope);
checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY,
util::Helpers::GetContainingObjectType(assignment)};
checker::ScopeContext sc {checker, scope};
loweringResult->Check(checker);
return loweringResult;
}
struct ArgumentInfo {
std::string newAssignmentStatements {};
ir::Identifier *id1 = nullptr;
ir::Expression *id2 = nullptr;
ir::Identifier *id3 = nullptr;
ir::Expression *object = nullptr;
ir::Expression *property = nullptr;
checker::Type *objType = nullptr;
checker::Type *propType = nullptr;
};
static void ParseArgument(public_lib::Context *ctx, ir::Expression *argument, ArgumentInfo &info)
{
auto *allocator = ctx->allocator;
if (argument->IsTSNonNullExpression()) {
argument = argument->AsTSNonNullExpression()->Expr();
}
if (argument->IsIdentifier()) {
info.id1 = GetClone(allocator, argument->AsIdentifier())->AsIdentifier();
} else if (argument->IsMemberExpression()) {
auto *memberExpression = argument->AsMemberExpression();
if (info.object = memberExpression->Object(); info.object != nullptr && info.object->IsIdentifier()) {
info.id1 = GetClone(allocator, info.object->AsIdentifier())->AsIdentifier();
} else if (info.object != nullptr) {
info.id1 = Gensym(allocator);
info.newAssignmentStatements = "const @@I1 = (@@E2) as @@T3; ";
info.objType = info.object->TsType();
}
if (info.property = memberExpression->Property(); info.property != nullptr && info.property->IsIdentifier()) {
info.id2 = GetClone(allocator, info.property->AsIdentifier());
} else if (info.property != nullptr && info.property->IsLiteral()) {
info.id2 = GetClone(allocator, info.property);
} else if (info.property != nullptr) {
info.id2 = Gensym(allocator);
info.newAssignmentStatements += "const @@I4 = (@@E5) as @@T6;";
info.newAssignmentStatements += ";";
info.propType = info.property->TsType();
}
}
}
static ir::Expression *ConstructUpdateResult(public_lib::Context *ctx, ir::UpdateExpression *upd)
{
auto *allocator = ctx->allocator;
auto *parser = ctx->parser->AsETSParser();
auto *argument = upd->Argument();
auto *checker = ctx->GetChecker()->AsETSChecker();
ArgumentInfo argInfo {};
argInfo.objType = checker->GlobalETSUndefinedType();
argInfo.propType = checker->GlobalETSUndefinedType();
argInfo.id3 = Gensym(allocator);
ParseArgument(ctx, argument, argInfo);
std::string opSign = lexer::TokenToString(CombinedOpToOp(upd->OperatorType()));
std::string suffix = argument->TsType()->IsETSBigIntType() ? "n" : "";
if (upd->IsPrefix()) {
argInfo.newAssignmentStatements += "const @@I7 = (" + GenFormatForExpression(argument, 8U, 9U) +
(argument->IsTSNonNullExpression() ? "!" : "") + opSign + " 1" + suffix +
")" + GetCastString(argument) + ";\n";
argInfo.newAssignmentStatements += GenFormatForExpression(argument, 10U, 11U) + " = @@I12; @@I13";
return parser->CreateFormattedExpression(
argInfo.newAssignmentStatements, argInfo.id1, argInfo.object, argInfo.objType, argInfo.id2,
argInfo.property, argInfo.propType, argInfo.id3, GetClone(allocator, argInfo.id1),
GetClone(allocator, argInfo.id2), GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2),
GetClone(allocator, argInfo.id3), GetClone(allocator, argInfo.id3));
}
argInfo.newAssignmentStatements += "const @@I7 = " + GenFormatForExpression(argument, 8, 9) +
(argument->IsTSNonNullExpression() ? "!" : "") + GetCastString(argument) + ";" +
GenFormatForExpression(argument, 10U, 11U) + " = (@@I12 " + opSign + " 1" +
suffix + ")" + GetCastString(argument) + "; @@I13\n;";
return parser->CreateFormattedExpression(
argInfo.newAssignmentStatements, argInfo.id1, argInfo.object, argInfo.objType, argInfo.id2, argInfo.property,
argInfo.propType, argInfo.id3, GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2),
GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2), GetClone(allocator, argInfo.id3),
GetClone(allocator, argInfo.id3));
}
static ir::AstNode *HandleUpdate(public_lib::Context *ctx, ir::UpdateExpression *upd)
{
auto *const scope = NearestScope(upd);
ir::Expression *loweringResult = ConstructUpdateResult(ctx, upd);
auto *checker = ctx->GetChecker()->AsETSChecker();
auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY,
util::Helpers::GetContainingObjectType(upd)};
checker::ScopeContext sc {checker, scope};
loweringResult->SetParent(upd->Parent());
auto rng = upd->Range();
loweringResult->SetRange(rng);
loweringResult->TransformChildrenRecursively(
[rng](auto *node) {
node->SetRange(rng);
return node;
},
"");
InitScopesPhaseETS::RunExternalNode(loweringResult, checker->VarBinder());
checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult,
NearestScope(loweringResult));
loweringResult->Check(checker);
return loweringResult;
}
bool OpAssignmentLowering::PerformForProgram(parser::Program *program)
{
program->Ast()->TransformChildrenRecursively(
[ctx = Context()](ir::AstNode *ast) {
if (ast->IsAssignmentExpression() &&
ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) {
return HandleOpAssignment(ctx, ast->AsAssignmentExpression());
}
if (ast->IsUpdateExpression()) {
return HandleUpdate(ctx, ast->AsUpdateExpression());
}
return ast;
},
Name());
return true;
}
bool OpAssignmentLowering::PostconditionForProgram(const parser::Program *program)
{
return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) {
return (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->TsType() != nullptr &&
ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) ||
ast->IsUpdateExpression();
});
}
}