* 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 "arrayExpression.h"
#include <cstddef>
#include "checker/ETSchecker.h"
#include "checker/TSchecker.h"
#include "checker/ets/typeRelationContext.h"
#include "checker/ts/destructuringContext.h"
#include "checker/types/ets/etsTupleType.h"
#include "compiler/core/ETSGen.h"
#include "compiler/core/pandagen.h"
namespace ark::es2panda::ir {
ArrayExpression::ArrayExpression([[maybe_unused]] Tag const tag, ArrayExpression const &other,
ArenaAllocator *const allocator)
: AnnotatedExpression(static_cast<AnnotatedExpression const &>(other), allocator), elements_(allocator->Adapter())
{
SetPreferredType(other.PreferredType());
isDeclaration_ = other.isDeclaration_;
trailingComma_ = other.trailingComma_;
optional_ = other.optional_;
for (auto *element : other.elements_) {
elements_.emplace_back(element->Clone(allocator, this)->AsExpression());
}
}
ArrayExpression *ArrayExpression::Clone(ArenaAllocator *const allocator, AstNode *const parent)
{
auto *const clone = allocator->New<ArrayExpression>(Tag {}, *this, allocator);
ES2PANDA_ASSERT(clone);
if (parent != nullptr) {
clone->SetParent(parent);
}
clone->SetRange(Range());
return clone;
}
bool ArrayExpression::ConvertibleToArrayPattern()
{
bool restFound = false;
bool convResult = true;
for (auto *it : elements_) {
switch (it->Type()) {
case AstNodeType::ARRAY_EXPRESSION: {
convResult = it->AsArrayExpression()->ConvertibleToArrayPattern();
break;
}
case AstNodeType::SPREAD_ELEMENT: {
if (!restFound && it == elements_.back() && !trailingComma_) {
convResult = it->AsSpreadElement()->ConvertibleToRest(isDeclaration_);
} 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::MEMBER_EXPRESSION:
case AstNodeType::OMITTED_EXPRESSION:
case AstNodeType::IDENTIFIER:
case AstNodeType::ARRAY_PATTERN:
case AstNodeType::OBJECT_PATTERN:
case AstNodeType::ASSIGNMENT_PATTERN:
case AstNodeType::REST_ELEMENT: {
break;
}
default: {
convResult = false;
break;
}
}
if (!convResult) {
break;
}
}
SetType(AstNodeType::ARRAY_PATTERN);
return convResult;
}
void ArrayExpression::TransformChildren(const NodeTransformer &cb, std::string_view const transformationName)
{
for (auto *&it : VectorIterationGuard(elements_)) {
if (auto *transformedNode = cb(it); it != transformedNode) {
it->SetTransformedNode(transformationName, transformedNode);
it = transformedNode->AsExpression();
}
}
if (auto *typeAnnotation = TypeAnnotation(); typeAnnotation != nullptr) {
if (auto *transformedNode = cb(typeAnnotation); typeAnnotation != transformedNode) {
typeAnnotation->SetTransformedNode(transformationName, transformedNode);
SetTsTypeAnnotation(static_cast<TypeNode *>(transformedNode));
}
}
}
void ArrayExpression::Iterate(const NodeTraverser &cb) const
{
for (auto *it : VectorIterationGuard(elements_)) {
cb(it);
}
if (TypeAnnotation() != nullptr) {
cb(TypeAnnotation());
}
}
void ArrayExpression::Dump(ir::AstDumper *dumper) const
{
dumper->Add({{"type", Type() == AstNodeType::ARRAY_EXPRESSION ? "ArrayExpression" : "ArrayPattern"},
{"elements", elements_},
{"typeAnnotation", AstDumper::Optional(TypeAnnotation())},
{"optional", AstDumper::Optional(optional_)}});
}
void ArrayExpression::Dump(ir::SrcDumper *dumper) const
{
dumper->Add("[");
for (auto element : elements_) {
element->Dump(dumper);
if (element != elements_.back()) {
dumper->Add(", ");
}
}
dumper->Add("]");
}
void ArrayExpression::Compile(compiler::PandaGen *pg) const
{
pg->GetAstCompiler()->Compile(this);
}
void ArrayExpression::Compile(compiler::ETSGen *const etsg) const
{
etsg->GetAstCompiler()->Compile(this);
}
checker::Type *ArrayExpression::Check(checker::TSChecker *checker)
{
return checker->GetAnalyzer()->Check(this);
}
checker::Type *CheckAssignmentPattern(Expression *it, checker::TSChecker *checker, checker::Type *elementType,
bool &addOptional, checker::ElementFlags &memberFlag)
{
auto *assignmentPattern = it->AsAssignmentPattern();
if (assignmentPattern->Left()->IsIdentifier()) {
const ir::Identifier *ident = assignmentPattern->Left()->AsIdentifier();
ES2PANDA_ASSERT(ident->Variable());
varbinder::Variable *bindingVar = ident->Variable();
checker::Type *initializerType = checker->GetBaseTypeOfLiteralType(assignmentPattern->Right()->Check(checker));
bindingVar->SetTsType(initializerType);
elementType = initializerType;
} else if (assignmentPattern->Left()->IsArrayPattern()) {
auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::FORCE_TUPLE);
auto destructuringContext = checker::ArrayDestructuringContext(
{checker, assignmentPattern->Left()->AsArrayPattern(), false, true, nullptr, assignmentPattern->Right()});
destructuringContext.Start();
elementType = destructuringContext.InferredType();
} else {
ES2PANDA_ASSERT(assignmentPattern->Left()->IsObjectPattern());
auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::FORCE_TUPLE);
auto destructuringContext = checker::ObjectDestructuringContext(
{checker, assignmentPattern->Left()->AsObjectPattern(), false, true, nullptr, assignmentPattern->Right()});
destructuringContext.Start();
elementType = destructuringContext.InferredType();
}
if (addOptional) {
memberFlag = checker::ElementFlags::OPTIONAL;
} else {
memberFlag = checker::ElementFlags::REQUIRED;
}
return elementType;
}
checker::Type *CheckElementPattern(Expression *it, checker::Type *elementType, checker::TSChecker *checker,
bool &addOptional, checker::ElementFlags &memberFlag)
{
switch (it->Type()) {
case ir::AstNodeType::REST_ELEMENT: {
elementType = checker->Allocator()->New<checker::ArrayType>(checker->GlobalAnyType());
memberFlag = checker::ElementFlags::REST;
addOptional = false;
return elementType;
}
case ir::AstNodeType::OBJECT_PATTERN: {
elementType = it->AsObjectPattern()->CheckPattern(checker);
memberFlag = checker::ElementFlags::REQUIRED;
addOptional = false;
return elementType;
}
case ir::AstNodeType::ARRAY_PATTERN: {
elementType = it->AsArrayPattern()->CheckPattern(checker);
memberFlag = checker::ElementFlags::REQUIRED;
addOptional = false;
return elementType;
}
case ir::AstNodeType::ASSIGNMENT_PATTERN: {
return CheckAssignmentPattern(it, checker, elementType, addOptional, memberFlag);
}
case ir::AstNodeType::OMITTED_EXPRESSION: {
elementType = checker->GlobalAnyType();
memberFlag = checker::ElementFlags::REQUIRED;
addOptional = false;
return elementType;
}
case ir::AstNodeType::IDENTIFIER: {
const ir::Identifier *ident = it->AsIdentifier();
ES2PANDA_ASSERT(ident->Variable());
elementType = checker->GlobalAnyType();
ident->Variable()->SetTsType(elementType);
memberFlag = checker::ElementFlags::REQUIRED;
addOptional = false;
return elementType;
}
default: {
ES2PANDA_UNREACHABLE();
}
}
}
checker::Type *ArrayExpression::CheckPattern(checker::TSChecker *checker)
{
checker::ObjectDescriptor *desc = checker->Allocator()->New<checker::ObjectDescriptor>(checker->Allocator());
ArenaVector<checker::ElementFlags> elementFlags(checker->Allocator()->Adapter());
checker::ElementFlags combinedFlags = checker::ElementFlags::NO_OPTS;
uint32_t minLength = 0;
uint32_t index = elements_.size();
bool addOptional = true;
for (auto it = elements_.rbegin(); it != elements_.rend(); it++) {
checker::Type *elementType = nullptr;
checker::ElementFlags memberFlag = checker::ElementFlags::NO_OPTS;
elementType = CheckElementPattern(*it, elementType, checker, addOptional, memberFlag);
util::StringView memberIndex = util::Helpers::ToStringView(checker->Allocator(), index - 1);
auto *memberVar =
varbinder::Scope::CreateVar(checker->Allocator(), memberIndex, varbinder::VariableFlags::PROPERTY, *it);
if (memberFlag == checker::ElementFlags::OPTIONAL) {
memberVar->AddFlag(varbinder::VariableFlags::OPTIONAL);
} else {
minLength++;
}
memberVar->SetTsType(elementType);
elementFlags.push_back(memberFlag);
desc->properties.insert(desc->properties.begin(), memberVar);
combinedFlags |= memberFlag;
index--;
}
ES2PANDA_ASSERT(desc);
const checker::TupleTypeInfo tupleTypeInfo = {combinedFlags, minLength,
static_cast<uint32_t>(desc->properties.size()), false};
return checker->CreateTupleType(desc, std::move(elementFlags), tupleTypeInfo);
}
void ArrayExpression::CleanCheckInformation()
{
SetPreferredType(nullptr);
SetTsType(nullptr);
for (auto *element : elements_) {
element->SetAstNodeFlags(ir::AstNodeFlags::NO_OPTS);
element->CleanCheckInformation();
SetPreferredType(nullptr);
element->SetTsType(nullptr);
}
}
bool ArrayExpression::TrySetPreferredTypeForNestedArrayExpr(checker::ETSChecker *const checker,
ArrayExpression *const nestedArrayExpr,
const std::size_t idx) const
{
auto doesArrayExprFitInTuple = [&checker, &nestedArrayExpr](const checker::Type *const possibleTupleType) {
return !possibleTupleType->IsETSTupleType() ||
checker->IsArrayExprSizeValidForTuple(nestedArrayExpr, possibleTupleType->AsETSTupleType());
};
if (PreferredType()->IsETSTupleType()) {
if (idx >= PreferredType()->AsETSTupleType()->GetTupleSize()) {
return false;
}
auto *const typeInTupleAtIdx = PreferredType()->AsETSTupleType()->GetTypeAtIndex(idx);
nestedArrayExpr->SetPreferredType(typeInTupleAtIdx);
return doesArrayExprFitInTuple(typeInTupleAtIdx);
}
if (PreferredType()->IsETSArrayType()) {
auto *const arrayElementType = PreferredType()->AsETSArrayType()->ElementType();
nestedArrayExpr->SetPreferredType(arrayElementType);
return doesArrayExprFitInTuple(arrayElementType);
}
if (PreferredType()->IsETSResizableArrayType()) {
auto *const arrayElementType = PreferredType()->AsETSObjectType()->TypeArguments()[0];
if (!doesArrayExprFitInTuple(arrayElementType)) {
return false;
}
nestedArrayExpr->SetPreferredType(arrayElementType);
return true;
}
if (nestedArrayExpr->PreferredType() == nullptr) {
nestedArrayExpr->SetPreferredType(PreferredType());
}
return true;
}
checker::VerifiedType ArrayExpression::Check(checker::ETSChecker *checker)
{
return {this, checker->GetAnalyzer()->Check(this)};
}
static std::optional<checker::Type *> ExtractPossiblePreferredType(checker::Type *type)
{
ES2PANDA_ASSERT(type);
if (type->IsETSArrayType() || type->IsETSTupleType() || type->IsETSResizableArrayType()) {
return std::make_optional(type);
}
return std::nullopt;
}
void ArrayExpression::SetPreferredTypeOnFuncParam(checker::ETSChecker *checker, checker::Type *param,
checker::TypeRelationFlag flags)
{
auto possiblePreferredType = ExtractPossiblePreferredType(param);
if (!possiblePreferredType.has_value()) {
return;
}
param = possiblePreferredType.value();
if (param->IsETSTupleType()) {
SetPreferredType(param);
return;
}
checker::Type *targetType = nullptr;
if (param->IsETSArrayType()) {
targetType = param->AsETSArrayType()->ElementType();
} else {
targetType = param->AsETSResizableArrayType()->ElementType();
}
bool isAssignable = true;
for (auto *const elem : elements_) {
if (!elem->IsSpreadElement()) {
elem->SetPreferredType(targetType);
}
checker::Type *elemType = elem->Check(checker);
if (elem->IsSpreadElement()) {
elemType = checker->GetElementTypeOfSpreadType(elemType);
}
checker::AssignmentContext assignCtx(checker->Relation(), elem, elemType, targetType, elem->Start(),
std::nullopt, flags);
isAssignable &= assignCtx.IsAssignable();
}
if (isAssignable) {
SetPreferredType(param);
} else {
for (auto *const elem : elements_) {
elem->CleanCheckInformation();
}
}
}
void ArrayExpression::SetPreferredTypeBasedOnFuncParam(checker::ETSChecker *checker, checker::Type *param,
checker::TypeRelationFlag flags)
{
if (PreferredType() != nullptr) {
return;
}
if (param->IsETSUnionType()) {
for (checker::Type *typeOfUnion : param->AsETSUnionType()->ConstituentTypes()) {
SetPreferredTypeOnFuncParam(checker, typeOfUnion, flags);
}
} else {
SetPreferredTypeOnFuncParam(checker, param, flags);
}
}
}