* 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 "objectExpression.h"
#include <compiler/base/literals.h>
#include <compiler/core/pandagen.h>
#include <ir/astDump.h>
#include <ir/base/classDefinition.h>
#include <ir/base/property.h>
#include <ir/base/scriptFunction.h>
#include <ir/base/spreadElement.h>
#include <ir/expressions/arrayExpression.h>
#include <ir/expressions/arrowFunctionExpression.h>
#include <ir/expressions/assignmentExpression.h>
#include <ir/expressions/classExpression.h>
#include <ir/expressions/functionExpression.h>
#include <ir/expressions/literals/nullLiteral.h>
#include <ir/expressions/literals/numberLiteral.h>
#include <ir/expressions/literals/stringLiteral.h>
#include <ir/expressions/literals/taggedLiteral.h>
#include <ir/expressions/unaryExpression.h>
namespace panda::es2panda::ir {
static bool IsAnonClassOrFuncExpr(const ir::Expression *expr)
{
const ir::Identifier *identifier;
switch (expr->Type()) {
case ir::AstNodeType::FUNCTION_EXPRESSION: {
identifier = expr->AsFunctionExpression()->Function()->Id();
break;
}
case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION: {
identifier = expr->AsArrowFunctionExpression()->Function()->Id();
break;
}
case ir::AstNodeType::CLASS_EXPRESSION: {
identifier = expr->AsClassExpression()->Definition()->Ident();
break;
}
default: {
return false;
}
}
return identifier == nullptr || identifier->Name().Empty();
}
static bool IsLegalNameFormat(const ir::Expression *expr)
{
util::StringView name;
if (expr->IsIdentifier()) {
name = expr->AsIdentifier()->Name();
} else if (expr->IsStringLiteral()) {
name = expr->AsStringLiteral()->Str();
} else if (expr->IsNumberLiteral()) {
name = expr->AsNumberLiteral()->Str();
} else {
UNREACHABLE();
}
return name.Find(".") != std::string::npos && name.Find("\\") != std::string::npos;
}
ValidationInfo ObjectExpression::ValidateExpression()
{
ValidationInfo info;
bool foundProto = false;
for (auto *it : properties_) {
switch (it->Type()) {
case AstNodeType::OBJECT_EXPRESSION:
case AstNodeType::ARRAY_EXPRESSION: {
return {"Unexpected token.", it->Start()};
}
case AstNodeType::SPREAD_ELEMENT: {
info = it->AsSpreadElement()->ValidateExpression();
break;
}
case AstNodeType::PROPERTY: {
auto *prop = it->AsProperty();
info = prop->ValidateExpression();
if (prop->Kind() == PropertyKind::PROTO) {
if (foundProto) {
return {"Duplicate __proto__ fields are not allowed in object literals", prop->Key()->Start()};
}
foundProto = true;
}
break;
}
default: {
break;
}
}
if (info.Fail()) {
break;
}
}
return info;
}
bool ObjectExpression::ConvertibleToObjectPattern()
{
bool restFound = false;
bool convResult = true;
for (auto *it : properties_) {
switch (it->Type()) {
case AstNodeType::ARRAY_EXPRESSION: {
convResult = it->AsArrayExpression()->ConvertibleToArrayPattern();
break;
}
case AstNodeType::SPREAD_ELEMENT: {
if (!restFound && it == properties_.back() && !trailingComma_) {
convResult = it->AsSpreadElement()->ConvertibleToRest(isDeclaration_, false);
} else {
convResult = false;
}
restFound = true;
break;
}
case AstNodeType::OBJECT_EXPRESSION: {
convResult = it->AsObjectExpression()->ConvertibleToObjectPattern();
break;
}
case AstNodeType::ASSIGNMENT_EXPRESSION: {
convResult = it->AsAssignmentExpression()->ConvertibleToAssignmentPattern();
break;
}
case AstNodeType::META_PROPERTY_EXPRESSION:
case AstNodeType::CHAIN_EXPRESSION:
case AstNodeType::SEQUENCE_EXPRESSION: {
convResult = false;
break;
}
case AstNodeType::PROPERTY: {
convResult = it->AsProperty()->ConventibleToPatternProperty();
break;
}
default: {
break;
}
}
if (!convResult) {
break;
}
}
SetType(AstNodeType::OBJECT_PATTERN);
return convResult;
}
void ObjectExpression::SetDeclaration()
{
isDeclaration_ = true;
}
void ObjectExpression::SetOptional(bool optional)
{
optional_ = optional;
}
void ObjectExpression::SetTsTypeAnnotation(Expression *typeAnnotation)
{
typeAnnotation_ = typeAnnotation;
}
void ObjectExpression::Iterate(const NodeTraverser &cb) const
{
for (auto *it : properties_) {
cb(it);
}
if (typeAnnotation_) {
cb(typeAnnotation_);
}
}
void ObjectExpression::Dump(ir::AstDumper *dumper) const
{
dumper->Add({{"type", (type_ == AstNodeType::OBJECT_EXPRESSION) ? "ObjectExpression" : "ObjectPattern"},
{"properties", properties_},
{"typeAnnotation", AstDumper::Optional(typeAnnotation_)},
{"optional", AstDumper::Optional(optional_)}});
}
void ObjectExpression::FillInLiteralBuffer(compiler::LiteralBuffer *buf,
std::vector<std::vector<const Literal *>> &tempLiteralBuffer) const
{
for (size_t i = 0 ; i < tempLiteralBuffer.size(); i++) {
if (tempLiteralBuffer[i].size() == 0) {
continue;
}
auto propBuf = tempLiteralBuffer[i];
for (size_t j = 0; j < propBuf.size(); j++) {
buf->Add(propBuf[j]);
}
}
}
void ObjectExpression::EmitCreateObjectWithBuffer(compiler::PandaGen *pg, compiler::LiteralBuffer *buf,
bool hasMethod) const
{
if (buf->IsEmpty()) {
pg->CreateEmptyObject(this);
return;
}
int32_t bufIdx = pg->AddLiteralBuffer(buf);
pg->CreateObjectWithBuffer(this, static_cast<uint32_t>(bufIdx));
}
static const Literal *CreateLiteral(compiler::PandaGen *pg, const ir::Property *prop, util::BitSet *compiled,
size_t propIndex)
{
if (util::Helpers::IsConstantExpr(prop->Value())) {
compiled->Set(propIndex);
return prop->Value()->AsLiteral();
}
if (prop->Kind() != ir::PropertyKind::INIT) {
ASSERT(prop->IsAccessor());
return pg->Allocator()->New<TaggedLiteral>(LiteralTag::ACCESSOR);
}
if (prop->IsMethod()) {
const ir::ScriptFunction *method = prop->Value()->AsFunctionExpression()->Function();
LiteralTag tag = LiteralTag::METHOD;
if (method->IsGenerator()) {
tag = LiteralTag::GENERATOR_METHOD;
if (method->IsAsync()) {
tag = LiteralTag::ASYNC_GENERATOR_METHOD;
}
}
compiled->Set(propIndex);
return pg->Allocator()->New<TaggedLiteral>(tag, method->Scope()->InternalName());
}
return pg->Allocator()->New<NullLiteral>();
}
void ObjectExpression::CompileStaticProperties(compiler::PandaGen *pg, util::BitSet *compiled) const
{
bool hasMethod = false;
auto *buf = pg->NewLiteralBuffer();
std::vector<std::vector<const Literal *>> tempLiteralBuffer(properties_.size());
std::unordered_map<util::StringView, size_t> propNameMap;
std::unordered_map<util::StringView, size_t> getterIndxNameMap;
std::unordered_map<util::StringView, size_t> setterIndxNameMap;
for (size_t i = 0; i < properties_.size(); i++) {
if (properties_[i]->IsSpreadElement()) {
break;
}
const ir::Property *prop = properties_[i]->AsProperty();
if (!util::Helpers::IsConstantPropertyKey(prop->Key(), prop->IsComputed()) ||
prop->Kind() == ir::PropertyKind::PROTO) {
break;
}
std::vector<const Literal *> propBuf;
util::StringView name = util::Helpers::LiteralToPropName(pg->Allocator(), prop->Key());
size_t propIndex = i;
auto res = propNameMap.insert({name, propIndex});
if (!res.second) {
propIndex = res.first->second;
if (prop->Kind() != ir::PropertyKind::SET && getterIndxNameMap.find(name) != getterIndxNameMap.end()) {
compiled->Set(getterIndxNameMap[name]);
}
if (prop->Kind() != ir::PropertyKind::GET && setterIndxNameMap.find(name) != setterIndxNameMap.end()) {
compiled->Set(setterIndxNameMap[name]);
}
}
if (prop->Kind() == ir::PropertyKind::GET) {
getterIndxNameMap[name] = i;
} else if (prop->Kind() == ir::PropertyKind::SET) {
setterIndxNameMap[name] = i;
}
propBuf.push_back(pg->Allocator()->New<StringLiteral>(name));
propBuf.push_back(CreateLiteral(pg, prop, compiled, i));
if (prop->IsMethod()) {
hasMethod = true;
const ir::FunctionExpression *func = prop->Value()->AsFunctionExpression();
size_t paramNum = func->Function()->FormalParamsLength();
Literal *methodAffiliate = pg->Allocator()->New<TaggedLiteral>(LiteralTag::METHODAFFILIATE, paramNum);
propBuf.push_back(methodAffiliate);
}
tempLiteralBuffer[propIndex] = propBuf;
}
FillInLiteralBuffer(buf, tempLiteralBuffer);
EmitCreateObjectWithBuffer(pg, buf, hasMethod);
}
void ObjectExpression::CompilePropertyOfGetterOrSetter(compiler::PandaGen *pg, const ir::Property *prop,
compiler::VReg objReg) const
{
compiler::VReg key = pg->LoadPropertyKey(prop->Key(), prop->IsComputed());
compiler::VReg undef = pg->AllocReg();
pg->LoadConst(this, compiler::Constant::JS_UNDEFINED);
pg->StoreAccumulator(this, undef);
compiler::VReg getter = undef;
compiler::VReg setter = undef;
compiler::VReg accessor = pg->AllocReg();
pg->LoadAccumulator(prop->Value(), objReg);
prop->Value()->Compile(pg);
pg->StoreAccumulator(prop->Value(), accessor);
if (prop->Kind() == ir::PropertyKind::GET) {
getter = accessor;
} else {
setter = accessor;
}
pg->DefineGetterSetterByValue(this, objReg, key, getter, setter, prop->IsComputed());
}
void ObjectExpression::CompilePropertyWithInit(compiler::PandaGen *pg, const ir::Property *prop,
compiler::VReg objReg) const
{
compiler::Operand key = pg->ToPropertyKey(prop->Key(), prop->IsComputed());
const auto *value = prop->Value();
bool nameSetting = false;
if (prop->IsMethod()) {
pg->LoadAccumulator(value, objReg);
if (prop->IsComputed()) {
nameSetting = true;
}
} else {
if (prop->IsComputed()) {
nameSetting = IsAnonClassOrFuncExpr(value);
} else {
nameSetting = IsAnonClassOrFuncExpr(value) && IsLegalNameFormat(prop->Key());
}
}
bool shouldSetInvalidFlag = value->IsUnaryExpression() && value->AsUnaryExpression()->IsNegativeNumber()
&& !prop->IsComputed();
if (shouldSetInvalidFlag) {
pg->SetSourceLocationFlag(lexer::SourceLocationFlag::INVALID_SOURCE_LOCATION);
}
value->Compile(pg);
if (!nameSetting && pg->Binder()->Program()->TargetApiVersion() > 10) {
pg->DefineOwnProperty(this, objReg, key);
} else {
pg->StoreOwnProperty(this, objReg, key, nameSetting);
}
pg->SetSourceLocationFlag(lexer::SourceLocationFlag::VALID_SOURCE_LOCATION);
}
void ObjectExpression::CompileRemainingProperties(compiler::PandaGen *pg, const util::BitSet *compiled,
compiler::VReg objReg) const
{
for (size_t i = 0; i < properties_.size(); i++) {
if (compiled->Test(i)) {
continue;
}
compiler::RegScope rs(pg);
if (properties_[i]->IsSpreadElement()) {
const ir::SpreadElement *spread = properties_[i]->AsSpreadElement();
spread->Argument()->Compile(pg);
pg->CopyDataProperties(spread, objReg);
continue;
}
const ir::Property *prop = properties_[i]->AsProperty();
switch (prop->Kind()) {
case ir::PropertyKind::GET:
case ir::PropertyKind::SET: {
CompilePropertyOfGetterOrSetter(pg, prop, objReg);
break;
}
case ir::PropertyKind::INIT: {
CompilePropertyWithInit(pg, prop, objReg);
break;
}
case ir::PropertyKind::PROTO: {
prop->Value()->Compile(pg);
compiler::VReg proto = pg->AllocReg();
pg->StoreAccumulator(this, proto);
pg->SetObjectWithProto(this, proto, objReg);
break;
}
default: {
UNREACHABLE();
}
}
}
pg->LoadAccumulator(this, objReg);
}
void ObjectExpression::Compile(compiler::PandaGen *pg) const
{
if (properties_.empty()) {
pg->CreateEmptyObject(this);
return;
}
util::BitSet compiled(properties_.size());
CompileStaticProperties(pg, &compiled);
compiler::RegScope rs(pg);
compiler::VReg objReg = pg->AllocReg();
pg->StoreAccumulator(this, objReg);
CompileRemainingProperties(pg, &compiled, objReg);
}
const util::StringView &GetPropertyName(const ir::Expression *key)
{
if (key->IsIdentifier()) {
return key->AsIdentifier()->Name();
}
if (key->IsStringLiteral()) {
return key->AsStringLiteral()->Str();
}
ASSERT(key->IsNumberLiteral());
return key->AsNumberLiteral()->Str();
}
binder::VariableFlags GetFlagsForProperty(const ir::Property *prop)
{
if (!prop->IsMethod()) {
return binder::VariableFlags::PROPERTY;
}
binder::VariableFlags propFlags = binder::VariableFlags::METHOD;
if (prop->IsAccessor() && prop->Kind() == PropertyKind::GET) {
propFlags |= binder::VariableFlags::READONLY;
}
return propFlags;
}
void ObjectExpression::UpdateSelf(const NodeUpdater &cb, [[maybe_unused]] binder::Binder *binder)
{
for (auto iter = properties_.begin(); iter != properties_.end(); iter++) {
*iter = std::get<ir::AstNode *>(cb(*iter))->AsExpression();
}
if (typeAnnotation_) {
typeAnnotation_ = std::get<ir::AstNode *>(cb(typeAnnotation_))->AsExpression();
}
}
}