* Copyright (c) 2021-2025 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 "ir/expressions/literals/bigIntLiteral.h"
#include "ir/expressions/literals/numberLiteral.h"
#include "ir/expressions/literals/stringLiteral.h"
#include "ir/expressions/functionExpression.h"
#include "ir/expressions/memberExpression.h"
#include "ir/expressions/identifier.h"
#include "ir/base/property.h"
#include "ir/base/scriptFunction.h"
#include "ir/base/spreadElement.h"
#include "ir/base/tsIndexSignature.h"
#include "ir/base/tsMethodSignature.h"
#include "ir/base/tsPropertySignature.h"
#include "ir/base/tsSignatureDeclaration.h"
#include "ir/ts/tsTypeLiteral.h"
#include "ir/ts/tsInterfaceDeclaration.h"
#include "ir/ts/tsInterfaceHeritage.h"
#include "ir/ts/tsInterfaceBody.h"
#include "util/helpers.h"
#include "varbinder/variable.h"
#include "varbinder/scope.h"
#include "checker/TSchecker.h"
#include "checker/types/ts/indexInfo.h"
namespace ark::es2panda::checker {
void TSChecker::CheckIndexConstraints(Type *type)
{
if (!type->IsObjectType()) {
return;
}
ObjectType *objType = type->AsObjectType();
ResolveStructuredTypeMembers(objType);
IndexInfo *numberInfo = objType->NumberIndexInfo();
IndexInfo *stringInfo = objType->StringIndexInfo();
const ArenaVector<varbinder::LocalVariable *> &properties = objType->Properties();
if (numberInfo != nullptr) {
for (auto *it : properties) {
if (it->HasFlag(varbinder::VariableFlags::NUMERIC_NAME)) {
Type *propType = GetTypeOfVariable(it);
IsTypeAssignableTo(propType, numberInfo->GetType(), diagnostic::PROP_ASSIGN_TO_NUMERIC_INDEX,
{it->Name(), propType, numberInfo->GetType()}, it->Declaration()->Node()->Start());
}
}
}
if (stringInfo != nullptr) {
for (auto *it : properties) {
Type *propType = GetTypeOfVariable(it);
IsTypeAssignableTo(propType, stringInfo->GetType(), diagnostic::PROP_ASSIGN_TO_STRING_INDEX,
{it->Name(), propType, stringInfo->GetType()}, it->Declaration()->Node()->Start());
}
if (numberInfo != nullptr && !IsTypeAssignableTo(numberInfo->GetType(), stringInfo->GetType())) {
ThrowTypeError({"Number index info type ", numberInfo->GetType(),
" is not assignable to string index info type ", stringInfo->GetType(), "."},
numberInfo->Pos());
}
}
}
void TSChecker::ResolveStructuredTypeMembers(Type *type)
{
if (type->IsObjectType()) {
ObjectType *objType = type->AsObjectType();
if (objType->IsObjectLiteralType()) {
ResolveObjectTypeMembers(objType);
return;
}
if (objType->IsInterfaceType()) {
ResolveInterfaceOrClassTypeMembers(objType->AsInterfaceType());
return;
}
}
if (type->IsUnionType()) {
ResolveUnionTypeMembers(type->AsUnionType());
return;
}
}
void TSChecker::ResolveUnionTypeMembers(UnionType *type)
{
if (type->MergedObjectType() != nullptr) {
return;
}
ObjectDescriptor *desc = Allocator()->New<ObjectDescriptor>(Allocator());
ArenaVector<Type *> stringInfoTypes(Allocator()->Adapter());
ArenaVector<Type *> numberInfoTypes(Allocator()->Adapter());
ArenaVector<Signature *> callSignatures(Allocator()->Adapter());
ArenaVector<Signature *> constructSignatures(Allocator()->Adapter());
for (auto *it : type->AsUnionType()->ConstituentTypes()) {
if (!it->IsObjectType()) {
continue;
}
ObjectType *objType = it->AsObjectType();
ResolveObjectTypeMembers(objType);
if (!objType->CallSignatures().empty()) {
for (auto *signature : objType->CallSignatures()) {
callSignatures.push_back(signature);
}
}
if (!objType->ConstructSignatures().empty()) {
for (auto *signature : objType->ConstructSignatures()) {
constructSignatures.push_back(signature);
}
}
if (objType->StringIndexInfo() != nullptr) {
stringInfoTypes.push_back(objType->StringIndexInfo()->GetType());
}
if (objType->NumberIndexInfo() != nullptr) {
numberInfoTypes.push_back(objType->NumberIndexInfo()->GetType());
}
}
ES2PANDA_ASSERT(desc != nullptr);
desc->callSignatures = callSignatures;
desc->constructSignatures = constructSignatures;
if (!stringInfoTypes.empty()) {
desc->stringIndexInfo = Allocator()->New<IndexInfo>(CreateUnionType(std::move(stringInfoTypes)), "x", false);
}
if (!numberInfoTypes.empty()) {
desc->numberIndexInfo = Allocator()->New<IndexInfo>(CreateUnionType(std::move(numberInfoTypes)), "x", false);
}
ObjectType *mergedType = Allocator()->New<ObjectLiteralType>(desc);
ES2PANDA_ASSERT(mergedType != nullptr);
mergedType->AddObjectFlag(ObjectFlags::RESOLVED_MEMBERS);
type->SetMergedObjectType(mergedType);
}
void TSChecker::ResolveInterfaceOrClassTypeMembers(InterfaceType *type)
{
if (type->HasObjectFlag(ObjectFlags::RESOLVED_MEMBERS)) {
return;
}
ResolveDeclaredMembers(type);
GetBaseTypes(type);
type->AddObjectFlag(ObjectFlags::RESOLVED_MEMBERS);
}
void TSChecker::ResolveObjectTypeMembers(ObjectType *type)
{
if (!type->IsObjectLiteralType() || type->HasObjectFlag(ObjectFlags::RESOLVED_MEMBERS)) {
return;
}
ES2PANDA_ASSERT(type->Variable() && type->Variable()->Declaration()->Node()->IsTSTypeLiteral());
auto *typeLiteral = type->Variable()->Declaration()->Node()->AsTSTypeLiteral();
std::vector<ir::TSSignatureDeclaration *> signatureDeclarations {};
std::vector<ir::TSIndexSignature *> indexDeclarations {};
for (auto *it : typeLiteral->Members()) {
ResolvePropertiesOfObjectType(type, it, signatureDeclarations, indexDeclarations, false);
}
type->AddObjectFlag(ObjectFlags::RESOLVED_MEMBERS);
ResolveSignaturesOfObjectType(type, signatureDeclarations);
ResolveIndexInfosOfObjectType(type, indexDeclarations);
}
void TSChecker::ResolvePropertiesOfObjectType(ObjectType *type, ir::AstNode *member,
std::vector<ir::TSSignatureDeclaration *> &signatureDeclarations,
std::vector<ir::TSIndexSignature *> &indexDeclarations, bool isInterface)
{
if (member->IsTSPropertySignature()) {
varbinder::Variable *prop = member->AsTSPropertySignature()->Variable();
if (!isInterface ||
ValidateInterfaceMemberRedeclaration(type, prop, member->AsTSPropertySignature()->Key()->Start())) {
type->AddProperty(prop->AsLocalVariable());
}
return;
}
if (member->IsTSMethodSignature()) {
varbinder::Variable *method = member->AsTSMethodSignature()->Variable();
if (!isInterface ||
ValidateInterfaceMemberRedeclaration(type, method, member->AsTSMethodSignature()->Key()->Start())) {
type->AddProperty(method->AsLocalVariable());
}
return;
}
if (member->IsTSSignatureDeclaration()) {
signatureDeclarations.push_back(member->AsTSSignatureDeclaration());
return;
}
ES2PANDA_ASSERT(member->IsTSIndexSignature());
indexDeclarations.push_back(member->AsTSIndexSignature());
}
void TSChecker::ResolveSignaturesOfObjectType(ObjectType *type,
std::vector<ir::TSSignatureDeclaration *> &signatureDeclarations)
{
for (auto *it : signatureDeclarations) {
Type *placeholderObj = it->Check(this);
if (it->AsTSSignatureDeclaration()->Kind() ==
ir::TSSignatureDeclaration::TSSignatureDeclarationKind::CALL_SIGNATURE) {
type->AddCallSignature(placeholderObj->AsObjectType()->CallSignatures()[0]);
continue;
}
type->AddConstructSignature(placeholderObj->AsObjectType()->ConstructSignatures()[0]);
}
}
void TSChecker::ResolveIndexInfosOfObjectType(ObjectType *type, std::vector<ir::TSIndexSignature *> &indexDeclarations)
{
for (auto *it : indexDeclarations) {
Type *placeholderObj = it->Check(this);
if (it->AsTSIndexSignature()->Kind() == ir::TSIndexSignature::TSIndexSignatureKind::NUMBER) {
IndexInfo *numberInfo = placeholderObj->AsObjectType()->NumberIndexInfo();
if (type->NumberIndexInfo() != nullptr) {
ThrowTypeError("Duplicated index signature for type 'number'", it->Start());
}
type->Desc()->numberIndexInfo = numberInfo;
continue;
}
IndexInfo *stringInfo = placeholderObj->AsObjectType()->StringIndexInfo();
if (type->StringIndexInfo() != nullptr) {
ThrowTypeError("Duplicated index signature for type 'string'", it->Start());
}
type->Desc()->stringIndexInfo = stringInfo;
}
}
varbinder::Variable *TSChecker::GetPropertyOfType(Type *type, const util::StringView &name, bool getPartial,
varbinder::VariableFlags propagateFlags)
{
if (type->IsObjectType()) {
ResolveObjectTypeMembers(type->AsObjectType());
return type->AsObjectType()->GetProperty(name, true);
}
if (type->IsUnionType()) {
return GetPropertyOfUnionType(type->AsUnionType(), name, getPartial, propagateFlags);
}
return nullptr;
}
varbinder::Variable *TSChecker::GetPropertyOfUnionType(UnionType *type, const util::StringView &name, bool getPartial,
varbinder::VariableFlags propagateFlags)
{
auto found = type->CachedSyntheticProperties().find(name);
if (found != type->CachedSyntheticProperties().end()) {
return found->second;
}
varbinder::VariableFlags flags = varbinder::VariableFlags::PROPERTY;
ArenaVector<Type *> collectedTypes(Allocator()->Adapter());
for (auto *it : type->ConstituentTypes()) {
varbinder::Variable *prop = GetPropertyOfType(it, name);
if (prop == nullptr) {
if (it->IsArrayType()) {
collectedTypes.push_back(it->AsArrayType()->ElementType());
continue;
}
if (!it->IsObjectType() && getPartial) {
continue;
}
if (!it->IsObjectType() && !getPartial) {
return nullptr;
}
ObjectType *objType = it->AsObjectType();
if (objType->StringIndexInfo() == nullptr && getPartial) {
continue;
}
if (objType->StringIndexInfo() == nullptr && !getPartial) {
return nullptr;
}
collectedTypes.push_back(objType->StringIndexInfo()->GetType());
continue;
}
prop->AddFlag(propagateFlags);
if (prop->HasFlag(varbinder::VariableFlags::OPTIONAL)) {
flags |= varbinder::VariableFlags::OPTIONAL;
}
collectedTypes.push_back(GetTypeOfVariable(prop));
}
if (collectedTypes.empty()) {
return nullptr;
}
varbinder::Variable *syntheticProp = varbinder::Scope::CreateVar(Allocator(), name, flags, nullptr);
ES2PANDA_ASSERT(syntheticProp != nullptr);
syntheticProp->SetTsType(CreateUnionType(std::move(collectedTypes)));
type->CachedSyntheticProperties().insert({name, syntheticProp});
return syntheticProp;
}
Type *TSChecker::CheckComputedPropertyName(ir::Expression *key)
{
if (key->TsType() != nullptr) {
return key->TsType();
}
Type *keyType = key->Check(this);
if (!keyType->HasTypeFlag(TypeFlag::STRING_LIKE | TypeFlag::NUMBER_LIKE)) {
ThrowTypeError(
"A computed property name in a type literal must refer to an expression whose type is a literal "
"type "
"or a 'unique symbol' type",
key->Start());
}
key->SetTsType(keyType);
return keyType;
}
IndexInfo *TSChecker::GetApplicableIndexInfo(Type *type, Type *indexType)
{
ResolveStructuredTypeMembers(type);
bool getNumberInfo = indexType->HasTypeFlag(TypeFlag::NUMBER_LIKE);
if (type->IsObjectType()) {
if (getNumberInfo) {
return type->AsObjectType()->NumberIndexInfo();
}
return type->AsObjectType()->StringIndexInfo();
}
if (type->IsUnionType()) {
ES2PANDA_ASSERT(type->AsUnionType()->MergedObjectType());
if (getNumberInfo) {
return type->AsUnionType()->MergedObjectType()->NumberIndexInfo();
}
return type->AsUnionType()->MergedObjectType()->StringIndexInfo();
}
return nullptr;
}
Type *TSChecker::GetPropertyTypeForIndexType(Type *type, Type *indexType)
{
ES2PANDA_ASSERT(type != nullptr);
if (type->IsArrayType()) {
return type->AsArrayType()->ElementType();
}
ES2PANDA_ASSERT(indexType != nullptr);
if (indexType->HasTypeFlag(TypeFlag::STRING_LITERAL | TypeFlag::NUMBER_LITERAL)) {
varbinder::Variable *prop = nullptr;
if (indexType->IsStringLiteralType()) {
prop = GetPropertyOfType(type, indexType->AsStringLiteralType()->Value());
} else {
util::StringView propName =
util::Helpers::ToStringView(Allocator(), indexType->AsNumberLiteralType()->Value());
prop = GetPropertyOfType(type, propName);
}
if (prop != nullptr) {
Type *propType = GetTypeOfVariable(prop);
if (prop->HasFlag(varbinder::VariableFlags::READONLY)) {
propType->AddTypeFlag(TypeFlag::READONLY);
}
return propType;
}
}
if (indexType->HasTypeFlag(TypeFlag::STRING_LIKE | TypeFlag::NUMBER_LIKE)) {
IndexInfo *indexInfo = GetApplicableIndexInfo(type, indexType);
if (indexInfo != nullptr) {
Type *indexInfoType = indexInfo->GetType();
if (indexInfo->Readonly()) {
indexInfoType->AddTypeFlag(TypeFlag::READONLY);
}
return indexInfoType;
}
}
return nullptr;
}
ArenaVector<ObjectType *> TSChecker::GetBaseTypes(InterfaceType *type)
{
if (type->HasObjectFlag(ObjectFlags::RESOLVED_BASE_TYPES)) {
return type->Bases();
}
ES2PANDA_ASSERT(type->Variable() && type->Variable()->Declaration()->IsInterfaceDecl());
varbinder::InterfaceDecl *decl = type->Variable()->Declaration()->AsInterfaceDecl();
TypeStackElement tse(this, type, {{diagnostic::RECURSIVE_EXTENSION, {type->Name()}}},
decl->Node()->AsTSInterfaceDeclaration()->Id()->Start());
if (tse.HasTypeError()) {
type->Bases().clear();
return type->Bases();
}
for (const auto *declaration : decl->Decls()) {
if (declaration->Extends().empty()) {
continue;
}
for (auto *extends : declaration->Extends()) {
Type *baseType = extends->Expr()->GetType(this);
ES2PANDA_ASSERT(baseType != nullptr);
if (!baseType->HasTypeFlag(TypeFlag::OBJECT | TypeFlag::NON_PRIMITIVE | TypeFlag::ANY)) {
ThrowTypeError(
"An interface can only extend an object type or intersection of object types with statically "
"known "
"members",
extends->Start());
}
if (!baseType->IsObjectType()) {
continue;
}
ObjectType *baseObj = baseType->AsObjectType();
if (baseType == type) {
ThrowTypeError({"Type ", type->Name(), " recursively references itself as a base type."},
decl->Node()->AsTSInterfaceDeclaration()->Id()->Start());
}
type->AddBase(baseObj);
if (!baseObj->IsInterfaceType()) {
continue;
}
CheckExtendsBases(baseObj, type, decl);
}
}
type->AddObjectFlag(ObjectFlags::RESOLVED_BASE_TYPES);
return type->Bases();
}
void TSChecker::CheckExtendsBases(ObjectType *&baseObj, InterfaceType *&type, varbinder::InterfaceDecl *&decl)
{
ArenaVector<ObjectType *> extendsBases = GetBaseTypes(baseObj->AsInterfaceType());
for (auto *extendBase : extendsBases) {
if (extendBase == type) {
ThrowTypeError({"Type ", type->Name(), " recursively references itself as a base type."},
decl->Node()->AsTSInterfaceDeclaration()->Id()->Start());
}
}
}
void TSChecker::ResolveDeclaredMembers(InterfaceType *type)
{
if (type->HasObjectFlag(ObjectFlags::RESOLVED_DECLARED_MEMBERS)) {
return;
}
ES2PANDA_ASSERT(type->Variable() && type->Variable()->Declaration()->IsInterfaceDecl());
varbinder::InterfaceDecl *decl = type->Variable()->Declaration()->AsInterfaceDecl();
std::vector<ir::TSSignatureDeclaration *> signatureDeclarations {};
std::vector<ir::TSIndexSignature *> indexDeclarations {};
for (const auto *declaration : decl->Decls()) {
for (auto *member : declaration->Body()->Body()) {
ResolvePropertiesOfObjectType(type, member, signatureDeclarations, indexDeclarations, true);
}
type->AddObjectFlag(ObjectFlags::RESOLVED_DECLARED_MEMBERS);
ResolveSignaturesOfObjectType(type, signatureDeclarations);
ResolveIndexInfosOfObjectType(type, indexDeclarations);
}
}
bool TSChecker::ValidateInterfaceMemberRedeclaration(ObjectType *type, varbinder::Variable *prop,
const lexer::SourcePosition &locInfo)
{
if (prop->HasFlag(varbinder::VariableFlags::COMPUTED)) {
return true;
}
varbinder::Variable *found = type->GetProperty(prop->Name(), false);
if (found == nullptr) {
return true;
}
Type *targetType = GetTypeOfVariable(prop);
Type *sourceType = GetTypeOfVariable(found);
IsTypeIdenticalTo(targetType, sourceType, diagnostic::DIFFERENT_SUBSEQ_PROP_DECL,
{prop->Name(), sourceType, targetType}, locInfo);
return false;
}
}