* 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 "ETSAnalyzer.h"
#include "checker/ETSchecker.h"
#include "checker/types/ets/etsObjectTypeConstants.h"
#include "checker/types/ets/etsTupleType.h"
#include "checker/types/globalTypesHolder.h"
#include "checker/types/typeError.h"
#include "checker/types/typeRelation.h"
#include "compiler/lowering/checkerPhase.h"
#include "compiler/lowering/ets/setJumpTarget.h"
#include "compiler/lowering/util.h"
#include "evaluate/scopedDebugInfoPlugin.h"
#include "ir/base/classProperty.h"
#include "ir/base/methodDefinition.h"
#include "ir/base/property.h"
#include "ir/ets/etsDestructuring.h"
namespace ark::es2panda::checker {
static Type *GetAppropriatePreferredType(Type *originalType, std::function<bool(Type *)> const &predicate);
ETSChecker *ETSAnalyzer::GetETSChecker() const
{
return static_cast<ETSChecker *>(GetChecker());
}
static bool IsInsideObjectLiteralMethod(const ir::AstNode *ast)
{
bool foundMethod = false;
for (const ir::AstNode *curr = ast->Parent(); curr != nullptr; curr = curr->Parent()) {
if (curr->IsProperty() && curr->AsProperty()->IsMethod()) {
foundMethod = true;
}
if (foundMethod && curr->IsObjectExpression()) {
return true;
}
}
return false;
}
static ETSObjectType *GetObjectLiteralMethodThisType(const ir::AstNode *ast)
{
if (!IsInsideObjectLiteralMethod(ast)) {
return nullptr;
}
auto *ancestor = util::Helpers::FindAncestorGivenByType(ast, ir::AstNodeType::OBJECT_EXPRESSION);
if (ancestor == nullptr) {
return nullptr;
}
auto *preferredType = ancestor->AsObjectExpression()->PreferredType();
if (preferredType == nullptr) {
return nullptr;
}
if (preferredType->IsETSTypeAliasType()) {
preferredType = preferredType->AsETSTypeAliasType()->GetTargetType();
}
if (preferredType->IsETSUnionType()) {
preferredType = GetAppropriatePreferredType(preferredType, [](Type *type) { return type->IsETSObjectType(); });
}
return preferredType != nullptr && preferredType->IsETSObjectType() ? preferredType->AsETSObjectType() : nullptr;
}
static bool IsNotInStructFieldInitializer(const ir::AstNode *node)
{
for (const ir::AstNode *current = node; current != nullptr; current = current->Parent()) {
if (current->IsClassDefinition() && current->AsClassDefinition()->IsFromStruct()) {
return false;
}
}
return true;
}
static bool IsExpressionInClassPropertyInitializer(const ir::Expression *expr)
{
for (const ir::AstNode *node = expr; node != nullptr && !node->IsClassDefinition(); node = node->Parent()) {
if (node->IsClassProperty() && IsNotInStructFieldInitializer(node)) {
return true;
}
}
return false;
}
static void LogNonExportedTypeError(ETSChecker *checker, checker::Type const *type, ir::AstNode const *decl)
{
if (!util::Helpers::IsExported(decl) && !util::Helpers::IsStdLib(decl->Program())) {
checker->LogError(diagnostic::USED_TYPE_IS_NOT_EXPORTED, {type->ToString()}, decl->Start());
}
}
static void CheckPartialTypeExport(ETSChecker *checker, checker::ETSObjectType const *partialType)
{
auto *baseType = partialType->GetBaseType();
if (baseType == nullptr || baseType->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE)) {
return;
}
LogNonExportedTypeError(checker, static_cast<Type const *>(baseType), baseType->GetDeclNode());
}
static void CheckExport(ETSChecker *checker, checker::Type const *type)
{
if (type == nullptr || type->IsTypeError()) {
return;
}
auto const checkExported = [checker](Type const *testType) {
if (testType->IsETSObjectType()) {
if (testType->AsETSObjectType()->IsPartial()) {
CheckPartialTypeExport(checker, testType->AsETSObjectType());
return;
}
if (testType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE)) {
return;
}
LogNonExportedTypeError(checker, testType, testType->AsETSObjectType()->GetDeclNode());
return;
}
if (testType->IsETSTypeAliasType()) {
LogNonExportedTypeError(checker, testType, testType->AsETSTypeAliasType()->GetDeclNode());
}
};
type->IterateRecursively(checkExported);
}
static void CheckExportForTypeParams(ETSChecker *checker, ir::TSTypeParameterDeclaration *typeParams)
{
if (typeParams == nullptr) {
return;
}
for (auto *typeParam : typeParams->Params()) {
auto *constraintType = typeParam->Constraint();
if (constraintType != nullptr) {
CheckExport(checker, constraintType->GetType(checker));
}
auto *defaultType = typeParam->DefaultType();
if (defaultType != nullptr) {
CheckExport(checker, defaultType->GetType(checker));
}
}
}
static void CheckExportForTypeAlias(checker::ETSChecker *checker, const ir::AstNode *node)
{
if (!node->IsETSTypeReferencePart()) {
return;
}
auto *typeRef = node->AsETSTypeReferencePart();
auto *baseName = typeRef->Name();
if (baseName == nullptr || baseName->Variable() == nullptr) {
return;
}
auto *declNode = baseName->Variable()->Declaration()->Node();
if (declNode->IsTSTypeAliasDeclaration() && !util::Helpers::IsExported(declNode)) {
checker->LogError(diagnostic::USED_TYPE_IS_NOT_EXPORTED, {baseName->AsIdentifier()->Name().Utf8()},
baseName->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::CatchClause *st) const
{
ETSChecker *checker = GetETSChecker();
checker::Type *exceptionType = checker->GlobalTypeError();
if (st->Param() != nullptr) {
ES2PANDA_ASSERT(st->Param()->IsIdentifier());
ir::Identifier *paramIdent = st->Param()->AsIdentifier();
if (!paramIdent->IsErrorPlaceHolder()) {
if (paramIdent->TypeAnnotation() != nullptr) {
checker::Type *catchParamAnnotationType = paramIdent->TypeAnnotation()->GetType(checker);
exceptionType = checker->CheckExceptionOrErrorType(catchParamAnnotationType, st->Param()->Start());
} else {
exceptionType = checker->GlobalBuiltinErrorType();
}
paramIdent->Variable()->SetTsType(exceptionType);
}
paramIdent->SetTsType(exceptionType);
} else {
ES2PANDA_ASSERT(checker->IsAnyError());
}
const varbinder::Variable *catchVar = nullptr;
if (st->Param() != nullptr && st->Param()->IsIdentifier()) {
catchVar = st->Param()->AsIdentifier()->Variable();
ES2PANDA_ASSERT(catchVar != nullptr);
catchParamStack_.push_back(catchVar);
}
st->Body()->Check(checker);
if (catchVar != nullptr) {
catchParamStack_.pop_back();
}
return st->SetTsType(exceptionType);
}
checker::Type *ETSAnalyzer::Check(ir::ClassDefinition *node) const
{
ETSChecker *checker = GetETSChecker();
if (node->TsType() == nullptr) {
checker->BuildBasicClassProperties(node);
}
if (!node->IsClassDefinitionChecked()) {
checker->CheckClassDefinition(node);
}
if ((node->IsExported() || node->IsDefaultExported()) && node->TsType() != nullptr) {
const auto *classType = node->TsType();
if (classType->IsETSObjectType() && classType->AsETSObjectType()->SuperType() != nullptr) {
CheckExport(checker, classType->AsETSObjectType()->SuperType());
}
CheckExportForTypeParams(checker, node->TypeParams());
}
return node->TsType();
}
static void CheckOverridenFieldImpl(ir::ClassProperty *st, ETSChecker *checker, ir::ClassDefinition *classDef,
varbinder::LocalVariable *propVar, util::StringView superTypeName)
{
auto *propNode = propVar->Declaration()->Node();
propNode->Check(checker);
if (propNode->IsPrivate()) {
if (st->IsOverride()) {
checker->LogError(diagnostic::OVERRIDE_NOT_PRIVATE, {propVar->Declaration()->Name(), superTypeName},
st->Start());
}
return;
}
if (st->HasAnnotations()) {
checker->LogError(diagnostic::CANNOT_ANNOTATE, {propVar->Declaration()->Name(), superTypeName}, st->Start());
}
size_t baseAccessLevel = propNode->IsPrivate() ? 0 : propNode->IsProtected() ? 1 : 2;
size_t derivedAccessLevel = st->IsPrivate() ? 0 : st->IsProtected() ? 1 : 2;
if (baseAccessLevel != derivedAccessLevel) {
checker->LogError(diagnostic::ACCESS_MODIFIER_MISMATCH,
{st->Id()->Name(), classDef->Ident()->Name(), superTypeName}, st->Start());
}
if (!st->TsType()->IsETSTypeParameter() && propVar->Declaration()->Node()->IsClassProperty()) {
auto *baseProp = propVar->Declaration()->Node()->AsClassProperty();
if (baseProp->TsType() != nullptr && baseProp->TsType()->IsETSTypeParameter()) {
return;
}
}
if (!checker->Relation()->IsIdenticalTo(propVar->TsType(), st->TsType())) {
checker->LogError(diagnostic::INCOMPATIBLE_TYPE_FOR_OVERRIDE,
{st->Id()->Name(), "", classDef->Ident()->Name(), propVar->Name(), "", superTypeName},
st->Start());
}
st->SetOverride();
st->SetBasePropertyVar(propVar);
if (propNode->IsDefinite()) {
st->AddModifier(ir::ModifierFlags::DEFINITE);
} else {
st->ClearModifier(ir::ModifierFlags::DEFINITE);
}
}
static bool CheckFieldInitializationInBody(ir::AstNode *body, ir::ClassProperty *st)
{
bool isInitialized = false;
body->IterateRecursively([&](ir::AstNode *node) {
if (isInitialized) {
return;
}
if (!node->IsAssignmentExpression()) {
return;
}
auto *assign = node->AsAssignmentExpression();
auto *left = assign->Left();
if (left->IsMemberExpression()) {
auto *member = left->AsMemberExpression();
if (member->Object() && member->Object()->IsThisExpression() && member->Property() &&
member->Property()->IsIdentifier() && member->Property()->AsIdentifier()->Name() == st->Id()->Name()) {
isInitialized = true;
}
} else if (left->IsIdentifier() && left->AsIdentifier()->Name() == st->Id()->Name()) {
isInitialized = true;
}
});
return isInitialized;
}
static bool CheckOverridenFieldInitialization(ir::ClassProperty *st, ir::ClassDefinition *classDef)
{
auto modifiers = st->Modifiers();
const bool isDefinite = (modifiers & ir::ModifierFlags::DEFINITE) != 0;
if (isDefinite) {
return true;
}
if (st->Value() != nullptr) {
return true;
}
for (const auto it : classDef->Body()) {
if (!it->IsMethodDefinition()) {
continue;
}
auto *methodDef = it->AsMethodDefinition();
if (!methodDef->IsConstructor() || !methodDef->IsDefaultAccessModifier()) {
continue;
}
auto *body = methodDef->Function()->Body();
if (body == nullptr) {
continue;
}
bool isInitialized = CheckFieldInitializationInBody(body, st);
if (isInitialized) {
return true;
}
}
return false;
}
static void CheckOverridenField(ir::ClassProperty *st, ETSChecker *checker, ir::ClassDefinition *classDef)
{
auto *superType = classDef->Super()->TsType()->AsETSObjectType();
ES2PANDA_ASSERT(classDef->Super() != nullptr && superType != nullptr && !superType->IsGradual());
varbinder::LocalVariable *propVar = nullptr;
while (superType != nullptr) {
propVar = superType->GetProperty(st->Id()->Name(), PropertySearchFlags::SEARCH_INSTANCE_FIELD);
if (propVar != nullptr) {
break;
}
superType = superType->SuperType();
}
if (superType == nullptr) {
if (st->IsOverride()) {
checker->LogError(diagnostic::OVERRIDE_NOT_IN_BASE,
{classDef->Super()->TsType()->AsETSObjectType()->Name()}, st->Start());
}
ES2PANDA_ASSERT(st->BasePropertyVar() == nullptr);
return;
}
bool isInitialized = CheckOverridenFieldInitialization(st, classDef);
if (!isInitialized) {
checker->LogError(diagnostic::OVERRIDE_FIELD_MUST_HAVE_INITIALIZER, {st->Id()->Name()}, st->Start());
return;
}
ES2PANDA_ASSERT(propVar != nullptr);
CheckOverridenFieldImpl(st, checker, classDef, propVar, superType->AsETSObjectType()->Name());
}
static void CheckFieldOverride(ir::ClassProperty *st, ETSChecker *checker)
{
if (st->IsStatic()) {
if (st->IsOverride()) {
checker->LogError(diagnostic::STATIC_OVERRIDE, {st->Id()->Name()}, st->Start());
}
return;
}
auto *parent = st->Parent();
if (parent == nullptr || !parent->IsClassDefinition()) {
return;
}
ir::ClassDefinition *classDef = parent->AsClassDefinition();
util::StringView subClassName = classDef->Ident()->Name();
if (classDef->Super() == nullptr) {
if (st->IsOverride()) {
checker->LogError(diagnostic::OVERRIDE_NOT_EXTENDS, {subClassName}, st->Start());
}
return;
}
if (classDef->Super() != nullptr && classDef->Super()->TsType() != nullptr &&
classDef->Super()->TsType()->IsETSObjectType()) {
CheckOverridenField(st, checker, classDef);
}
}
static void TypeAnnoCheckForExportedMethod(checker::ETSChecker *checker, ir::MethodDefinition *methodDef)
{
if (!methodDef->IsConstructor() && !methodDef->Function()->IsSetter() &&
methodDef->Function()->ReturnTypeAnnotation() == nullptr) {
checker->LogError(diagnostic::EXPORTED_ENTITIES_DOESNOT_HAS_TYPEANNO, {"function", methodDef->Id()->Name()},
methodDef->Start());
}
}
static void TypeAnnoCheckForExportedClassProp(checker::ETSChecker *checker, ir::ClassProperty *classProp)
{
if (classProp->TypeAnnotation() == nullptr && !classProp->Parent()->IsAnnotationUsage() &&
classProp->Id()->Name() != compiler::Signatures::REEXPORT_DEFAULT_ANONYMOUSLY) {
const std::string entityType = util::Helpers::IsGlobalClass(checker->Context().ContainingClass()->GetDeclNode())
? "variable"
: "class property";
checker->LogError(diagnostic::EXPORTED_ENTITIES_DOESNOT_HAS_TYPEANNO, {entityType, classProp->Id()->Name()},
classProp->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::ClassProperty *st) const
{
if (st->TsType() != nullptr) {
return st->TsType();
}
ES2PANDA_ASSERT(st->Id() != nullptr);
ETSChecker *checker = GetETSChecker();
if (st->Id()->Variable() == nullptr) {
auto ident = st->Id();
auto [decl, var] = checker->VarBinder()->NewVarDecl<varbinder::LetDecl>(
ident->Start(), compiler::GenName(checker->ProgramAllocator()).View());
var->SetScope(checker->VarBinder()->GetScope());
ident->SetVariable(var);
decl->BindNode(ident);
ident->SetTsType(var->SetTsType(checker->GlobalTypeError()));
}
ES2PANDA_ASSERT(st->Id()->Variable() != nullptr);
checker->CheckAnnotations(st);
if (st->TypeAnnotation() != nullptr) {
st->TypeAnnotation()->Check(checker);
}
checker::SavedCheckerContext savedContext(checker, checker->Context().Status(),
checker->Context().ContainingClass(),
checker->Context().ContainingSignature());
if (st->IsStatic()) {
checker->AddStatus(checker::CheckerStatus::IN_STATIC_CONTEXT);
}
checker::Type *propertyType =
checker->CheckVariableDeclaration(st->Id(), st->TypeAnnotation(), st->Value(), st->Modifiers());
propertyType = propertyType != nullptr ? propertyType : checker->GlobalTypeError();
st->SetTsType(propertyType);
if (st->IsDefinite() && propertyType->PossiblyETSNullish()) {
checker->LogError(diagnostic::LATE_INITIALIZATION_FIELD_HAS_INVALID_TYPE, st->TypeAnnotation()->Start());
}
CheckFieldOverride(st, checker);
if (!st->IsPrivate() && util::Helpers::IsExported(st)) {
CheckExport(checker, propertyType);
TypeAnnoCheckForExportedClassProp(checker, st);
}
return propertyType;
}
checker::Type *ETSAnalyzer::Check(ir::ClassStaticBlock *st) const
{
ETSChecker *checker = GetETSChecker();
if (checker->HasStatus(checker::CheckerStatus::INNER_CLASS)) {
checker->LogError(diagnostic::STATIC_INIT_IN_NESTED_CLASS, {}, st->Start());
st->SetTsType(checker->GlobalTypeError());
return st->TsType();
}
auto *func = st->Function();
checker->BuildFunctionSignature(func);
if (func->Signature() == nullptr) {
st->SetTsType(checker->GlobalTypeError());
} else {
st->SetTsType(checker->BuildMethodType(func));
}
if (!func->HasBody() || (func->IsExternal() && !func->IsExternalOverload())) {
return st->TsType();
}
checker::ScopeContext scopeCtx(checker, func->Scope());
checker::SavedCheckerContext savedContext(checker, checker->Context().Status(),
checker->Context().ContainingClass());
checker->AddStatus(checker::CheckerStatus::IN_STATIC_BLOCK | checker::CheckerStatus::IN_STATIC_CONTEXT);
func->Body()->Check(checker);
return st->TsType();
}
static void CheckAsyncFunctionReturnType(ETSChecker *checker, ir::ScriptFunction *scriptFunc)
{
ES2PANDA_ASSERT(scriptFunc);
if (!scriptFunc->IsAsyncFunc() || scriptFunc->IsProxy() || scriptFunc->Signature() == nullptr) {
return;
}
* NOTE(knazarov): To not break compatibility with existing behaviour,
* we keep return type of the AsyncImpl methods as Object, so here we only check
* AsyncFunc itself.
*/
auto *asyncFuncReturnType = scriptFunc->Signature()->ReturnType();
ES2PANDA_ASSERT(asyncFuncReturnType);
if (!asyncFuncReturnType->IsETSObjectType() || !checker->IsPromiseType(asyncFuncReturnType->AsETSObjectType())) {
checker->LogError(diagnostic::ASYNC_FUNCTION_RETURN_TYPE, {}, scriptFunc->Start());
scriptFunc->Signature()->SetReturnType(checker->GlobalTypeError());
return;
}
}
static checker::Type *CheckMethodDefinitionHelper(ETSChecker *checker, ir::MethodDefinition *method) noexcept
{
auto *const methodType = method->TsType();
if ((method->Parent()->Modifiers() & ir::ModifierFlags::FUNCTIONAL) == 0) {
checker->CheckOverride(methodType->AsETSFunctionType()->FindSignature(method->Function()));
}
for (auto *overload : method->Overloads()) {
overload->Check(checker);
}
if (!method->IsPrivate() && method->Function() != nullptr && util::Helpers::IsExported(method) &&
method->Id()->Name().Utf8().find("lambda_invoke-") == std::string_view::npos) {
CheckExport(checker, methodType);
CheckExportForTypeParams(checker, method->Function()->TypeParams());
TypeAnnoCheckForExportedMethod(checker, method);
}
return methodType;
}
static bool IsInitializerBlockTransfer(std::string_view str)
{
auto prefix = compiler::Signatures::INITIALIZER_BLOCK_INIT;
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
}
static bool IsSignatureUnreachable(ETSChecker *checker, Signature *currSig, Signature *prevSig,
std::optional<lexer::SourcePosition> start_pos)
{
SavedTypeRelationFlagsContext savedFlagsCtx(checker->Relation(), TypeRelationFlag::NO_RETURN_TYPE_CHECK);
if (checker->Relation()->SignatureIsCoveredBy(currSig, prevSig)) {
auto start = start_pos.has_value() ? start_pos.value() : currSig->Function()->Id()->Start();
checker->LogError(diagnostic::OVERLOAD_UNREACHABLE_WARNING, {currSig->ToString(), prevSig->ToString()}, start);
return true;
}
return false;
}
static Type *CheckUnreachableSignatureInFunctionType(
ETSChecker *checker, Type *type,
std::optional<lexer::SourcePosition> start_pos = std::optional<lexer::SourcePosition>())
{
if (type == nullptr || !type->IsETSFunctionType()) {
return type;
}
auto &signatures = type->AsETSFunctionType()->CallSignatures();
for (size_t j = 1; j < signatures.size(); ++j) {
for (size_t i = 0; i < j; ++i) {
auto *currSig = signatures[j];
auto *prevSig = signatures[i];
if (IsSignatureUnreachable(checker, currSig, prevSig, start_pos)) {
break;
}
}
}
return type;
}
checker::Type *ETSAnalyzer::Check(ir::MethodDefinition *node) const
{
ETSChecker *checker = GetETSChecker();
auto *scriptFunc = node->Function();
auto const returnErrorType = [checker, node]() -> checker::Type * {
node->SetTsType(checker->GlobalTypeError());
return node->TsType();
};
checker->CheckAnnotations(scriptFunc);
checker->CheckFunctionSignatureAnnotations(scriptFunc->Params(), scriptFunc->TypeParams(),
scriptFunc->ReturnTypeAnnotation());
if (scriptFunc->IsProxy()) {
return ReturnTypeForStatement(node);
}
ES2PANDA_ASSERT(!(scriptFunc->IsGetter() && scriptFunc->IsSetter()));
if (scriptFunc->IsGetter() || scriptFunc->IsSetter()) {
auto status = scriptFunc->IsGetter() ? CheckerStatus::IN_GETTER : CheckerStatus::IN_SETTER;
checker->AddStatus(status);
}
if (!scriptFunc->HasBody() && !(node->IsAbstract() || node->IsNative() || node->IsDeclare() ||
checker->HasStatus(checker::CheckerStatus::IN_INTERFACE))) {
checker->LogError(diagnostic::FUNCTION_WITHOUT_BODY, {}, scriptFunc->Start());
return returnErrorType();
}
if (CheckReturnTypeNecessity(node) && scriptFunc->ReturnTypeAnnotation() == nullptr) {
checker->LogError(diagnostic::MISSING_RETURN_TYPE, {}, scriptFunc->Start());
return returnErrorType();
}
if (node->TsType() == nullptr) {
node->SetTsType(checker->BuildMethodSignature(node));
}
if (IsInitializerBlockTransfer(scriptFunc->Id()->Name().Utf8())) {
checker->AddStatus(CheckerStatus::IN_STATIC_BLOCK);
}
if (node->TsType() != nullptr && node->TsType()->IsTypeError()) {
return node->TsType();
}
this->CheckMethodModifiers(node);
CheckAsyncFunctionReturnType(checker, scriptFunc);
DoBodyTypeChecking(checker, node, scriptFunc);
CheckPredefinedMethodReturnType(checker, scriptFunc);
if (node->TsType()->IsTypeError()) {
return node->TsType();
}
auto *type = CheckMethodDefinitionHelper(checker, node);
return CheckUnreachableSignatureInFunctionType(checker, type);
}
void ETSAnalyzer::CheckMethodModifiers(ir::MethodDefinition *node) const
{
ETSChecker *checker = GetETSChecker();
auto const notValidInAbstract = ir::ModifierFlags::NATIVE | ir::ModifierFlags::PRIVATE |
ir::ModifierFlags::OVERRIDE | ir::ModifierFlags::FINAL | ir::ModifierFlags::STATIC;
if (node->IsAbstract() && (node->Modifiers() & notValidInAbstract) != 0U) {
checker->LogError(diagnostic::ABSTRACT_METHOD_INVALID_MODIFIER, {}, node->Start());
node->SetTsType(checker->GlobalTypeError());
return;
}
if ((node->IsAbstract() || (!node->Function()->HasBody() && !node->IsNative() && !node->IsDeclare())) &&
!(checker->HasStatus(checker::CheckerStatus::IN_ABSTRACT) ||
checker->HasStatus(checker::CheckerStatus::IN_INTERFACE))) {
checker->LogError(diagnostic::ABSTRACT_IN_CONCRETE, {}, node->Start());
node->SetTsType(checker->GlobalTypeError());
}
auto const notValidInFinal = ir::ModifierFlags::ABSTRACT | ir::ModifierFlags::STATIC;
if (node->IsFinal() && (node->Modifiers() & notValidInFinal) != 0U) {
checker->LogError(diagnostic::FINAL_METHOD_INVALID_MODIFIER, {}, node->Start());
node->SetTsType(checker->GlobalTypeError());
}
auto const notValidInStatic = ir::ModifierFlags::ABSTRACT | ir::ModifierFlags::FINAL | ir::ModifierFlags::OVERRIDE;
if (node->IsStatic() && (node->Modifiers() & notValidInStatic) != 0U) {
checker->LogError(diagnostic::STATIC_METHOD_INVALID_MODIFIER, {}, node->Start());
node->SetTsType(checker->GlobalTypeError());
}
}
static void CheckDuplicationInOverloadDeclaration(ETSChecker *const checker, ir::OverloadDeclaration *const node)
{
auto overloadedNameSet = ArenaSet<std::string>(checker->ProgramAllocator()->Adapter());
for (ir::Expression *const overloadedName : node->OverloadedList()) {
bool isQualifiedName = true;
std::function<std::string(ir::Expression *const)> getFullOverloadedName =
[&isQualifiedName, &getFullOverloadedName](ir::Expression *const expr) -> std::string {
if (!isQualifiedName) {
return "";
}
if (expr->IsIdentifier()) {
return expr->AsIdentifier()->Name().Mutf8();
}
if (expr->IsMemberExpression()) {
return getFullOverloadedName(expr->AsMemberExpression()->Object()) + "." +
getFullOverloadedName(expr->AsMemberExpression()->Property());
}
isQualifiedName = false;
return "";
};
std::string fullOverloadedName = getFullOverloadedName(overloadedName);
if (!isQualifiedName) {
continue;
}
if (overloadedNameSet.find(fullOverloadedName) != overloadedNameSet.end()) {
checker->LogError(diagnostic::DUPLICATE_OVERLOADED_NAME, overloadedName->Start());
continue;
}
overloadedNameSet.insert(fullOverloadedName);
}
}
static void CheckOverloadSameNameMethod(ETSChecker *const checker, ir::OverloadDeclaration *const overloadDecl)
{
Type *objectType = overloadDecl->Parent()->IsClassDefinition()
? overloadDecl->Parent()->AsClassDefinition()->Check(checker)
: overloadDecl->Parent()->Parent()->AsTSInterfaceDeclaration()->Check(checker);
ES2PANDA_ASSERT(objectType->IsETSObjectType());
if (!overloadDecl->IsClassMethodOverloadDeclaration() && !overloadDecl->IsFunctionOverloadDeclaration() &&
!overloadDecl->IsInterfaceMethodOverloadDeclaration()) {
return;
}
PropertySearchFlags searchFlags = PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION |
(overloadDecl->IsStatic() || overloadDecl->IsFunctionOverloadDeclaration()
? PropertySearchFlags::SEARCH_STATIC_METHOD
: PropertySearchFlags::SEARCH_INSTANCE_METHOD);
if (!overloadDecl->IsStatic() && overloadDecl->IsClassMethodOverloadDeclaration()) {
searchFlags |= PropertySearchFlags::SEARCH_IN_INTERFACES | PropertySearchFlags::SEARCH_IN_BASE;
}
auto *sameNameMethod = objectType->AsETSObjectType()->GetProperty(overloadDecl->Id()->Name(), searchFlags);
if (sameNameMethod == nullptr) {
return;
}
auto *methodOwner = static_cast<checker::ETSObjectType *>(nullptr);
if (sameNameMethod->TsType() != nullptr && sameNameMethod->TsType()->IsETSFunctionType() &&
!sameNameMethod->TsType()->AsETSFunctionType()->CallSignatures().empty()) {
methodOwner = sameNameMethod->TsType()->AsETSFunctionType()->CallSignatures().front()->Owner();
if (sameNameMethod->HasFlag(varbinder::VariableFlags::PRIVATE) &&
methodOwner != objectType->AsETSObjectType()) {
return;
}
}
auto serachName = overloadDecl->Id()->Name().Mutf8();
auto hasSameNameMethod =
std::find_if(overloadDecl->OverloadedList().begin(), overloadDecl->OverloadedList().end(),
[serachName](ir::Expression *overloadId) {
return overloadId->IsIdentifier() && overloadId->AsIdentifier()->Name().Is(serachName);
});
if (hasSameNameMethod == overloadDecl->OverloadedList().end()) {
checker->LogError(diagnostic::OVERLOAD_SAME_NAME_METHOD, {serachName}, overloadDecl->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::OverloadDeclaration *node) const
{
ETSChecker *checker = GetETSChecker();
ES2PANDA_ASSERT(node != nullptr);
ES2PANDA_ASSERT(node->Key());
CheckDuplicationInOverloadDeclaration(checker, node);
CheckOverloadSameNameMethod(checker, node);
if (node->IsConstructorOverloadDeclaration()) {
ES2PANDA_ASSERT(node->Parent()->IsClassDefinition());
checker->CheckConstructorOverloadDeclaration(checker, node);
} else if (node->IsFunctionOverloadDeclaration()) {
ES2PANDA_ASSERT(
node->Parent()->IsClassDefinition() &&
(compiler::HasGlobalClassParent(node) || node->Parent()->AsClassDefinition()->IsNamespaceTransformed()));
checker->CheckFunctionOverloadDeclaration(checker, node);
} else if (node->IsClassMethodOverloadDeclaration()) {
ES2PANDA_ASSERT(node->Parent()->IsClassDefinition());
checker->CheckClassMethodOverloadDeclaration(checker, node);
} else if (node->IsInterfaceMethodOverloadDeclaration()) {
ES2PANDA_ASSERT(node->Parent()->Parent()->IsTSInterfaceDeclaration());
checker->CheckInterfaceMethodOverloadDeclaration(checker, node);
}
auto *type = checker->CreateSyntheticTypeFromOverload(node->Id()->Variable());
return CheckUnreachableSignatureInFunctionType(checker, type, node->Id()->Start());
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::Property *expr) const
{
ETSChecker *checker = GetETSChecker();
return checker->GlobalTypeError();
}
static ir::SpreadElement::ResolvedSpreadKind GetResolvedSpreadKind(ETSChecker *checker, Type *type)
{
auto *const normalizedType = checker->NormalizeSpreadType(type);
if (normalizedType->IsTypeError()) {
return ir::SpreadElement::ResolvedSpreadKind::INVALID;
}
if (normalizedType->IsETSTupleType()) {
return ir::SpreadElement::ResolvedSpreadKind::TUPLE;
}
if (normalizedType->IsETSUnionType()) {
return ir::SpreadElement::ResolvedSpreadKind::ITERABLE;
}
if (normalizedType->IsETSStringType() || util::Helpers::IsArrayType(normalizedType)) {
return ir::SpreadElement::ResolvedSpreadKind::INDEXABLE;
}
return ir::SpreadElement::ResolvedSpreadKind::ITERABLE;
}
static void SetResolvedSpread(ETSChecker *checker, ir::SpreadElement *expr, Type *sourceType)
{
auto const kind = GetResolvedSpreadKind(checker, sourceType);
auto *const elementType = kind == ir::SpreadElement::ResolvedSpreadKind::INVALID
? checker->GlobalTypeError()
: checker->GetElementTypeOfSpreadType(sourceType);
expr->SetResolvedSpread(kind, sourceType, elementType);
}
checker::Type *ETSAnalyzer::Check(ir::SpreadElement *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
if (expr->GetResolvedSpreadKind() == ir::SpreadElement::ResolvedSpreadKind::INVALID &&
checker->IsValidSpreadType(expr->TsType())) {
SetResolvedSpread(checker, expr, expr->TsType());
}
return expr->TsType();
}
if (expr->PreferredType() != nullptr) {
expr->Argument()->SetPreferredType(expr->PreferredType());
}
auto const exprType = expr->Argument()->Check(checker);
if (!checker->IsValidSpreadType(exprType)) {
if (!exprType->IsTypeError()) {
checker->LogError(diagnostic::SPREAD_OF_INVALID_TYPE, {exprType}, expr->Start());
}
expr->SetResolvedSpread(ir::SpreadElement::ResolvedSpreadKind::INVALID, checker->GlobalTypeError(),
checker->GlobalTypeError());
return checker->InvalidateType(expr);
}
SetResolvedSpread(checker, expr, exprType);
return expr->SetTsType(exprType);
}
checker::Type *ETSAnalyzer::Check(ir::TemplateElement *expr) const
{
ETSChecker *checker = GetETSChecker();
expr->SetTsType(checker->CreateETSStringLiteralType(expr->Raw()));
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::ETSClassLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
auto *const literal = expr->Expr();
checker->LogError(diagnostic::UNSUPPORTED_CLASS_LITERAL, {}, literal->Start());
expr->SetTsType(checker->GlobalTypeError());
return expr->TsType();
auto exprType = literal->Check(checker);
ArenaVector<checker::Type *> typeArgTypes(checker->ProgramAllocator()->Adapter());
typeArgTypes.push_back(exprType);
checker::InstantiationContext ctx(checker, checker->GlobalBuiltinTypeType(), std::move(typeArgTypes),
expr->Range().start);
expr->SetTsType(ctx.Result());
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSIntrinsicNode *node) const
{
ES2PANDA_UNREACHABLE();
}
checker::Type *ETSAnalyzer::Check(ir::ETSFunctionType *node) const
{
if (node->TsType() != nullptr) {
return node->TsType();
}
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
checker->CheckFunctionSignatureAnnotations(node->Params(), node->TypeParams(), node->ReturnType());
auto *signatureInfo = checker->ComposeSignatureInfo(node->TypeParams(), node->Params());
if (signatureInfo == nullptr) {
ES2PANDA_ASSERT(GetChecker()->IsAnyError());
return node->SetTsType(checker->GlobalTypeError());
}
auto *returnType = node->IsExtensionFunction() && node->ReturnType()->IsTSThisType()
? signatureInfo->params.front()->TsType()
: checker->ComposeReturnType(node->ReturnType(), node->IsAsync());
auto *const signature =
checker->CreateSignature(signatureInfo, returnType, node->Flags(), node->IsExtensionFunction());
if (signature == nullptr) {
ES2PANDA_ASSERT(GetChecker()->IsAnyError());
return node->SetTsType(checker->GlobalTypeError());
}
signature->SetOwner(checker->Context().ContainingClass());
return node->SetTsType(checker->CreateETSArrowType(signature));
}
static Signature *ValidateParameterlessConstructor(ETSChecker *checker, Signature *signature,
const lexer::SourcePosition &pos, bool throwError)
{
if (signature->MinArgCount() != 0) {
if (throwError) {
checker->LogError(diagnostic::NO_SUCH_PARAMLESS_CTOR_2, {signature->MinArgCount()}, pos);
}
return nullptr;
}
return signature;
}
static Signature *CollectParameterlessConstructor(ETSChecker *checker, ArenaVector<Signature *> &signatures,
const lexer::SourcePosition &pos)
{
bool throwError = signatures.size() == 1;
for (auto *sig : signatures) {
if (auto *concreteSig = ValidateParameterlessConstructor(checker, sig, pos, throwError);
concreteSig != nullptr) {
return concreteSig;
}
}
checker->LogError(diagnostic::NO_SUCH_PARAMLESS_CTOR, {}, pos);
return nullptr;
}
static bool HasBareTypeParameter(checker::Type const *elementType)
{
if (elementType->IsETSTypeParameter()) {
return true;
}
if (elementType->IsETSUnionType()) {
return false;
}
return elementType->TypeExpressionContains([](checker::Type const *tp) { return tp->IsETSTypeParameter(); });
}
static bool IsTypeNotPreservedByErasure(checker::Type const *elementType, bool isFirstCall = true);
template <typename Container>
static bool AnyChildNotPreserved(const Container &children, bool isFirstCall)
{
for (auto *child : children) {
if (IsTypeNotPreservedByErasure(child, isFirstCall)) {
return true;
}
}
return false;
}
static bool CheckTopLevelUnion(checker::Type const *elementType)
{
bool hasTypeParam = false;
bool hasConcrete = false;
for (auto *ct : elementType->AsETSUnionType()->ConstituentTypes()) {
if (ct->IsETSUndefinedType() || ct->IsETSVoidType()) {
continue;
}
if (ct->IsETSTypeParameter()) {
hasTypeParam = true;
} else {
hasConcrete = true;
if (IsTypeNotPreservedByErasure(ct)) {
hasTypeParam = true;
}
}
}
return hasTypeParam && hasConcrete;
}
static bool IsTypeNotPreservedByErasure(checker::Type const *elementType, bool isFirstCall)
{
if (elementType->IsETSTypeParameter()) {
return false;
}
if (elementType->IsETSArrayType() || elementType->IsETSReadonlyArrayType()) {
return false;
}
if (elementType->IsETSFunctionType()) {
return true;
}
if (elementType->IsETSUnionType()) {
return isFirstCall ? CheckTopLevelUnion(elementType)
: AnyChildNotPreserved(elementType->AsETSUnionType()->ConstituentTypes(), false);
}
if (elementType->IsETSTupleType()) {
return AnyChildNotPreserved(elementType->AsETSTupleType()->GetTupleTypesList(), false);
}
if (elementType->IsETSObjectType() && !elementType->AsETSObjectType()->TypeArguments().empty()) {
return AnyChildNotPreserved(elementType->AsETSObjectType()->TypeArguments(), false);
}
return !isFirstCall;
}
template <typename T, typename = typename std::enable_if_t<std::is_base_of_v<ir::Expression, T>>>
static bool CheckArrayElementType(ETSChecker *checker, T *newArrayInstanceExpr, checker::Type *elementType)
{
ES2PANDA_ASSERT(checker != nullptr);
ES2PANDA_ASSERT(newArrayInstanceExpr != nullptr);
ES2PANDA_ASSERT(elementType != nullptr);
ES2PANDA_ASSERT(!elementType->IsETSPrimitiveType());
if (IsTypeNotPreservedByErasure(elementType)) {
checker->LogError(diagnostic::TYPE_NOT_PRESERVED_BY_ERASURE_FOR_FIXED_ARRAY, {elementType},
newArrayInstanceExpr->Start());
return false;
}
if (elementType->IsETSObjectType()) {
auto *calleeObj = elementType->AsETSObjectType();
const auto flags = checker::ETSObjectFlags::ABSTRACT | checker::ETSObjectFlags::INTERFACE;
if (!calleeObj->HasObjectFlag(flags)) {
newArrayInstanceExpr->SetSignature(CollectParameterlessConstructor(
checker, calleeObj->ConstructSignatures(), newArrayInstanceExpr->Start()));
checker->ValidateSignatureAccessibility(calleeObj, newArrayInstanceExpr->Signature(),
newArrayInstanceExpr->Start());
} else {
checker->LogError(diagnostic::ABSTRACT_CLASS_AS_ARRAY_ELEMENT_TYPE, {}, newArrayInstanceExpr->Start());
return false;
}
} else {
if (HasBareTypeParameter(elementType)) {
checker->LogError(diagnostic::TYPE_PARAMETER_AS_ARRAY_ELEMENT_TYPE, {}, newArrayInstanceExpr->Start());
return false;
}
if (!checker->Relation()->IsSupertypeOf(elementType, checker->GlobalETSUndefinedType()) &&
!checker->Relation()->IsIdenticalTo(checker->GetApparentType(elementType), elementType)) {
checker->LogError(diagnostic::TYPE_PARAMETER_AS_ARRAY_ELEMENT_TYPE, {}, newArrayInstanceExpr->Start());
return false;
}
if (elementType->IsETSFunctionType() && newArrayInstanceExpr->IsETSNewClassInstanceExpression()) {
return true;
}
if (!checker->Relation()->IsSupertypeOf(elementType, checker->GlobalETSUndefinedType())) {
checker->LogError(diagnostic::NON_SUPERTYPE_OF_UNDEFINED_AS_ARRAY_ELEMENT_TYPE, {},
newArrayInstanceExpr->Start());
return false;
}
}
return true;
}
static checker::Type *CheckInstantiatedNewType(ETSChecker *checker, ir::ETSNewClassInstanceExpression *expr)
{
auto calleeType = expr->GetTypeRef()->Check(checker);
FORWARD_TYPE_ERROR(checker, calleeType, expr->GetTypeRef());
if (calleeType->IsETSUnionType()) {
return checker->TypeError(expr->GetTypeRef(), diagnostic::UNION_NONCONSTRUCTIBLE, expr->Start());
}
if (!ir::ETSNewClassInstanceExpression::TypeIsAllowedForInstantiation(calleeType)) {
return checker->TypeError(expr->GetTypeRef(), diagnostic::CALLEE_NONCONSTRUCTIBLE, {calleeType}, expr->Start());
}
if (!calleeType->IsETSObjectType()) {
return checker->TypeError(expr->GetTypeRef(), diagnostic::EXPR_NONCONSTRUCTIBLE, {}, expr->Start());
}
auto calleeObj = calleeType->AsETSObjectType();
if (calleeObj->HasObjectFlag(checker::ETSObjectFlags::ABSTRACT)) {
checker->LogError(diagnostic::ABSTRACT_INSTANTIATION, {calleeObj->Name()}, expr->Start());
return checker->GlobalTypeError();
}
if (calleeObj->HasObjectFlag(checker::ETSObjectFlags::INTERFACE)) {
checker->LogError(diagnostic::INTERFACE_INSTANTIATION, {calleeObj->Name()}, expr->Start());
return checker->GlobalTypeError();
}
if (calleeObj->HasObjectFlag(ETSObjectFlags::REQUIRED) &&
!expr->HasAstNodeFlags(ir::AstNodeFlags::ALLOW_REQUIRED_INSTANTIATION)) {
checker->LogError(diagnostic::NONLITERAL_INSTANTIATION, {}, expr->GetTypeRef()->Start());
return checker->GlobalTypeError();
}
return calleeType;
}
* Object literals do not get checked in the process of call resolution; we need to check them separately
* afterwards.
*/
static void CheckObjectLiteralArguments(ETSChecker *checker, Signature *signature,
ArenaVector<ir::Expression *> const &arguments)
{
for (uint32_t index = 0; index < arguments.size(); index++) {
if (!arguments[index]->IsObjectExpression()) {
continue;
}
Type *tp;
if (index >= signature->Params().size()) {
ES2PANDA_ASSERT(signature->RestVar());
tp = checker->GetElementTypeOfArray(signature->RestVar()->TsType());
} else {
tp = checker->GetNonNullishType(signature->Params()[index]->TsType());
}
arguments[index]->SetPreferredType(tp);
arguments[index]->Check(checker);
}
}
checker::Type *ETSAnalyzer::Check(ir::ETSNewClassInstanceExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
expr->GetTypeRef()->Check(checker);
auto *type = expr->GetTypeRef()->TsType();
if (type != nullptr && type->IsETSArrayType()) {
if (expr->GetArguments().empty()) {
checker->LogError(diagnostic::MISSING_ARRAY_SIZE, {type->ToString()}, expr->Start());
return expr->SetTsType(checker->GlobalTypeError());
}
if (expr->GetArguments().size() > 1) {
auto *arg = expr->GetArguments()[1];
if (!arg->IsArrowFunctionExpression()) {
arg->SetPreferredType(type->AsETSArrayType()->ElementType());
}
}
for (auto arg : expr->GetArguments()) {
arg->Check(checker);
}
checker->ValidateArrayIndex(expr->GetArguments()[0], true);
CheckArrayElementType(checker, expr->AsETSNewClassInstanceExpression(), type->AsETSArrayType()->ElementType());
expr->SetTsType(type);
checker->CreateBuiltinArraySignature(expr->TsType()->AsETSArrayType(), 1);
return type;
}
auto *calleeType = CheckInstantiatedNewType(checker, expr);
FORWARD_TYPE_ERROR(checker, calleeType, expr);
auto calleeObj = calleeType->AsETSObjectType();
expr->SetTsType(calleeType);
if (calleeType->IsETSResizableArrayType() && expr->GetArguments().size() == 2U &&
expr->GetArguments()[1]->IsArrowFunctionExpression()) {
expr->GetArguments()[1]->SetPreferredType(calleeType->AsETSResizableArrayType()->ElementType());
if (!checker->ValidateResizableArrayDimension(expr->GetArguments()[0])) {
return checker->InvalidateType(expr);
}
}
auto *signature = checker->ResolveConstructExpression(calleeObj, expr);
if (signature == nullptr) {
return checker->InvalidateType(expr);
}
CheckObjectLiteralArguments(checker, signature, expr->GetArguments());
checker->ValidateSignatureAccessibility(calleeObj, signature, expr->Start());
expr->SetSignature(signature);
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::ETSNewMultiDimArrayInstanceExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
auto *elementType = expr->TypeReference()->GetType(checker);
CheckArrayElementType(checker, expr->AsETSNewMultiDimArrayInstanceExpression(), elementType);
auto *fixedArrayType = elementType;
std::size_t dimensionIndex = 1U;
for (auto *dim : expr->Dimensions()) {
checker->ValidateResizableArrayDimension(dim, dimensionIndex);
++dimensionIndex;
fixedArrayType = checker->CreateETSArrayType(fixedArrayType, false);
}
expr->SetTsType(checker->CreateETSMultiDimResizableArrayType(elementType, expr->Dimensions().size()));
if (expr->TsType()->IsETSArrayType()) {
expr->SetSignature(
checker->CreateBuiltinArraySignature(expr->TsType()->AsETSArrayType(), expr->Dimensions().size()));
}
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSPackageDeclaration *st) const
{
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ETSParameterExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ASSERT_PRINT(expr->Initializer() == nullptr, "default parameter was not lowered");
if (expr->Ident()->TsType() != nullptr) {
expr->SetTsType(expr->Ident()->TsType());
} else if (expr->IsRestParameter()) {
expr->SetTsType(expr->RestParameter()->Check(checker));
} else {
expr->SetTsType(expr->Ident()->Check(checker));
}
ES2PANDA_ASSERT(!expr->IsOptional() ||
checker->Relation()->IsSupertypeOf(expr->TsType(), checker->GlobalETSUndefinedType()));
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSPrimitiveType *node) const
{
ETSChecker *checker = GetETSChecker();
return node->GetType(checker);
}
checker::Type *ETSAnalyzer::Check(ir::ETSStructDeclaration *node) const
{
ETSChecker *checker = GetETSChecker();
node->Definition()->Check(checker);
return ReturnTypeForStatement(node);
}
checker::Type *ETSAnalyzer::Check(ir::ETSTypeReference *node) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
return node->GetType(checker);
}
checker::Type *ETSAnalyzer::Check(ir::ETSTypeReferencePart *node) const
{
ETSChecker *checker = GetETSChecker();
return node->GetType(checker);
}
checker::Type *ETSAnalyzer::Check(ir::ETSNonNullishTypeNode *node) const
{
if (node->TsType() != nullptr) {
return node->TsType();
}
ETSChecker *checker = GetETSChecker();
return node->SetTsType(checker->GetNonNullishType(node->GetTypeNode()->Check(checker)));
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSNullType *node) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
return node->SetTsType(checker->GlobalETSNullType());
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSUndefinedType *node) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
return node->SetTsType(checker->GlobalETSUndefinedType());
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSNeverType *node) const
{
ETSChecker *checker = GetETSChecker();
return checker->GlobalETSNeverType();
}
checker::Type *ETSAnalyzer::Check(ir::ETSStringLiteralType *node) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
return node->GetType(checker);
}
checker::Type *ETSAnalyzer::Check(ir::ETSKeyofType *node) const
{
ETSChecker *checker = GetETSChecker();
return node->GetType(checker);
}
static void AddSpreadElementTypes(ETSChecker *checker, ir::SpreadElement *const element,
std::vector<std::pair<Type *, ir::Expression *>> &elementTypes)
{
Type *const spreadType = element->Check(checker);
if (spreadType->IsTypeError()) {
return;
}
Type *const spreadArgumentType = element->Argument()->TsType();
if (spreadArgumentType->IsETSTupleType()) {
for (Type *type : spreadArgumentType->AsETSTupleType()->GetTupleTypesList()) {
elementTypes.emplace_back(type, element);
}
} else {
elementTypes.emplace_back(checker->GetElementTypeOfSpreadType(spreadArgumentType), element);
}
}
static bool ValidArrayExprSizeForTupleSize(ETSChecker *checker, Type *possibleTupleType,
ir::Expression *possibleArrayExpr)
{
if (!possibleArrayExpr->IsArrayExpression() || !possibleTupleType->IsETSTupleType()) {
return true;
}
return checker->IsArrayExprSizeValidForTuple(possibleArrayExpr->AsArrayExpression(),
possibleTupleType->AsETSTupleType());
}
static std::vector<std::pair<Type *, ir::Expression *>> GetElementTypes(ETSChecker *checker, ir::ArrayExpression *expr)
{
std::vector<std::pair<Type *, ir::Expression *>> elementTypes {};
auto *const exprPreferredType = expr->PreferredType();
auto *const exprTupleType = exprPreferredType->IsETSTupleType() ? exprPreferredType->AsETSTupleType() : nullptr;
checker::Type *elemPreferredType =
exprPreferredType->IsETSTupleType() ? nullptr : checker->GetElementTypeOfArray(exprPreferredType);
for (std::size_t idx = 0U; idx < expr->Elements().size(); ++idx) {
ir::Expression *const element = expr->Elements()[idx];
if (element->IsSpreadElement()) {
element->SetPreferredType(exprPreferredType);
AddSpreadElementTypes(checker, element->AsSpreadElement(), elementTypes);
continue;
}
if (exprTupleType != nullptr) {
if (idx >= exprTupleType->GetTupleSize() ||
!ValidArrayExprSizeForTupleSize(checker, exprTupleType->GetTypeAtIndex(idx), element)) {
elementTypes.emplace_back(element->SetTsType(checker->GlobalTypeError()), element);
continue;
}
elemPreferredType = exprTupleType->GetTypeAtIndex(idx);
}
element->SetPreferredType(elemPreferredType);
elementTypes.emplace_back(element->Check(checker), element);
}
return elementTypes;
}
static Type *GetArrayElementType(ETSChecker *checker, Type *preferredType)
{
if (preferredType->IsETSArrayType()) {
return checker->GetNonConstantType(checker->GetElementTypeOfArray(preferredType));
}
ES2PANDA_ASSERT(preferredType->IsETSResizableArrayType());
return preferredType->AsETSResizableArrayType()->ElementType();
}
static bool CheckElement(ETSChecker *checker, Type *const preferredType,
std::vector<std::pair<Type *, ir::Expression *>> arrayExprElementTypes, std::size_t idx)
{
auto [elementType, currentElement] = arrayExprElementTypes[idx];
if (elementType->IsTypeError()) {
return true;
}
Type *targetType = nullptr;
if (preferredType->IsETSTupleType()) {
const auto *const tupleType = preferredType->AsETSTupleType();
if (tupleType->GetTupleSize() != arrayExprElementTypes.size()) {
return false;
}
auto *const compareType = tupleType->GetTypeAtIndex(idx);
if (compareType == nullptr) {
checker->LogError(diagnostic::TUPLE_SIZE_MISMATCH, {tupleType->GetTupleSize()}, currentElement->Start());
return false;
}
auto ctx = AssignmentContext(checker->Relation(), currentElement, elementType, compareType,
currentElement->Start(), {{diagnostic::TUPLE_UNASSIGNABLE_ARRAY, {idx}}});
if (!ctx.IsAssignable()) {
return false;
}
const CastingContext castCtx(checker->Relation(), diagnostic::CAST_FAIL_UNREACHABLE, {},
CastingContext::ConstructorData {currentElement, compareType, compareType,
currentElement->Start(), TypeRelationFlag::NONE});
targetType = compareType;
} else {
targetType = GetArrayElementType(checker, preferredType);
}
auto ctx = AssignmentContext(checker->Relation(), currentElement, elementType, targetType, currentElement->Start(),
{{diagnostic::ARRAY_ELEMENT_INIT_TYPE_INCOMPAT, {idx, elementType, targetType}}});
return ctx.IsAssignable();
}
static Type *InferPreferredTypeFromElements(ETSChecker *checker, ir::ArrayExpression *arrayExpr)
{
std::vector<Type *> arrayExpressionElementTypes;
for (auto *const element : arrayExpr->Elements()) {
element->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
auto *elementType = *element->Check(checker);
if (element->IsSpreadElement() && elementType->IsETSTupleType()) {
for (auto *typeFromTuple : elementType->AsETSTupleType()->GetTupleTypesList()) {
arrayExpressionElementTypes.emplace_back(typeFromTuple);
}
continue;
}
if (element->IsSpreadElement()) {
elementType = checker->GetElementTypeOfSpreadType(elementType);
}
arrayExpressionElementTypes.emplace_back(elementType);
}
auto *un = checker->CreateETSUnionType(std::move(arrayExpressionElementTypes));
return checker->CreateETSResizableArrayType(
checker->GetNonConstantType(un->IsETSUnionType() ? un->AsETSUnionType()->NormalizedType() : un));
}
static bool CheckArrayExpressionElements(ETSChecker *checker, ir::ArrayExpression *arrayExpr)
{
const std::vector<std::pair<Type *, ir::Expression *>> arrayExprElementTypes = GetElementTypes(checker, arrayExpr);
bool allElementsAssignable = !std::any_of(arrayExprElementTypes.begin(), arrayExprElementTypes.end(),
[](auto &pair) { return pair.first->IsTypeError(); });
for (std::size_t idx = 0; idx < arrayExprElementTypes.size(); ++idx) {
allElementsAssignable &= CheckElement(checker, arrayExpr->PreferredType(), arrayExprElementTypes, idx);
}
return allElementsAssignable;
}
static Type *GetAppropriatePreferredType(Type *originalType, std::function<bool(Type *)> const &predicate)
{
if (originalType == nullptr) {
return nullptr;
}
while (originalType->IsETSTypeAliasType()) {
if (predicate(originalType)) {
return originalType;
}
auto *targetType = originalType->AsETSTypeAliasType()->GetTargetType();
if (targetType == nullptr) {
return nullptr;
}
originalType = targetType;
}
if (originalType->IsETSTypeParameter()) {
return GetAppropriatePreferredType(originalType->AsETSTypeParameter()->GetConstraintType(), predicate);
}
if (predicate(originalType)) {
return originalType;
}
if (!originalType->IsETSUnionType()) {
return nullptr;
}
Type *preferredType = nullptr;
for (Type *type : originalType->AsETSUnionType()->ConstituentTypes()) {
while (type->IsETSTypeAliasType()) {
auto *targetType = type->AsETSTypeAliasType()->GetTargetType();
if (targetType == nullptr) {
type = nullptr;
break;
}
type = targetType;
}
if (type != nullptr && predicate(type)) {
if (preferredType != nullptr) {
return nullptr;
}
preferredType = type;
}
}
return preferredType;
}
static inline checker::Type *CheckElemUnder(checker::ETSChecker *checker, ir::Expression *node,
checker::Type *preferElem)
{
auto *oldPref = node->PreferredType();
node->SetPreferredType(preferElem);
checker::Type *t = node->Check(checker);
node->SetPreferredType(oldPref);
return t;
}
static bool CheckCandidateCompatibility(ETSChecker *checker, ir::ArrayExpression *arrayLiteral, Type *candElem)
{
return std::all_of(arrayLiteral->Elements().begin(), arrayLiteral->Elements().end(), [=](auto *el) {
Type *elTy = CheckElemUnder(checker, el, candElem);
if (elTy == nullptr || elTy->IsTypeError()) {
return false;
}
AssignmentContext ctx(checker->Relation(), el, elTy, candElem, arrayLiteral->Start(), std::nullopt,
TypeRelationFlag::NONE);
return ctx.IsAssignable();
});
}
static bool CheckElementTypeAssignabilityToTuple(ETSChecker *checker, ETSTupleType *tupleType,
ir::ArrayExpression *arrayExpr)
{
if (!ValidArrayExprSizeForTupleSize(checker, tupleType, arrayExpr)) {
return false;
}
for (size_t i = 0; i < arrayExpr->Elements().size(); ++i) {
ir::Expression *element = arrayExpr->Elements()[i];
element->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
auto *elementType = *element->Check(checker);
auto *targetType = tupleType->GetTypeAtIndex(i);
if (const auto ctx = AssignmentContext(checker->Relation(), element, elementType, targetType,
arrayExpr->Start(), std::nullopt, TypeRelationFlag::NONE);
!ctx.IsAssignable()) {
return false;
}
}
return true;
}
static Type *ValidatePreferredTypeForArrayLiteral(ETSChecker *checker, ir::ArrayExpression *arrayLiteral,
Type *candidate)
{
ES2PANDA_ASSERT(candidate->IsETSArrayType() || candidate->IsETSResizableArrayType() || candidate->IsETSTupleType());
InferMatchContext specificTypeMatchCtx(checker, util::DiagnosticType::SEMANTIC, arrayLiteral->Range(), false);
bool valid = true;
if (candidate->IsETSArrayType() || candidate->IsETSResizableArrayType()) {
Type *candidateElem = checker->GetElementTypeOfArray(candidate);
valid = CheckCandidateCompatibility(checker, arrayLiteral, candidateElem);
}
if (candidate->IsETSTupleType()) {
valid = CheckElementTypeAssignabilityToTuple(checker, candidate->AsETSTupleType(), arrayLiteral);
}
valid &= specificTypeMatchCtx.ValidMatchStatus();
arrayLiteral->CleanCheckInformation();
return valid ? candidate : nullptr;
}
static Type *SelectPreferredTypeForLiteral(ETSChecker *checker, ir::ArrayExpression *arrayLiteral,
ETSUnionType *contextualType)
{
for (auto *el : arrayLiteral->Elements()) {
if (el->IsSpreadElement() || el->IsBrokenExpression()) {
return nullptr;
}
}
auto &alts = contextualType->ConstituentTypes();
std::vector<Type *> matchedCandidates;
checker->AddStatus(checker::CheckerStatus::IN_TYPE_INFER);
for (Type *candidate : alts) {
if (!candidate->IsETSArrayType() && !candidate->IsETSResizableArrayType() && !candidate->IsETSTupleType()) {
continue;
}
if (auto select = ValidatePreferredTypeForArrayLiteral(checker, arrayLiteral, candidate); select != nullptr) {
matchedCandidates.emplace_back(select);
}
}
checker->RemoveStatus(checker::CheckerStatus::IN_TYPE_INFER);
if (matchedCandidates.empty()) {
return nullptr;
}
if (matchedCandidates.size() != 1) {
checker->LogError(diagnostic::AMBIGUOUS_ARRAY_LITERAL_TYPE, {matchedCandidates[0], matchedCandidates[1]},
arrayLiteral->Start());
return nullptr;
}
auto res = matchedCandidates.front();
if (res->IsETSTupleType()) {
CheckElementTypeAssignabilityToTuple(checker, res->AsETSTupleType(), arrayLiteral);
return res;
}
Type *resElem = checker->GetElementTypeOfArray(res);
CheckCandidateCompatibility(checker, arrayLiteral, resElem);
return res;
}
static Type *GetPreferredTypeFromArraySupertypes(ETSChecker *checker, Type *originalType)
{
if (originalType == nullptr || !originalType->IsETSObjectType() ||
originalType->AsETSObjectType()->TypeArguments().empty()) {
return nullptr;
}
auto *arrayType = checker->CreateETSResizableArrayType(originalType->AsETSObjectType()->TypeArguments().front());
if (checker->Relation()->IsSupertypeOf(originalType->AsETSObjectType(), arrayType)) {
return arrayType;
}
return nullptr;
}
static bool IsInitForSyntheticVariable(const ir::ArrayExpression *expr)
{
bool hasSyntheticId = false;
if (expr->Parent() != nullptr && expr->Parent()->IsVariableDeclarator()) {
const auto *identifier = expr->Parent()->AsVariableDeclarator()->Id();
if (identifier != nullptr) {
hasSyntheticId = compiler::IsSyntheticIdentifier(identifier);
}
}
return hasSyntheticId;
}
static Type *ExtractArrayPreferredType(ETSChecker *checker, ir::ArrayExpression *expr)
{
Type *preferredType = GetPreferredTypeFromArraySupertypes(checker, expr->PreferredType());
if (preferredType == nullptr) {
preferredType = GetAppropriatePreferredType(expr->PreferredType(), &Type::IsAnyETSArrayOrTupleType);
}
if (expr->PreferredType() != nullptr && expr->PreferredType()->IsETSUnionType()) {
if (auto *picked = SelectPreferredTypeForLiteral(checker, expr, expr->PreferredType()->AsETSUnionType())) {
preferredType = picked;
expr->SetPreferredType(preferredType);
}
}
if (preferredType != nullptr && preferredType->IsETSReadonlyArrayType()) {
const auto elementType = preferredType->AsETSObjectType()->TypeArguments().front();
preferredType = checker->CreateETSResizableArrayType(elementType);
}
return preferredType;
}
static Type *ResolvePreferredTypeForArrayLiteral(ETSChecker *checker, ir::ArrayExpression *expr)
{
Type *preferredType = ExtractArrayPreferredType(checker, expr);
if (!IsArrayExpressionValidInitializerForType(checker, preferredType)) {
checker->LogError(diagnostic::UNEXPECTED_ARRAY, {expr->PreferredType()}, expr->Start());
return checker->InvalidateType(expr);
}
if (!expr->Elements().empty()) {
if (preferredType == nullptr ||
checker->Relation()->IsSupertypeOf(preferredType, checker->GlobalETSObjectType()) ||
util::Helpers::TypeContainsParameterUnderInference(preferredType)) {
preferredType = InferPreferredTypeFromElements(checker, expr);
}
expr->SetPreferredType(preferredType);
}
if (preferredType == nullptr) {
return checker->TypeError(expr, diagnostic::UNRESOLVABLE_ARRAY, expr->Start());
}
if (preferredType->IsETSArrayType()) {
auto *elementType = preferredType->AsETSArrayType()->ElementType();
if (HasBareTypeParameter(elementType) && !IsInitForSyntheticVariable(expr)) {
return checker->TypeError(expr, diagnostic::TYPE_PARAMETER_AS_ARRAY_ELEMENT_TYPE, expr->Start());
}
bool isInsideCallArg = false;
for (auto *p = expr->Parent(); p != nullptr && !p->IsScriptFunction() && !p->IsArrowFunctionExpression();
p = p->Parent()) {
if (p->IsCallExpression()) {
isInsideCallArg = true;
break;
}
}
if (IsTypeNotPreservedByErasure(elementType) && !IsInitForSyntheticVariable(expr) && !isInsideCallArg) {
return checker->TypeError(expr, diagnostic::TYPE_NOT_PRESERVED_BY_ERASURE_FOR_FIXED_ARRAY,
{elementType->ToString()}, expr->Start());
}
}
return preferredType;
}
checker::Type *ETSAnalyzer::Check(ir::ArrayExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
Type *preferredType = ResolvePreferredTypeForArrayLiteral(checker, expr);
if (preferredType->IsTypeError()) {
return preferredType;
}
if (!ValidArrayExprSizeForTupleSize(checker, preferredType, expr) ||
(!expr->Elements().empty() && !CheckArrayExpressionElements(checker, expr))) {
return checker->InvalidateType(expr);
}
expr->SetTsType(preferredType);
if (!preferredType->IsETSResizableArrayType() && !preferredType->IsETSTupleType()) {
ES2PANDA_ASSERT(preferredType->IsETSArrayType());
const auto *const arrayType = preferredType->AsETSArrayType();
checker->CreateBuiltinArraySignature(arrayType, arrayType->Rank());
}
return expr->TsType();
}
static bool IsReturnTypeBoxedPrimitive(checker::ETSChecker *checker, Type *tp, bool isAsyncFunc)
{
if (isAsyncFunc && tp->IsETSObjectType()) {
ES2PANDA_ASSERT(checker->IsPromiseType(tp));
tp = checker->PromiseTypeArg(tp->AsETSObjectType());
}
return tp->IsETSObjectType() && tp->AsETSObjectType()->IsBoxedPrimitive();
}
static Type *DoReturnTypeInference(checker::ETSChecker *checker, ir::ScriptFunction *func)
{
ES2PANDA_ASSERT(func->ReturnTypeAnnotation() == nullptr && func->HasBody());
const auto isAsyncFunc = func->IsAsyncFunc();
* 15.7.2: If a lambda body contains no return statement but all execution paths leads to a throw statement
* execution, then the lambda return type is never;
* 15.7.2: If a function, a method, or a lambda is async, a return type is inferred by applying the above rules,
* and the return type T is not Promise, then the return type is assumed to be Promise<T>;
*/
if (!func->HasReturnStatement() && func->HasThrowStatement() && checker->HasStatus(CheckerStatus::MEET_THROW)) {
return isAsyncFunc ? checker->CreatePromiseOf(checker->GlobalETSNeverType()) : checker->GlobalETSNeverType();
}
std::vector<Type *> returnTypes {};
std::function<void(ir::AstNode *)> retCheck = [&](ir::AstNode *ast) {
if (ast->IsScriptFunction()) {
return;
}
ast->Iterate(retCheck);
if (!ast->IsReturnStatement()) {
return;
}
auto *ret = ast->AsReturnStatement();
* 15.7.2: If at least one of return statements has no expression, then type undefined is added to the return
* type union;
*/
if (ret->Argument() == nullptr) {
returnTypes.push_back(checker->GlobalETSUndefinedType());
return;
}
* Account for possible implicit conversions;
* For async functions, unwrap the promise type to try and match numerics, since:
* 16.3.1: An asynchronous function with the return type Promise<T> can explicitly return a Promise<T> instance
* (in this case, the returned value is returned “as is”) or a value of type T;
*/
auto *expectedPrimitive = GetAppropriatePreferredType(
ret->ReturnType(), [&](Type *tp) { return IsReturnTypeBoxedPrimitive(checker, tp, isAsyncFunc); });
SavedTypeRelationFlagsContext trCtx(checker->Relation(), TypeRelationFlag::IN_ASSIGNMENT_CONTEXT);
checker->Relation()->SetNode(ret->Argument());
const auto argType = ret->Argument()->Check(checker);
ES2PANDA_ASSERT(argType);
if (expectedPrimitive != nullptr &&
checker->Relation()->IsLegalBoxedPrimitiveConversion(expectedPrimitive, argType)) {
returnTypes.push_back(expectedPrimitive);
} else {
const auto argTypeNonConst = checker->GetNonConstantType(argType);
ES2PANDA_ASSERT(argTypeNonConst);
returnTypes.push_back(argTypeNonConst);
}
};
func->Iterate(retCheck);
Type *inferredReturnType = nullptr;
if (returnTypes.empty()) {
* 15.7.2: If there is no return statement, or if all return statements have no expressions, then the return
* type is void;
*/
ES2PANDA_ASSERT(!func->HasThrowStatement() || !checker->HasStatus(CheckerStatus::MEET_THROW));
inferredReturnType = checker->GlobalETSUndefinedType();
} else {
* 15.7.2: If there are k return statements (where k is 1 or more) with the same type expression R, then R is
* the return type;
* 15.7.2: If there are k return statements (where k is 2 or more) with expressions of types T1, ..., Tk, then R
* is the union type of these types (T1 | ... | Tk), and its normalized version is the return type;
*/
inferredReturnType = checker->CreateETSUnionType(std::move(returnTypes));
}
* 15.7.2: If a function, a method, or a lambda is async, a return type is inferred by applying the above rules, and
* the return type T is not Promise, then the return type is assumed to be Promise<T>;
*/
inferredReturnType =
isAsyncFunc ? checker->CreatePromiseOf(checker->UnwrapPromiseType(inferredReturnType)) : inferredReturnType;
ES2PANDA_ASSERT(!isAsyncFunc ||
(inferredReturnType->IsETSObjectType() && checker->IsPromiseType(inferredReturnType)));
return inferredReturnType;
}
static Type *GetPreferredTypeFromPromiseSupertypes(ETSChecker *checker, Type *originalType)
{
* NOTE(knazarov): Emulate first step of intersection - try to match the type by constructing the
* Promise<T> with the same type argument as originalType and check their compatibility;
*/
const auto promiseTypeArgCount = checker->GlobalBuiltinPromiseType()->TypeArguments().size();
if (originalType == nullptr || !originalType->IsETSObjectType() ||
originalType->AsETSObjectType()->TypeArguments().size() != promiseTypeArgCount) {
return nullptr;
}
const auto promiseType = checker->CreatePromiseOf(originalType->AsETSObjectType()->TypeArguments().front());
if (!checker->Relation()->IsSupertypeOf(originalType->AsETSObjectType(), promiseType)) {
return nullptr;
}
return promiseType;
}
static Type *ExtractAsyncFunctionPreferredReturnType(ETSChecker *checker, Type *origType)
{
ES2PANDA_ASSERT(checker);
ES2PANDA_ASSERT(origType);
while (origType->IsETSTypeAliasType()) {
origType = origType->AsETSTypeAliasType()->GetTargetType();
}
const auto preferredPromiseType = GetPreferredTypeFromPromiseSupertypes(checker, origType);
if (preferredPromiseType != nullptr) {
return preferredPromiseType;
}
return GetAppropriatePreferredType(origType, [&](Type *t) { return checker->IsPromiseType(t); });
}
Type *ETSChecker::ResolvePreferredReturnTypeForAsyncFunction(ir::ScriptFunction *expr)
{
ES2PANDA_ASSERT(expr);
ES2PANDA_ASSERT(expr->IsAsyncFunc());
ES2PANDA_ASSERT(expr->GetPreferredReturnType());
auto preferredType = expr->GetPreferredReturnType();
if (preferredType->IsTypeError()) {
return preferredType;
}
if (IsPromiseType(preferredType)) {
return preferredType;
}
Type *preferredReturnType = ExtractAsyncFunctionPreferredReturnType(this, preferredType);
if (preferredReturnType != nullptr) {
return preferredReturnType;
}
if (preferredType->IsETSUnionType()) {
std::vector<Type *> possibleReturnTypes = {};
for (const auto &ct : preferredType->AsETSUnionType()->ConstituentTypes()) {
auto candidate = ExtractAsyncFunctionPreferredReturnType(this, ct);
if (candidate) {
possibleReturnTypes.push_back(candidate);
}
}
if (!possibleReturnTypes.empty()) {
return CreateETSUnionType(std::move(possibleReturnTypes));
}
}
return preferredType;
}
static void CheckArrowFunctionAfterSignatureBuild(checker::ETSChecker *checker, ir::ArrowFunctionExpression *expr)
{
if (expr->Function()->HasReceiver()) {
checker->AddStatus(checker::CheckerStatus::IN_EXTENSION_METHOD);
CheckExtensionMethod(checker, expr->Function(), expr);
}
checker->Context().SetContainingSignature(expr->Function()->Signature());
if (!expr->Function()->HasBody()) {
return;
}
expr->Function()->Body()->Check(checker);
if (expr->Function()->ReturnTypeAnnotation() != nullptr || expr->Function()->Signature() == nullptr) {
return;
}
ES2PANDA_ASSERT(expr->Function()->ReturnTypeAnnotation() == nullptr && expr->Function()->Signature() != nullptr);
const auto sig = expr->Function()->Signature();
const auto isUnderInference = util::Helpers::TypeContainsParameterUnderInference(sig->ReturnType());
const auto isReturnUndefined = sig->ReturnType()->IsETSUndefinedType();
const auto isAsyncReturnNonPromise =
expr->Function()->IsAsyncFunc() &&
!(sig->ReturnType()->IsETSObjectType() && checker->IsPromiseType(sig->ReturnType()));
if (isUnderInference || isReturnUndefined || isAsyncReturnNonPromise) {
Type *computedRetType = DoReturnTypeInference(checker, expr->Function());
sig->SetReturnType(computedRetType);
for (auto &returnStatement : expr->Function()->ReturnStatements()) {
returnStatement->SetReturnType(checker, computedRetType);
}
}
}
static void TryInferPreferredType(ir::ArrowFunctionExpression *expr, checker::Type *preferredType, ETSChecker *checker)
{
ES2PANDA_ASSERT(preferredType->IsETSUnionType() || preferredType->IsETSArrowType());
if (preferredType->IsETSArrowType()) {
ES2PANDA_ASSERT(preferredType->IsETSFunctionType());
if (!preferredType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().empty()) {
checker->TryInferTypeForLambdaTypeAlias(expr, preferredType->AsETSFunctionType());
checker->BuildFunctionSignature(expr->Function(), false);
CheckArrowFunctionAfterSignatureBuild(checker, expr);
}
return;
}
ES2PANDA_ASSERT(preferredType->IsETSUnionType());
for (auto &ct : preferredType->AsETSUnionType()->ConstituentTypes()) {
if (!ct->IsETSArrowType() || ct->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().empty()) {
continue;
}
InferMatchContext specificTypeMatchCtx(checker, util::DiagnosticType::SEMANTIC, expr->Range(), false);
checker->TryInferTypeForLambdaTypeAlias(expr, ct->AsETSFunctionType());
checker->BuildFunctionSignature(expr->Function(), false);
CheckArrowFunctionAfterSignatureBuild(checker, expr);
if (specificTypeMatchCtx.ValidMatchStatus()) {
return;
}
expr->CleanCheckInformation();
}
checker->BuildFunctionSignature(expr->Function(), false);
}
checker::Type *ETSAnalyzer::Check(ir::ArrowFunctionExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(expr);
if (expr->TsType() != nullptr) {
return expr->TsType();
}
checker::ScopeContext scopeCtx(checker, expr->Function()->Scope());
if (checker->HasStatus(checker::CheckerStatus::IN_EXTENSION_METHOD) && !expr->Function()->HasReceiver()) {
example code:
```
class A {
prop:number
}
function method(this: A) {
let a = () => {
console.log(this.prop)
}
}
```
here the enclosing class of arrow function should be Class A
*/
checker->Context().SetContainingClass(
checker->Scope()->Find(varbinder::VarBinder::MANDATORY_PARAM_THIS).variable->TsType()->AsETSObjectType());
}
auto lambdaSavedSmartCasts = checker->Context().CloneSmartCasts();
checker::SavedCheckerContext savedContext(checker, checker->Context().Status(),
checker->Context().ContainingClass());
if (expr->Parent()->IsCallExpression() && !expr->Function()->IsAsyncFunc()) {
checker->Context().RestoreSmartCasts(lambdaSavedSmartCasts);
}
checker->AddStatus(checker::CheckerStatus::IN_LAMBDA);
checker->Context().SetContainingLambda(expr);
if (expr->PreferredType() != nullptr &&
(expr->PreferredType()->IsETSArrowType() || expr->PreferredType()->IsETSUnionType())) {
TryInferPreferredType(expr, expr->PreferredType(), checker);
} else {
checker->BuildFunctionSignature(expr->Function(), false);
}
if (expr->Function()->Signature() == nullptr || util::Helpers::IsErrorPlaceHolder(expr->Function()->Id())) {
return checker->InvalidateType(expr);
}
CheckArrowFunctionAfterSignatureBuild(checker, expr);
CheckAsyncFunctionReturnType(checker, expr->Function());
auto *funcType = checker->CreateETSArrowType(expr->Function()->Signature());
checker->Context().SetContainingSignature(nullptr);
return expr->SetTsType(funcType);
}
static bool IsInvalidArrayMemberAssignment(const ir::AssignmentExpression *const expr, ETSChecker *checker)
{
if (!expr->Left()->IsMemberExpression()) {
return false;
}
const auto *const leftExpr = expr->Left()->AsMemberExpression();
if (leftExpr->Object()->TsType()->IsETSArrayType() || leftExpr->Object()->TsType()->IsETSTupleType() ||
leftExpr->Object()->TsType()->IsETSResizableArrayType()) {
if (leftExpr->Object()->TsType()->IsETSArrayType() && leftExpr->Property()->IsIdentifier() &&
leftExpr->Property()->AsIdentifier()->Name().Is("length")) {
checker->LogError(diagnostic::ARRAY_LENGTH_MODIFICATION, {}, expr->Left()->Start());
return true;
}
if (leftExpr->Object()->TsType()->HasTypeFlag(TypeFlag::READONLY)) {
checker->LogError(diagnostic::READONLY_ARRAYLIKE_MODIFICATION, {}, expr->Left()->Start());
return true;
}
}
return false;
}
static checker::Type *GetSmartType(ETSChecker *checker, ir::Identifier *ident, varbinder::Variable const *variable,
checker::Type *sourceType, checker::Type *targetType, std::optional<double> value)
{
checker::Type *smartType = targetType;
smartType = checker->ResolveSmartType(sourceType, targetType, value);
auto const *const variableScope = variable->GetScope();
auto const topLevelVariable =
variableScope != nullptr && (variableScope->IsGlobalScope() ||
(variableScope->Parent() != nullptr && variableScope->Parent()->IsGlobalScope()));
if (!topLevelVariable) {
if (checker->Relation()->IsIdenticalTo(targetType, smartType)) {
checker->Context().RemoveSmartCast(variable);
} else {
ident->SetTsType(smartType);
checker->Context().SetSmartCast(variable, smartType);
}
}
return smartType;
}
checker::Type *ETSAnalyzer::GetSmartTypeForAssignment(ir::AssignmentExpression *const expr,
checker::Type *const leftType, checker::Type *const rightType,
ir::Expression *const relationNode) const
{
auto isLazyImportObject =
leftType->IsETSObjectType() && leftType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::LAZY_IMPORT_OBJECT);
if (leftType->IsTypeError() || isLazyImportObject) {
return rightType;
}
ETSChecker *checker = GetETSChecker();
if (const auto ctx =
checker::AssignmentContext(checker->Relation(), relationNode, rightType, leftType, expr->Right()->Start(),
{{diagnostic::INVALID_ASSIGNMNENT, {rightType, leftType}}});
!ctx.IsAssignable()) {
return rightType;
}
auto *smartType = rightType;
if (expr->Left()->IsIdentifier() && expr->Target() != nullptr) {
auto const value = expr->Right()->IsNumberLiteral()
? std::make_optional(expr->Right()->AsNumberLiteral()->Number().GetDouble())
: std::nullopt;
smartType = GetSmartType(checker, expr->Left()->AsIdentifier(), expr->Target(), rightType, leftType, value);
}
return smartType;
}
static checker::Type const *ResolveMethodDefinition(const ir::Expression *const expression, ETSChecker *checker)
{
if (!expression->IsMemberExpression()) {
return nullptr;
}
auto const *memberExpression = expression->AsMemberExpression();
if (memberExpression->Kind() != ir::MemberExpressionKind::PROPERTY_ACCESS ||
memberExpression->Property() == nullptr || !memberExpression->Property()->IsIdentifier()) {
return nullptr;
}
auto const *variable = memberExpression->Property()->Variable();
if (variable == nullptr) {
if (auto *objectType = const_cast<checker::Type *>(memberExpression->Object()->TsType());
objectType != nullptr && objectType->IsETSObjectType()) {
auto resolved = checker->ResolveMemberReference(memberExpression, objectType->AsETSObjectType());
if (resolved.size() == 2U && resolved[1]->Kind() == checker::ResolvedKind::PROPERTY) {
variable = resolved[1U]->Variable()->AsLocalVariable();
}
}
}
if (variable != nullptr) {
return variable->TsType();
}
return nullptr;
}
static bool IsInvalidMethodAssignment(const ir::AssignmentExpression *const expr, ETSChecker *checker)
{
auto left = expr->Left();
if (auto const *methodType = ResolveMethodDefinition(left, checker);
methodType != nullptr && methodType->IsETSMethodType()) {
auto const callSigs = methodType->AsETSFunctionType()->CallSignatures();
for (auto callSig : callSigs) {
if (callSig->HasSignatureFlag(SignatureFlags::SETTER)) {
return false;
}
}
checker->LogError(diagnostic::METHOD_ASSIGNMENT, left->Start());
return true;
}
return false;
}
static bool TryExtractNumberFromIdentifier(ir::Expression *expr, lexer::Number &outNum, std::string &outStr)
{
auto *id = expr->AsIdentifier();
auto *var = id->Variable();
if (var == nullptr || var->Declaration() == nullptr || var->Declaration()->Node() == nullptr) {
return false;
}
auto *declNode = var->Declaration()->Node();
auto *parent = declNode->Parent();
if (parent->IsClassProperty() || parent->IsVariableDeclarator()) {
declNode = parent;
}
auto getNumberFromLiteral = [&](ir::Expression *literal) -> bool {
if (literal == nullptr || !literal->IsNumberLiteral()) {
return false;
}
outNum = literal->AsNumberLiteral()->Number();
outStr = literal->DumpEtsSrc();
return true;
};
if (declNode->IsClassProperty()) {
auto *prop = declNode->AsClassProperty();
return prop->IsConst() && getNumberFromLiteral(prop->Value());
}
if (declNode->IsVariableDeclarator()) {
auto *decl = declNode->AsVariableDeclarator();
bool isConst = decl->Flag() == ir::VariableDeclaratorFlag::CONST;
return isConst && getNumberFromLiteral(decl->Init());
}
return false;
}
static bool TryExtractNumber(ir::Expression *expr, lexer::Number &outNum, std::string &outStr)
{
if (expr->IsNumberLiteral()) {
outNum = expr->AsNumberLiteral()->Number();
outStr = expr->DumpEtsSrc();
return true;
}
if (expr->IsIdentifier()) {
return TryExtractNumberFromIdentifier(expr, outNum, outStr);
}
return false;
}
static bool FitsNumericType(Type *ctype, const lexer::Number &number)
{
if (!ctype->IsETSObjectType()) {
return false;
}
auto *obj = ctype->AsETSObjectType();
struct FitCase {
ETSObjectFlags flag;
bool isInteger;
std::function<bool()> canFit;
};
const std::vector<FitCase> fitCases = {
{ETSObjectFlags::BUILTIN_BYTE, true, [&] { return number.CanGetValue<int8_t>(); }},
{ETSObjectFlags::BUILTIN_SHORT, true, [&] { return number.CanGetValue<int16_t>(); }},
{ETSObjectFlags::BUILTIN_INT, true, [&] { return number.CanGetValue<int32_t>(); }},
{ETSObjectFlags::BUILTIN_LONG, true, [&] { return number.CanGetValue<int64_t>(); }},
{ETSObjectFlags::BUILTIN_FLOAT, false, [&] { return number.CanGetValue<float>(); }},
{ETSObjectFlags::BUILTIN_DOUBLE, false, [&] { return number.CanGetValue<double>(); }},
};
for (const auto &f : fitCases) {
if (obj->HasObjectFlag(f.flag) && f.canFit() &&
((f.isInteger && number.IsInteger()) || (!f.isInteger && number.IsReal()))) {
return true;
}
}
return false;
}
static checker::Type *GetSetterType(varbinder::Variable *const var, ETSChecker *checker)
{
if (var == nullptr || !checker->IsVariableGetterSetter(var)) {
return nullptr;
}
if (var->TsType()->IsETSFunctionType()) {
auto *funcType = var->TsType()->AsETSFunctionType();
if (funcType->HasTypeFlag(checker::TypeFlag::SETTER)) {
auto *setter = funcType->FindSetter();
ES2PANDA_ASSERT(setter != nullptr && setter->Params().size() == 1);
return setter->Params()[0]->TsType();
}
}
return nullptr;
}
bool ETSAnalyzer::SetAssignmentExpressionTarget(ir::AssignmentExpression *const expr, ETSChecker *checker) const
{
if (expr->Left()->IsIdentifier()) {
expr->target_ = expr->Left()->AsIdentifier()->Variable();
} else if (expr->Left()->IsMemberExpression()) {
if (!expr->IsIgnoreConstAssign() &&
expr->Left()->AsMemberExpression()->Object()->TsType()->HasTypeFlag(TypeFlag::READONLY)) {
checker->LogError(diagnostic::READONLY_PROPERTY_REASSIGN, {}, expr->Left()->Start());
}
expr->target_ = expr->Left()->AsMemberExpression()->PropVar();
} else {
return false;
}
return true;
}
static void IsAmbiguousUnionInit(checker::ETSUnionType *unionType, ir::Expression *initExpr, ETSChecker *checker)
{
if (initExpr->IsNumberLiteral() && initExpr->AsNumberLiteral()->IsFolded() && initExpr->TsType() != nullptr) {
return;
}
lexer::Number initNumber;
std::string str;
if (!TryExtractNumber(initExpr, initNumber, str)) {
return;
}
if (initNumber.IsReal()) {
return;
}
lexer::Number number {initNumber.GetValueAndCastTo<int64_t>()};
int fits = std::count_if(unionType->ConstituentTypes().begin(), unionType->ConstituentTypes().end(),
[&](Type *t) { return FitsNumericType(t, number); });
if (fits > 1) {
checker->LogError(diagnostic::AMBIGUOUS_UNION_VALUE, {str}, initExpr->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::ETSDestructuring *const expr) const
{
ETSChecker *checker = GetETSChecker();
std::vector<checker::Type *> tupleTypeList;
for (auto *elem : expr->Elements()) {
if (elem->IsOmittedExpression()) {
continue;
}
if (elem->IsRestElement()) {
checker->LogError(diagnostic::REST_UNSUPPORTED_IN_DESTRUCTURING, {}, elem->Start());
continue;
}
if (elem->IsAssignmentPattern()) {
checker->LogError(diagnostic::DEFAULT_UNSUPPORTED_IN_DESTRUCTURING, {}, elem->Start());
continue;
}
elem->Check(checker);
tupleTypeList.emplace_back(elem->TsType());
}
return expr->SetTsType(checker->CreateETSTupleType(std::move(tupleTypeList), false));
}
static checker::Type *CheckETSArrayOrTupleTypeInDestructuring(ETSChecker *checker, ir::ETSDestructuring *dstrNode,
ir::Expression *initializer)
{
bool isVarDecl = dstrNode->Parent()->IsVariableDeclarator();
auto initType = initializer->Check(checker);
if (initType->IsAnyETSArrayOrTupleType()) {
bool isTuple = initType->IsETSTupleType();
auto *initArrayElementType = !isTuple ? GetArrayElementType(checker, initType) : nullptr;
if (isTuple && initType->AsETSTupleType()->GetTupleSize() < dstrNode->Size()) {
checker->LogError(diagnostic::INVALID_DESTRUCTURING_INIT_SIZE,
{initType->AsETSTupleType()->GetTupleSize(), dstrNode->Size()}, initializer->Start());
return initType;
}
for (uint32_t idx = 0; idx < dstrNode->Size(); idx++) {
auto *initElementType = isTuple ? initType->AsETSTupleType()->GetTypeAtIndex(idx) : initArrayElementType;
auto *dstrElement = dstrNode->GetExpressionAtIndex(idx);
auto *targetType = dstrElement->TsType();
ES2PANDA_ASSERT(initElementType != nullptr);
if (dstrElement->IsOmittedExpression() || dstrElement->IsRestElement() ||
dstrElement->IsAssignmentPattern()) {
continue;
}
if (isVarDecl) {
dstrElement->SetTsType(initElementType);
continue;
}
if (!checker->Relation()->IsAssignableTo(initElementType, targetType)) {
checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {initElementType, targetType}, dstrElement->Start());
} else if (dstrElement->IsIdentifier() && dstrElement->Variable() != nullptr) {
GetSmartType(checker, dstrElement->AsIdentifier(), dstrElement->Variable(), initElementType, targetType,
std::nullopt);
}
}
} else {
checker->LogError(diagnostic::INVALID_DESTRUCTURING_TARGET, {}, initializer->Start());
}
return initType;
}
checker::Type *CheckDestructuringExpression(ETSChecker *checker, ir::ETSDestructuring *dstrNode,
ir::Expression *initializer)
{
if (initializer == nullptr) {
return checker->GlobalTypeError();
}
bool isVarDecl = dstrNode->Parent()->IsVariableDeclarator();
if (initializer->IsArrayExpression()) {
std::vector<Type *> tupleTypeList;
auto arrayElements = initializer->AsArrayExpression()->Elements();
for (uint32_t idx = 0; idx < dstrNode->Size() && idx < arrayElements.size(); idx++) {
auto *arrayElement = arrayElements.at(idx);
auto *dstrElement = dstrNode->GetExpressionAtIndex(idx);
if (dstrElement->IsArrayPattern()) {
checker->LogError(diagnostic::NOT_IMPLEMENTED, {}, dstrElement->Start());
}
if (dstrElement->IsOmittedExpression() || dstrElement->IsRestElement() ||
dstrElement->IsAssignmentPattern()) {
tupleTypeList.emplace_back(arrayElements.at(idx)->Check(checker));
continue;
}
if (isVarDecl) {
auto initElementType = arrayElement->Check(checker);
dstrElement->SetTsType(initElementType);
tupleTypeList.emplace_back(initElementType);
continue;
}
auto dstrType = dstrElement->TsType();
checker->SetPreferredTypeForExpression(arrayElement, nullptr, arrayElement, dstrType);
auto initElementType = arrayElement->Check(checker);
if (checker->Relation()->IsAssignableTo(initElementType, dstrType)) {
tupleTypeList.emplace_back(initElementType);
} else {
checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {initElementType, dstrType}, arrayElement->Start());
tupleTypeList.emplace_back(checker->GlobalTypeError());
}
}
if (arrayElements.size() < dstrNode->Size()) {
checker->LogError(diagnostic::INVALID_DESTRUCTURING_INIT_SIZE, {arrayElements.size(), dstrNode->Size()},
initializer->Start());
} else {
for (uint32_t idx = dstrNode->Size(); idx < arrayElements.size(); idx++) {
tupleTypeList.emplace_back(arrayElements.at(idx)->Check(checker));
}
}
return initializer->SetTsType(checker->CreateETSTupleType(std::move(tupleTypeList), false));
}
return CheckETSArrayOrTupleTypeInDestructuring(checker, dstrNode, initializer);
}
checker::Type *ETSAnalyzer::Check(ir::AssignmentExpression *const expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
if (checker->HasStatus(CheckerStatus::IN_SETTER) && expr->Left()->IsMemberExpression()) {
checker->WarnForEndlessLoopInGetterSetter(expr->Left()->AsMemberExpression());
}
checker::Type *leftType = expr->Left()->Check(checker);
if (expr->Left()->IsETSDestructuring()) {
return expr->SetTsType(
CheckDestructuringExpression(checker, expr->Left()->AsETSDestructuring(), expr->Right()));
}
if (IsInvalidArrayMemberAssignment(expr, checker) || IsInvalidMethodAssignment(expr, checker)) {
expr->SetTsType(checker->GlobalTypeError());
return expr->TsType();
}
if (!SetAssignmentExpressionTarget(expr, checker)) {
checker->LogError(diagnostic::ASSIGNMENT_INVALID_LHS, {}, expr->Left()->Start());
expr->SetTsType(checker->GlobalTypeError());
return expr->TsType();
}
if (expr->target_ != nullptr && !expr->IsIgnoreConstAssign()) {
checker->ValidateUnaryOperatorOperand(expr->target_, expr);
}
checker->InferLambdaInAssignmentExpression(expr);
if (auto setterType = GetSetterType(expr->target_, checker); setterType != nullptr) {
leftType = setterType;
expr->Left()->SetTsType(leftType);
}
auto [rightType, relationNode] = CheckAssignmentExprOperatorType(expr, leftType);
if (rightType->IsTypeError()) {
return expr->SetTsType(leftType);
}
if (leftType->IsETSUnionType()) {
IsAmbiguousUnionInit(leftType->AsETSUnionType(), expr->Right(), checker);
}
return expr->SetTsType(GetSmartTypeForAssignment(expr, leftType, rightType, relationNode));
}
static bool IsLoweredGlobalImmediateInitAssignment(ir::AssignmentExpression const *expr)
{
if (expr->Parent() == nullptr || !expr->Parent()->IsExpressionStatement() || !expr->Left()->IsIdentifier()) {
return false;
}
auto *lhsIdent = expr->Left()->AsIdentifier();
if (lhsIdent->Variable() == nullptr || lhsIdent->Variable()->Declaration() == nullptr ||
lhsIdent->Variable()->Declaration()->Node() == nullptr) {
return false;
}
auto *declNode = lhsIdent->Variable()->Declaration()->Node();
if (!declNode->IsClassProperty()) {
return false;
}
auto *classProp = declNode->AsClassProperty();
if (!classProp->IsImmediateInit() || classProp->TypeAnnotation() != nullptr || classProp->Value() == nullptr) {
return false;
}
auto const *ownerClass = util::Helpers::GetContainingClassDefinition(classProp);
auto const *exprClass = util::Helpers::GetContainingClassDefinition(expr);
if (ownerClass == nullptr || exprClass == nullptr || ownerClass != exprClass ||
!util::Helpers::IsGlobalClass(ownerClass)) {
return false;
}
bool sameStmtRange = !(expr->Parent()->Range() != classProp->Range());
bool sameRhsRange = !(expr->Right()->Range() != classProp->Value()->Range());
return sameStmtRange && sameRhsRange;
}
static bool ShouldSkipContextualTypingForArrayLiteral(ETSChecker *checker, Type *leftType)
{
if (!util::Helpers::IsArrayType(leftType)) {
return false;
}
auto *elementType = checker->GetElementTypeOfArray(leftType);
if (elementType == nullptr || !elementType->IsETSUnionType()) {
return false;
}
size_t arrayLikeCandidates = 0;
for (auto *candidate : elementType->AsETSUnionType()->ConstituentTypes()) {
if (candidate->IsAnyETSArrayOrTupleType()) {
if (++arrayLikeCandidates > 1) {
return true;
}
}
}
return false;
}
static checker::Type *HandleSubstitution(ETSChecker *checker, ir::AssignmentExpression *expr, Type *const leftType)
{
bool possibleInferredTypeOfArray =
util::Helpers::IsArrayType(leftType) || leftType->IsETSTupleType() || leftType->IsETSUnionType();
if (expr->Right()->IsArrayExpression() && possibleInferredTypeOfArray) {
auto *arrayExpr = expr->Right()->AsArrayExpression();
bool const skipContextualTyping = IsLoweredGlobalImmediateInitAssignment(expr) &&
ShouldSkipContextualTypingForArrayLiteral(checker, leftType);
checker->ModifyPreferredType(arrayExpr, skipContextualTyping ? nullptr : leftType);
} else if (expr->Right()->IsArrowFunctionExpression() &&
(leftType->IsETSArrowType() || leftType->IsETSUnionType())) {
if (auto *preferredType = GetAppropriatePreferredType(leftType, [](Type *tp) { return tp->IsETSArrowType(); });
preferredType != nullptr) {
checker->TryInferTypeForLambdaTypeAlias(expr->Right()->AsArrowFunctionExpression(),
preferredType->AsETSFunctionType());
} else {
expr->Right()->SetPreferredType(leftType);
}
} else if (expr->Right()->IsObjectExpression()) {
expr->Right()->AsObjectExpression()->SetPreferredType(leftType);
} else {
checker->SetPreferredTypeForExpression(expr->Left(), nullptr, expr->Right(), leftType);
}
return expr->Right()->Check(checker);
}
std::tuple<Type *, ir::Expression *> ETSAnalyzer::CheckAssignmentExprOperatorType(ir::AssignmentExpression *expr,
Type *const leftType) const
{
ETSChecker *checker = GetETSChecker();
checker::Type *sourceType {};
ir::Expression *relationNode = expr->Right();
switch (expr->OperatorType()) {
case lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL:
case lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL:
case lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL:
case lexer::TokenType::PUNCTUATOR_MOD_EQUAL:
case lexer::TokenType::PUNCTUATOR_MINUS_EQUAL:
case lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL:
case lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL:
case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL:
case lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL:
case lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL:
case lexer::TokenType::PUNCTUATOR_PLUS_EQUAL: {
std::tie(std::ignore, expr->operationType_) = checker->CheckBinaryOperator(
expr->Left(), expr->Right(), expr, expr->OperatorType(), expr->Start(), true);
sourceType = leftType;
relationNode = expr;
break;
}
case lexer::TokenType::PUNCTUATOR_SUBSTITUTION: {
sourceType = HandleSubstitution(checker, expr, leftType);
break;
}
default: {
ES2PANDA_UNREACHABLE();
break;
}
}
return {sourceType, relationNode};
}
checker::Type *ETSAnalyzer::Check(ir::AwaitExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
* NOTE(knazarov): Spec 16.3.4.
* A compile-time error occurs if await is used outside of an asynchronous function, method or lambda body.
* Check only the nearest ScriptFunction.
*/
const auto ancestor = util::Helpers::FindAncestorGivenByType(expr, ir::AstNodeType::SCRIPT_FUNCTION);
const auto isAncestorAsync = (ancestor != nullptr) && (ancestor->AsScriptFunction()->IsDeclaredAsync());
if (!isAncestorAsync) {
checker->LogError(diagnostic::AWAIT_IN_NON_ASYNC_DEPRECATED, {}, expr->Start());
}
expr->SetTsType(checker->HandleAwaitedUtilityType(expr->argument_->Check(checker)));
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::BinaryExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
bool inSmartExpr = false;
if (!checker->Context().IsInTestExpression()) {
switch (expr->OperatorType()) {
case lexer::TokenType::KEYW_INSTANCEOF:
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL:
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_LOGICAL_AND:
case lexer::TokenType::PUNCTUATOR_LOGICAL_OR: {
inSmartExpr = true;
SmartCastArray smartCasts = checker->Context().EnterTestExpression();
break;
}
default:
break;
}
}
auto [newTsType, operationType] =
checker->CheckBinaryOperator(expr->Left(), expr->Right(), expr, expr->OperatorType(), expr->Start());
expr->SetTsType(newTsType);
expr->SetOperationType(operationType);
checker->Context().CheckBinarySmartCastCondition(expr);
if (inSmartExpr) {
checker->Context().ExitTestExpression();
}
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::BlockExpression *st) const
{
if (st->TsType() != nullptr) {
return st->TsType();
}
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
for (std::size_t idx = 0; idx < st->Statements().size(); idx++) {
st->Statements()[idx]->Check(checker);
}
auto lastStmt = st->Statements().back();
ES2PANDA_ASSERT(lastStmt->IsExpressionStatement());
st->SetTsType(lastStmt->AsExpressionStatement()->GetExpression()->TsType());
return st->TsType();
}
static bool LambdaIsField(ir::CallExpression *expr)
{
if (!expr->Callee()->IsMemberExpression()) {
return false;
}
auto *me = expr->Callee()->AsMemberExpression();
return me->PropVar() != nullptr;
}
static Signature *CreateRelaxedAnySyntheticCallSignature(ETSChecker *checker)
{
auto *info = checker->CreateSignatureInfo();
info->minArgCount = 0;
auto *paramVar =
varbinder::Scope::CreateVar(checker->ProgramAllocator(), "args", varbinder::VariableFlags::NONE, nullptr);
paramVar->SetTsType(checker->CreateETSArrayType(checker->GlobalETSRelaxedAnyType(), false));
info->restVar = paramVar;
return checker->CreateSignature(info, checker->GlobalETSRelaxedAnyType(), ir::ScriptFunctionFlags::NONE, false);
}
static checker::Signature *ResolveSignature(ETSChecker *checker, ir::CallExpression *expr, checker::Type *calleeType)
{
if (calleeType->IsETSExtensionFuncHelperType()) {
auto *signature =
ResolveCallForETSExtensionFuncHelperType(calleeType->AsETSExtensionFuncHelperType(), checker, expr);
return signature;
}
if (checker->IsExtensionETSFunctionType(calleeType) && !LambdaIsField(expr)) {
auto *signature = ResolveCallExtensionFunction(calleeType, checker, expr);
if (signature != nullptr && signature->IsExtensionAccessor() &&
!checker->HasStatus(CheckerStatus::IN_EXTENSION_ACCESSOR_CHECK)) {
checker->LogError(diagnostic::EXTENSION_ACCESSOR_INVALID_CALL, {}, expr->Start());
return nullptr;
}
return signature;
}
auto noSignatures = ArenaVector<checker::Signature *> {checker->Allocator()->Adapter()};
if (calleeType->IsETSRelaxedAnyType()) {
noSignatures.push_back(CreateRelaxedAnySyntheticCallSignature(checker));
}
auto &signatures = expr->IsETSConstructorCall() ? calleeType->AsETSObjectType()->ConstructSignatures()
: calleeType->IsETSRelaxedAnyType()
? noSignatures
: calleeType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow();
return checker->FirstMatchSignatures(signatures, expr);
}
static ETSObjectType *GetCallExpressionCalleeObject(ETSChecker *checker, ir::CallExpression *expr, Type *calleeType)
{
if (expr->IsETSConstructorCall()) {
return calleeType->AsETSObjectType();
}
auto callee = expr->Callee();
if (callee->IsMemberExpression()) {
return callee->AsMemberExpression()->ObjType();
}
ES2PANDA_ASSERT(callee->IsIdentifier());
return checker->Context().ContainingClass();
}
static Type *ResolveThisTypeForThisReturnCall(ETSChecker *checker, ir::CallExpression *expr, Type *calleeType)
{
auto *callee = expr->Callee();
if (callee->IsMemberExpression() && callee->AsMemberExpression()->Object()->IsSuperExpression()) {
return checker->Context().ContainingClass();
}
return GetCallExpressionCalleeObject(checker, expr, calleeType);
}
static bool CalleeIsBuiltinArrayStaticCreate(ir::CallExpression *expr)
{
auto *callee = expr->Callee();
if (!callee->IsMemberExpression()) {
return false;
}
auto *mem = callee->AsMemberExpression();
auto *prop = mem->Property();
if (!prop->IsIdentifier()) {
return false;
}
static constexpr std::string_view ARRAY_CREATE = "create";
if (prop->AsIdentifier()->Name() != ARRAY_CREATE) {
return false;
}
auto *obj = mem->Object();
if (obj->TsType() == nullptr || !obj->TsType()->IsETSObjectType()) {
return false;
}
return obj->TsType()->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_ARRAY);
}
static void EmitBuiltinArrayCreateMismatch(ETSChecker *checker, ir::CallExpression *expr, Type *calleeType)
{
static constexpr std::string_view SIG_KIND_CALL = "call";
if (!calleeType->IsETSFunctionType()) {
return;
}
checker->LogSignatureMismatch(calleeType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow(), expr->Arguments(),
expr->Start(), SIG_KIND_CALL);
}
static Type *GetReturnType(ETSChecker *checker, ir::CallExpression *expr, Type *calleeType)
{
if (calleeType->IsTypeError()) {
return checker->GlobalTypeError();
}
if (!calleeType->IsETSFunctionType() && !expr->IsETSConstructorCall() &&
!calleeType->IsETSExtensionFuncHelperType() && !calleeType->IsETSRelaxedAnyType()) {
checker->LogError(diagnostic::NO_CALL_SIGNATURE, {calleeType}, expr->Start());
return checker->GlobalTypeError();
}
if (CalleeIsBuiltinArrayStaticCreate(expr) && expr->Arguments().size() == 2U) {
if (!checker->ValidateResizableArrayDimension(expr->Arguments()[0])) {
EmitBuiltinArrayCreateMismatch(checker, expr, calleeType);
return checker->GlobalTypeError();
}
}
Signature *const signature = ResolveSignature(checker, expr, calleeType);
if (signature == nullptr) {
if (CalleeIsBuiltinArrayStaticCreate(expr)) {
EmitBuiltinArrayCreateMismatch(checker, expr, calleeType);
}
return checker->GlobalTypeError();
}
CheckObjectLiteralArguments(checker, signature, expr->Arguments());
if (calleeType->IsETSMethodType()) {
ETSObjectType *calleeObj = GetCallExpressionCalleeObject(checker, expr, calleeType);
checker->ValidateSignatureAccessibility(calleeObj, signature, expr->Start());
}
expr->SetSignature(signature);
if (signature->HasSignatureFlag(SignatureFlags::THIS_RETURN_TYPE)) {
if (signature->HasSignatureFlag(SignatureFlags::EXTENSION_FUNCTION)) {
return expr->Arguments()[0]->TsType();
}
return ResolveThisTypeForThisReturnCall(checker, expr, calleeType);
}
return signature->ReturnType();
}
static void CheckAbstractCall(ETSChecker *checker, ir::CallExpression *expr)
{
if (expr->Callee()->IsMemberExpression()) {
auto obj = expr->Callee()->AsMemberExpression()->Object();
if (obj != nullptr && obj->IsSuperExpression()) {
if ((expr->Signature() != nullptr) && (expr->Signature()->HasSignatureFlag(SignatureFlags::ABSTRACT))) {
checker->LogError(diagnostic::ABSTRACT_CALL, {}, expr->Start());
expr->SetTsType(checker->GlobalTypeError());
}
}
}
}
static bool IsDirectlyInStaticInit(const ir::AstNode *node)
{
for (auto *iter = node->Parent(); iter != nullptr; iter = iter->Parent()) {
if (iter->IsClassProperty()) {
return iter->IsStatic();
}
if (iter->IsScriptFunction()) {
auto *func = iter->AsScriptFunction();
if (func->IsStaticBlock()) {
return true;
}
if (func->Id() != nullptr && IsInitializerBlockTransfer(func->Id()->Name().Utf8())) {
return true;
}
return false;
}
}
return false;
}
static void CheckAsyncCallInStaticInit(ETSChecker *checker, ir::CallExpression *expr)
{
auto *sig = expr->Signature();
if (sig == nullptr || !sig->HasFunction() || !sig->Function()->IsAsyncFunc()) {
return;
}
if (IsDirectlyInStaticInit(expr)) {
checker->LogError(diagnostic::ASYNC_CALL_IN_STATIC_INIT, {}, expr->Start());
}
}
static void CheckCallee(ETSChecker *checker, ir::CallExpression *expr)
{
checker->CheckNonNullish(expr->Callee());
if (!expr->Callee()->IsMemberExpression()) {
return;
}
auto memberExpr = expr->Callee()->AsMemberExpression();
if (memberExpr->Object() == nullptr) {
return;
}
auto baseType = memberExpr->Object()->TsType();
auto *baseTypeObj = baseType->IsETSObjectType() ? baseType->AsETSObjectType() : nullptr;
if (baseTypeObj != nullptr &&
(baseTypeObj->HasObjectFlag(ETSObjectFlags::READONLY) ||
(baseTypeObj->HasTypeFlag(TypeFlag::READONLY) && !baseType->IsAnyETSArrayOrTupleType()))) {
checker->LogError(diagnostic::READONLY_CALL, {}, expr->Start());
expr->SetTsType(checker->GlobalTypeError());
}
if (baseTypeObj != nullptr && (baseTypeObj->HasObjectFlag(ETSObjectFlags::REQUIRED))) {
checker->LogError(diagnostic::REQUIRED_CALL, {}, expr->Start());
expr->SetTsType(checker->GlobalTypeError());
}
}
static checker::SavedCheckerContext ReconstructOwnerClassContext(ETSChecker *checker, ETSObjectType *owner)
{
if (owner == nullptr) {
return SavedCheckerContext(checker, CheckerStatus::NO_OPTS, nullptr);
}
ES2PANDA_ASSERT(!owner->HasObjectFlag(ETSObjectFlags::ENUM));
CheckerStatus const status =
(owner->HasObjectFlag(ETSObjectFlags::CLASS) ? CheckerStatus::IN_CLASS : CheckerStatus::IN_INTERFACE) |
(owner->HasObjectFlag(ETSObjectFlags::ABSTRACT) ? CheckerStatus::IN_ABSTRACT : CheckerStatus::NO_OPTS) |
(owner->HasObjectFlag(ETSObjectFlags::INNER) ? CheckerStatus::INNER_CLASS : CheckerStatus::NO_OPTS) |
(owner->GetDeclNode()->IsClassDefinition() && owner->GetDeclNode()->AsClassDefinition()->IsLocal()
? CheckerStatus::IN_LOCAL_CLASS
: CheckerStatus::NO_OPTS);
return SavedCheckerContext(checker, status, owner);
}
static checker::Type *GetCallExpressionReturnType(ETSChecker *checker, ir::CallExpression *expr,
checker::Type *calleeType)
{
checker::Type *returnType = GetReturnType(checker, expr, calleeType);
if (returnType->IsTypeError()) {
return checker->GlobalTypeError();
}
auto *const signature = expr->Signature();
if (signature->RestVar() != nullptr && signature->RestVar()->TsType()->IsETSArrayType()) {
auto originalArray =
signature->RestVar()->TsType()->AsETSArrayType();
auto *const arrayType =
checker->CreateETSArrayType(originalArray->ElementType(), originalArray->IsValueArray())->AsETSArrayType();
checker->CreateBuiltinArraySignature(arrayType, arrayType->Rank());
}
if (!signature->HasSignatureFlag(checker::SignatureFlags::NEED_RETURN_TYPE) ||
(signature->HasSignatureFlag(checker::SignatureFlags::CONSTRUCTOR))) {
return returnType;
}
if (!signature->HasFunction()) {
return checker->GlobalTypeError();
}
auto owner = const_cast<ETSObjectType *>(util::Helpers::GetContainingObjectType(signature->Function()));
SavedCheckerContext savedCtx(ReconstructOwnerClassContext(checker, owner));
ir::AstNode *methodDef = signature->Function();
while (!methodDef->IsMethodDefinition()) {
methodDef = methodDef->Parent();
ES2PANDA_ASSERT(methodDef != nullptr);
}
ES2PANDA_ASSERT(methodDef->IsMethodDefinition());
methodDef->Check(checker);
if (!signature->Function()->HasBody()) {
return signature->ReturnType();
}
if (signature->Function()->IsExternal()) {
checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(signature->Function()->Body(),
signature->Function()->Scope());
}
checker::ScopeContext scopeCtx(checker, signature->Function()->Body()->Scope());
checker->CollectReturnStatements(signature->Function());
auto *const baseSig = signature->Function() != nullptr ? signature->Function()->Signature() : nullptr;
if (baseSig == nullptr || signature == baseSig) {
return signature->ReturnType();
}
auto *updatedSignature = ResolveSignature(checker, expr, calleeType);
if (updatedSignature == nullptr) {
return checker->GlobalTypeError();
}
expr->SetSignature(updatedSignature);
return updatedSignature->ReturnType();
}
static void CheckOverloadCall(ETSChecker *checker, ir::CallExpression *expr)
{
if (!expr->Callee()->IsMemberExpression() || !checker->IsOverloadDeclaration(expr->Callee())) {
return;
}
auto *sig = expr->Signature();
auto *functionNode = sig->OwnerVar()->Declaration()->Node();
ir::AstNode *parent = functionNode->Parent();
bool isExported = util::Helpers::IsExported(functionNode);
if (parent != nullptr && parent->IsClassDefinition() && parent->AsClassDefinition()->IsNamespaceTransformed() &&
!parent->AsClassDefinition()->IsDeclare() && !isExported) {
checker->LogError(diagnostic::NOT_EXPORTED,
{sig->OwnerVar()->Declaration()->Name(), parent->AsClassDefinition()->Ident()->Name()},
expr->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::CallExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ES2PANDA_ASSERT(!expr->IsOptional());
auto *oldCallee = expr->Callee();
checker::Type *calleeType = checker->GetApparentType(expr->Callee()->Check(checker));
if (calleeType->IsTypeError()) {
return checker->InvalidateType(expr);
}
if (expr->Callee() != oldCallee) {
calleeType = checker->GetApparentType(expr->Callee()->Check(checker));
}
CheckCallee(checker, expr);
checker::TypeStackElement tse(checker, expr, {{diagnostic::CYCLIC_CALLEE, {}}}, expr->Start());
ERROR_SANITY_CHECK(checker, !tse.HasTypeError(), return expr->SetTsType(checker->GlobalTypeError()));
checker::Type *const returnType = GetCallExpressionReturnType(checker, expr, calleeType);
expr->SetTsType(returnType);
if (returnType->IsTypeError()) {
return returnType;
}
if (calleeType->IsETSArrowType() || calleeType->IsETSRelaxedAnyType()) {
expr->SetUncheckedType(
checker->GuaranteedTypeForUncheckedCast(checker->GlobalETSAnyType(), expr->Signature()->ReturnType()));
} else {
expr->SetUncheckedType(checker->GuaranteedTypeForUncheckedCallReturn(expr->Signature()));
}
if (expr->UncheckedType() != nullptr) {
ES2PANDA_ASSERT(expr->UncheckedType()->IsETSReferenceType());
checker->ComputeApparentType(returnType);
}
CheckOverloadCall(checker, expr);
CheckAbstractCall(checker, expr);
CheckAsyncCallInStaticInit(checker, expr);
return expr->TsType();
}
static bool IsNumericType(ETSChecker *checker, Type *type)
{
ES2PANDA_ASSERT(!type->IsETSNeverType());
return !type->IsETSTypeParameter() &&
checker->Relation()->IsSupertypeOf(checker->GetGlobalTypesHolder()->GlobalNumericBuiltinType(), type);
}
static Type *BiggerNumericType(ETSChecker *checker, Type *t1, Type *t2)
{
ES2PANDA_ASSERT(IsNumericType(checker, t1));
ES2PANDA_ASSERT(IsNumericType(checker, t2));
auto *rel = checker->Relation();
if (rel->IsSupertypeOf(checker->GlobalDoubleBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalDoubleBuiltinType(), t2)) {
return checker->GlobalDoubleBuiltinType();
}
if (rel->IsSupertypeOf(checker->GlobalFloatBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalFloatBuiltinType(), t2)) {
return checker->GlobalFloatBuiltinType();
}
if (rel->IsSupertypeOf(checker->GlobalLongBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalLongBuiltinType(), t2)) {
return checker->GlobalLongBuiltinType();
}
if (rel->IsSupertypeOf(checker->GlobalIntBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalIntBuiltinType(), t2)) {
return checker->GlobalIntBuiltinType();
}
if (rel->IsSupertypeOf(checker->GlobalShortBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalShortBuiltinType(), t2)) {
return checker->GlobalShortBuiltinType();
}
if (rel->IsSupertypeOf(checker->GlobalByteBuiltinType(), t1) ||
rel->IsSupertypeOf(checker->GlobalByteBuiltinType(), t2)) {
return checker->GlobalByteBuiltinType();
}
ES2PANDA_UNREACHABLE();
}
static void ApplyTestedSmartCasts(ETSChecker *const checker, const SmartCastTypes &testedTypes,
bool const consequentBranch)
{
if (!testedTypes.has_value()) {
return;
}
for (auto [variable, consequentType, alternateType] : *testedTypes) {
checker->ApplySmartCast(variable, consequentBranch ? consequentType : alternateType);
}
}
checker::Type *ETSAnalyzer::Check(ir::ConditionalExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *const checker = GetETSChecker();
SmartCastArray smartCasts = checker->Context().EnterTestExpression();
checker->CheckTruthinessOfType(expr->Test());
SmartCastTypes testedTypes = checker->Context().ExitTestExpression();
auto const testValue = TryResolveConditionalTestValue(expr->Test());
if (testValue.has_value()) {
auto const checkBranch = [checker, &testedTypes](ir::Expression *branch, bool takeConsequentSmartCast,
bool suppressDiagnostics) {
ApplyTestedSmartCasts(checker, testedTypes, takeConsequentSmartCast);
if (suppressDiagnostics) {
checker::InferMatchContext silentBranchCheckCtx(checker, util::DiagnosticType::SEMANTIC,
branch->Range(), false);
Type *const branchType = branch->Check(checker);
return branchType;
}
Type *const branchType = branch->Check(checker);
return branchType;
};
auto *const takenBranch = testValue.value() ? expr->Consequent() : expr->Alternate();
auto *const deadBranch = testValue.value() ? expr->Alternate() : expr->Consequent();
auto *const takenType = checkBranch(takenBranch, testValue.value(), false);
checker->Context().RestoreSmartCasts(smartCasts);
(void)checkBranch(deadBranch, !testValue.value(), true);
expr->SetTsType(takenType);
checker->Context().RestoreSmartCasts(smartCasts);
return expr->TsType();
}
ApplyTestedSmartCasts(checker, testedTypes, true);
auto *consequent = expr->Consequent();
Type *consequentType = consequent->Check(checker);
SmartCastArray consequentSmartCasts = checker->Context().CloneSmartCasts();
checker->Context().RestoreSmartCasts(smartCasts);
ApplyTestedSmartCasts(checker, testedTypes, false);
auto *alternate = expr->Alternate();
Type *alternateType = alternate->Check(checker);
checker->Context().CombineSmartCasts(consequentSmartCasts);
if (consequentType->IsETSNeverType()) {
expr->SetTsType(alternateType);
} else if (alternateType->IsNeverType()) {
expr->SetTsType(consequentType);
} else if (checker->IsTypeIdenticalTo(consequentType, alternateType)) {
expr->SetTsType(consequentType);
} else {
Type *consequentTypeUnderly =
consequentType->IsETSNumericEnumType() ? consequentType->AsETSEnumType()->Underlying() : consequentType;
Type *alternateTypeUnderly =
alternateType->IsETSNumericEnumType() ? alternateType->AsETSEnumType()->Underlying() : alternateType;
if (IsNumericType(GetETSChecker(), consequentTypeUnderly) &&
IsNumericType(GetETSChecker(), alternateTypeUnderly)) {
if (consequentType->IsETSNumericEnumType()) {
expr->Consequent()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
if (alternateType->IsETSNumericEnumType()) {
expr->Alternate()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
expr->SetTsType(BiggerNumericType(GetETSChecker(), consequentTypeUnderly, alternateTypeUnderly));
} else {
expr->SetTsType(checker->CreateETSUnionType({consequentType, alternateType}));
}
}
checker->Context().RestoreSmartCasts(smartCasts);
return expr->TsType();
}
static bool HasGenericTypeParams(const checker::Type *type)
{
if (type == nullptr || type->IsTypeError() || !type->IsETSFunctionType()) {
return false;
}
auto *functionType = type->AsETSFunctionType();
auto &sigs = functionType->CallSignaturesOfMethodOrArrow();
for (auto *sig : sigs) {
if (!sig->TypeParams().empty()) {
return true;
}
}
return false;
}
static bool IsUsedAsCall(const ir::Expression *expr)
{
auto *p = expr->Parent();
return p != nullptr && p->IsCallExpression() && expr == p->AsCallExpression()->Callee();
}
static Type *CheckExplicitTypeArgumentsRequired(ETSChecker *checker, ir::Expression *const use, Type *type)
{
ir::Expression *top = use;
while (top->Parent()->IsMemberExpression() && !top->Parent()->AsMemberExpression()->IsComputed() &&
top->Parent()->AsMemberExpression()->Property() == top) {
top = top->Parent()->AsMemberExpression();
}
const bool usedAsCall = IsUsedAsCall(top);
const bool usedAsExplicitInstantiation = top->Parent()->IsETSGenericInstantiatedNode();
const bool hasGenericTypeParams = HasGenericTypeParams(type);
if (hasGenericTypeParams && !usedAsCall && !usedAsExplicitInstantiation) {
util::StringView name;
util::StringView func = "function";
if (use->IsIdentifier()) {
name = use->AsIdentifier()->Name();
} else {
auto *prop = use->AsMemberExpression()->Property();
name = prop->IsIdentifier() ? prop->AsIdentifier()->Name() : func;
}
checker->LogError(diagnostic::EXPLICIT_TYPE_ARGUMENTS_REQUIRED, {name}, use->Start());
return checker->GlobalTypeError();
}
return nullptr;
}
static Type *HandleNonMethodAndCallSite(ETSChecker *checker, ir::Expression *use, Type *type)
{
auto *parent = use->Parent();
if (!type->IsETSMethodType()) {
if (parent != nullptr && parent->IsCallExpression() && type->IsETSObjectType() && use->IsMemberExpression()) {
checker->ValidateCallExpressionIdentifier(use->AsMemberExpression()->Property()->AsIdentifier(), type);
}
return type;
}
ir::Expression *expr = use;
while (expr->Parent()->IsMemberExpression() && !expr->Parent()->AsMemberExpression()->IsComputed() &&
expr->Parent()->AsMemberExpression()->Property() == expr) {
expr = expr->Parent()->AsMemberExpression();
}
parent = expr->Parent();
if (parent != nullptr) {
if (parent->IsCallExpression() && parent->AsCallExpression()->Callee() == expr) {
return type;
}
if (parent->IsOverloadDeclaration()) {
return type;
}
if (parent->IsAssignmentExpression() && parent->AsAssignmentExpression()->Left() == expr) {
return type;
}
}
ES2PANDA_ASSERT(type->IsETSMethodType());
const auto *funcType = type->AsETSFunctionType();
for (auto *sig : funcType->CallSignatures()) {
if (sig->Function() != nullptr && sig->Function()->IsGetter()) {
return sig->ReturnType();
}
}
return nullptr;
}
static Type *TransformMethodTypeToArrow(ETSChecker *checker, ir::Expression *use, Type *methodType)
{
auto *functionType = methodType->AsETSFunctionType();
auto &signatures = functionType->CallSignatures();
auto getUseSite = [use]() {
return use->IsIdentifier() ? use->Start() : use->AsMemberExpression()->Property()->Start();
};
if (!signatures.empty()) {
auto *first = signatures.front();
if (first->HasSignatureFlag(SignatureFlags::PRIVATE)) {
checker->LogError(diagnostic::PRIVATE_OR_PROTECTED_METHOD_AS_VALUE, {"Private"}, getUseSite());
return checker->GlobalTypeError();
}
if (first->HasSignatureFlag(SignatureFlags::PROTECTED)) {
checker->LogError(diagnostic::PRIVATE_OR_PROTECTED_METHOD_AS_VALUE, {"Protected"}, getUseSite());
return checker->GlobalTypeError();
}
}
auto it = signatures.begin();
while (it != signatures.end()) {
if ((*it)->HasSignatureFlag(SignatureFlags::ABSTRACT) &&
!(*it)->Owner()->GetDeclNode()->IsTSInterfaceDeclaration()) {
it = signatures.erase(it);
} else {
++it;
}
}
if (signatures.size() > 1U) {
checker->LogError(diagnostic::OVERLOADED_METHOD_AS_VALUE, getUseSite());
return checker->GlobalTypeError();
}
auto *otherFuncType = functionType->MethodToArrow(checker);
return otherFuncType == nullptr ? checker->GlobalTypeError() : otherFuncType;
}
static Type *TransformTypeForMethodReference(ETSChecker *checker, ir::Expression *const use, Type *type)
{
ES2PANDA_ASSERT(use->IsIdentifier() || use->IsMemberExpression());
if (auto *errType = CheckExplicitTypeArgumentsRequired(checker, use, type); errType != nullptr) {
return errType;
}
if (auto *early = HandleNonMethodAndCallSite(checker, use, type); early != nullptr) {
return early;
}
return TransformMethodTypeToArrow(checker, use, type);
}
checker::Type *ETSAnalyzer::Check(ir::Identifier *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
auto *type = checker->ResolveIdentifier(expr);
auto *identType = TransformTypeForMethodReference(checker, expr, type);
if (expr->TsType() != nullptr && expr->TsType()->IsTypeError()) {
return expr->TsType();
}
ES2PANDA_ASSERT(expr->Variable() != nullptr);
if (expr->Parent() != nullptr &&
!(expr->Parent()->IsAssignmentExpression() && expr == expr->Parent()->AsAssignmentExpression()->Left()) &&
!expr->Parent()->IsETSDestructuring()) {
auto *const smartType = checker->Context().GetSmartCast(expr->Variable());
if (smartType != nullptr) {
identType = smartType;
}
}
ES2PANDA_ASSERT(identType != nullptr);
expr->SetTsType(identType);
ES2PANDA_ASSERT(identType != nullptr);
if (!identType->IsTypeError()) {
checker->Context().CheckIdentifierSmartCastCondition(expr);
}
return expr->TsType();
}
std::pair<checker::Type *, util::StringView> SearchReExportsType(ETSObjectType *baseType, ir::MemberExpression *expr,
util::StringView const &aliasName, ETSChecker *checker)
{
std::pair<ETSObjectType *, util::StringView> ret {};
for (auto *const item : baseType->ReExports()) {
auto name = item->GetReExportAliasValue(aliasName);
if (name == aliasName && item->IsReExportHaveAliasValue(name)) {
continue;
}
if (item->GetProperty(name, PropertySearchFlags::SEARCH_ALL) != nullptr) {
if (ret.first != nullptr) {
checker->LogError(diagnostic::AMBIGUOUS_REFERENCE, {aliasName}, expr->Start());
expr->SetTsType(checker->GlobalTypeError());
return ret;
}
ret = {item, name};
}
if (auto reExportType = SearchReExportsType(item, expr, name, checker); reExportType.first != nullptr) {
return reExportType;
}
}
return ret;
}
static void TypeErrorOnMissingProperty(ir::MemberExpression *expr, checker::Type *baseType,
checker::ETSChecker *checker)
{
std::ignore = checker->TypeError(expr, diagnostic::PROPERTY_NONEXISTENT,
{expr->Property()->AsIdentifier()->Name(), baseType}, expr->Object()->Start());
}
checker::Type *ETSAnalyzer::ResolveMemberExpressionByBaseType(ETSChecker *checker, checker::Type *baseType,
ir::MemberExpression *expr) const
{
if (baseType->IsTypeError()) {
return checker->InvalidateType(expr);
}
if (baseType->IsETSRelaxedAnyType()) {
return expr->AdjustType(checker, checker->GlobalETSRelaxedAnyType());
}
if (baseType->IsETSArrayType()) {
if (expr->Property()->AsIdentifier()->Name().Is("length")) {
return expr->AdjustType(checker, checker->GlobalIntBuiltinType());
}
return expr->SetAndAdjustType(checker, checker->GlobalETSObjectType());
}
if (baseType->IsETSTupleType()) {
return expr->SetAndAdjustType(checker, checker->GlobalETSObjectType());
}
if (baseType->IsETSFunctionType()) {
return expr->SetAndAdjustType(checker, checker->GlobalBuiltinFunctionType());
}
if (baseType->IsETSObjectType()) {
checker->ETSObjectTypeDeclNode(checker, baseType->AsETSObjectType());
auto *memberType = expr->SetAndAdjustType(checker, baseType->AsETSObjectType());
if (!checker->CheckSuperMemberBeforeCtorCall(expr)) {
return checker->InvalidateType(expr);
}
return expr->SetTsType(TransformTypeForMethodReference(checker, expr, memberType));
}
if (baseType->IsETSUnionType()) {
return expr->AdjustType(checker, expr->CheckUnionMember(checker, baseType));
}
TypeErrorOnMissingProperty(expr, baseType, checker);
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::MemberExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ES2PANDA_ASSERT(!expr->IsOptional());
ETSChecker *checker = GetETSChecker();
if (expr->Object()->IsSuperExpression() && IsExpressionInClassPropertyInitializer(expr)) {
checker->LogDiagnostic(diagnostic::THIS_OR_SUPER_IN_FIELD_INITIALIZER, {"super"}, expr->Object()->Start());
}
auto *baseType = checker->GetNonConstantType(checker->GetApparentType(expr->Object()->Check(checker)));
ES2PANDA_ASSERT(baseType != nullptr);
if (baseType->DefinitelyETSNullish() && expr->Object()->IsIdentifier()) {
baseType = expr->Object()->AsIdentifier()->Variable()->TsType();
}
if (baseType->IsETSObjectType() && !baseType->AsETSObjectType()->ReExports().empty() &&
baseType->AsETSObjectType()->GetProperty(expr->Property()->AsIdentifier()->Name(),
PropertySearchFlags::SEARCH_ALL) == nullptr) {
if (auto reExportType = SearchReExportsType(baseType->AsETSObjectType(), expr,
expr->Property()->AsIdentifier()->Name(), checker);
reExportType.first != nullptr) {
baseType = reExportType.first;
expr->object_->SetTsType(baseType);
expr->property_->AsIdentifier()->SetName(reExportType.second);
}
}
if (!checker->CheckNonNullish(expr->Object())) {
auto *invalidType = checker->HasStatus(checker::CheckerStatus::IN_EXTENSION_ACCESSOR_CHECK)
? checker->GlobalETSUnionUndefinedNull()
: checker->InvalidateType(expr);
return invalidType;
}
if (expr->IsComputed()) {
return expr->AdjustType(checker, expr->CheckComputed(checker, baseType));
}
if (baseType->IsETSTupleType()) {
baseType = baseType->AsETSTupleType()->GetWrapperType();
}
if (checker->IsPromiseType(baseType) &&
expr->Property()->AsIdentifier()->Name().Is(compiler::Signatures::AWAIT_SYNC)) {
checker->LogDiagnostic(diagnostic::AWAIT_ASYNC_USAGE, {}, expr->Start());
}
return ResolveMemberExpressionByBaseType(checker, baseType, expr);
}
checker::Type *ETSAnalyzer::CheckDynamic(ir::ObjectExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
for (ir::Expression *propExpr : expr->Properties()) {
ES2PANDA_ASSERT(propExpr->IsProperty());
ir::Property *prop = propExpr->AsProperty();
ir::Expression *value = prop->Value();
value->Check(checker);
ES2PANDA_ASSERT(value->TsType());
}
expr->SetTsType(expr->PreferredType());
return expr->PreferredType();
}
static bool ValidatePreferredType(ETSChecker *checker, ir::ObjectExpression *expr)
{
auto preferredType = expr->PreferredType();
if (preferredType == nullptr) {
checker->LogError(diagnostic::CLASS_COMPOSITE_UNKNOWN_TYPE, {}, expr->Start());
return false;
}
if (preferredType->IsTypeError()) {
return false;
}
if (!preferredType->IsETSObjectType()) {
checker->LogError(diagnostic::CLASS_COMPOSITE_INVALID_TARGET, {preferredType}, expr->Start());
return false;
}
return true;
}
static void SetTypeforRecordProperties(const ir::ObjectExpression *expr, checker::ETSObjectType *objType,
ETSChecker *checker)
{
const auto &recordProperties = expr->Properties();
auto typeArguments = objType->TypeArguments();
auto *const valueType = typeArguments[1];
for (auto *const recordProperty : recordProperties) {
ir::Expression *recordPropertyExpr = nullptr;
if (recordProperty->IsProperty()) {
recordProperty->AsProperty()->Key()->SetPreferredType(typeArguments[0]);
recordProperty->AsProperty()->Key()->Check(checker);
recordPropertyExpr = recordProperty->AsProperty()->Value();
} else if (recordProperty->IsSpreadElement()) {
recordPropertyExpr = recordProperty->AsSpreadElement()->Argument();
} else if (recordProperty->IsIdentifier() && recordProperty->AsIdentifier()->IsErrorPlaceHolder()) {
ES2PANDA_ASSERT(checker->IsAnyError());
continue;
} else {
ES2PANDA_UNREACHABLE();
}
recordPropertyExpr->SetPreferredType(valueType);
recordPropertyExpr->Check(checker);
}
}
static bool HasParameterlessConstructor(checker::ETSObjectType *objType, ETSChecker *checker,
const lexer::SourcePosition &pos)
{
for (checker::Signature *sig : objType->ConstructSignatures()) {
if (sig->MinArgCount() == 0) {
checker->ValidateSignatureAccessibility(objType, sig, pos);
return true;
}
}
return false;
}
static std::optional<util::StringView> GetPropertyNameFromKey(ir::Expression *key)
{
if (key->IsStringLiteral()) {
return key->AsStringLiteral()->Str();
}
if (key->IsIdentifier()) {
return key->AsIdentifier()->Name();
}
return std::nullopt;
}
static checker::PropertySearchFlags DetermineSearchFlagsForLiteral(checker::ETSObjectType *potentialObjType)
{
if (potentialObjType->HasObjectFlag(checker::ETSObjectFlags::INTERFACE)) {
return checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD |
checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
checker::PropertySearchFlags::SEARCH_INSTANCE_DECL | checker::PropertySearchFlags::SEARCH_IN_INTERFACES;
}
return checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD | checker::PropertySearchFlags::SEARCH_IN_BASE |
checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD;
}
static bool CheckSinglePropertyCompatibility(ir::Expression *propExpr, checker::ETSObjectType *potentialObjType)
{
if (!propExpr->IsProperty()) {
return false;
}
ir::Expression *key = propExpr->AsProperty()->Key();
std::optional<util::StringView> optPname = GetPropertyNameFromKey(key);
if (!optPname.has_value()) {
return false;
}
util::StringView pname = optPname.value();
checker::PropertySearchFlags searchFlags = DetermineSearchFlagsForLiteral(potentialObjType);
return potentialObjType->GetProperty(pname, searchFlags) != nullptr;
}
static bool CheckObjectLiteralCompatibility(ir::ObjectExpression *expr, checker::ETSObjectType *potentialObjType)
{
for (ir::Expression *propExpr : expr->Properties()) {
if (!CheckSinglePropertyCompatibility(propExpr, potentialObjType)) {
return false;
}
}
return true;
}
static bool IsPropertyTypeOptional(checker::Type *propertyType)
{
if (!propertyType->IsETSUnionType()) {
return false;
}
auto *unionType = propertyType->AsETSUnionType();
for (auto *constituentType : unionType->ConstituentTypes()) {
if (constituentType->IsETSUndefinedType()) {
return true;
}
}
return false;
}
static bool HasPropertyDefaultValue(varbinder::LocalVariable *property)
{
auto *decl = property->Declaration();
if (decl == nullptr || decl->Node() == nullptr || !decl->Node()->IsClassProperty()) {
return false;
}
auto *classProp = decl->Node()->AsClassProperty();
return classProp->Value() != nullptr;
}
static bool IsPropertyOptional(varbinder::LocalVariable *property, checker::Type *propertyType)
{
if (property->HasFlag(varbinder::VariableFlags::OPTIONAL)) {
return true;
}
if (IsPropertyTypeOptional(propertyType)) {
return true;
}
auto *decl = property->Declaration();
if (decl != nullptr && decl->Node() != nullptr && decl->Node()->IsClassProperty()) {
auto *classProp = decl->Node()->AsClassProperty();
if (classProp->IsOptionalDeclaration()) {
return true;
}
}
return false;
}
static bool IsMethodOnlyAccessors(checker::Type *propertyType)
{
if (!propertyType->IsETSMethodType()) {
return false;
}
auto methodType = propertyType->AsETSFunctionType();
for (auto *sig : methodType->CallSignatures()) {
if (!sig->HasSignatureFlag(checker::SignatureFlags::GETTER) &&
!sig->HasSignatureFlag(checker::SignatureFlags::SETTER)) {
return false;
}
}
return true;
}
static bool IsInterfacePropertyCompatible(ir::Expression *propExpr, checker::ETSObjectType *interfaceType,
ETSChecker *checker)
{
if (!propExpr->IsProperty()) {
return false;
}
ir::Expression *key = propExpr->AsProperty()->Key();
std::optional<util::StringView> optPname = GetPropertyNameFromKey(key);
if (!optPname.has_value()) {
return false;
}
util::StringView pname = optPname.value();
varbinder::LocalVariable *property =
interfaceType->GetProperty(pname, checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD |
checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
checker::PropertySearchFlags::SEARCH_INSTANCE_DECL |
checker::PropertySearchFlags::SEARCH_IN_INTERFACES);
if (property == nullptr) {
return false;
}
auto *propertyType = checker->GetTypeOfVariable(property);
if (propertyType->IsETSMethodType()) {
return IsMethodOnlyAccessors(propertyType);
}
return true;
}
static bool AreAllRequiredInterfacePropertiesSatisfied(ir::ObjectExpression *expr,
checker::ETSObjectType *interfaceType, ETSChecker *checker)
{
auto allProperties = interfaceType->GetAllProperties();
std::unordered_set<std::string_view> literalProperties;
for (ir::Expression *propExpr : expr->Properties()) {
if (propExpr->IsProperty()) {
ir::Expression *key = propExpr->AsProperty()->Key();
if (auto optPname = GetPropertyNameFromKey(key); optPname.has_value()) {
literalProperties.insert(optPname.value().Utf8());
}
}
}
for (const auto &litPropName : literalProperties) {
bool found = false;
for (auto *property : allProperties) {
if (property->Name().Utf8() == litPropName) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
for (auto *property : allProperties) {
auto *propertyType = checker->GetTypeOfVariable(property);
if (propertyType->IsETSMethodType()) {
if (!IsMethodOnlyAccessors(propertyType)) {
return false;
}
}
bool isInLiteral = literalProperties.find(property->Name().Utf8()) != literalProperties.end();
if (!isInLiteral) {
bool isOptional = IsPropertyOptional(property, propertyType);
bool hasDefaultValue = HasPropertyDefaultValue(property);
if (!isOptional && !hasDefaultValue) {
return false;
}
}
}
return true;
}
static bool IsObjectTypeCompatibleWithLiteral(ETSChecker *checker, ir::ObjectExpression *expr,
checker::ETSObjectType *potentialObjType)
{
checker::ETSObjectType *originalBaseType = potentialObjType->GetOriginalBaseType();
checker::GlobalTypesHolder *globalTypes = checker->GetGlobalTypesHolder();
if (checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalMapBuiltinType()) ||
checker->IsTypeIdenticalTo(originalBaseType, globalTypes->GlobalRecordBuiltinType())) {
return true;
}
if (potentialObjType->HasObjectFlag(checker::ETSObjectFlags::INTERFACE)) {
for (ir::Expression *propExpr : expr->Properties()) {
if (!IsInterfacePropertyCompatible(propExpr, potentialObjType, checker)) {
return false;
}
}
return AreAllRequiredInterfacePropertiesSatisfied(expr, potentialObjType, checker);
}
if (!HasParameterlessConstructor(potentialObjType, checker, expr->Start())) {
return false;
}
return CheckObjectLiteralCompatibility(expr, potentialObjType);
}
static checker::ETSObjectType *ResolveObjectTypeFromPreferredType(ir::ObjectExpression *expr)
{
checker::Type *preferredType = expr->PreferredType();
if (preferredType->IsETSObjectType()) {
return preferredType->AsETSObjectType();
}
return nullptr;
}
static checker::Type *HandleInterfaceType(ETSChecker *checker, ir::ObjectExpression *expr,
checker::ETSObjectType *objType)
{
auto *analyzer = static_cast<checker::ETSAnalyzer *>(checker->GetAnalyzer());
analyzer->CheckObjectExprProps(
expr, objType,
checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD | checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
checker::PropertySearchFlags::SEARCH_INSTANCE_DECL | checker::PropertySearchFlags::SEARCH_IN_INTERFACES);
expr->SetTsType(objType);
return objType;
}
static checker::Type *HandleRecordOrMapType(ETSChecker *checker, ir::ObjectExpression *expr,
checker::ETSObjectType *objType)
{
expr->SetTsType(objType);
SetTypeforRecordProperties(expr, objType, checker);
return objType;
}
static bool IsMapOrRecordBuiltinType(checker::ETSChecker *checker, checker::ETSObjectType *objType)
{
checker::ETSObjectType *originalBaseObjType = objType->GetOriginalBaseType();
checker::GlobalTypesHolder *globalTypes = checker->GetGlobalTypesHolder();
return checker->IsTypeIdenticalTo(originalBaseObjType, globalTypes->GlobalMapBuiltinType()) ||
checker->IsTypeIdenticalTo(originalBaseObjType, globalTypes->GlobalRecordBuiltinType());
}
checker::Type *ETSAnalyzer::CheckObjectExprBaseOnObjectType(ir::ObjectExpression *expr,
checker::ETSObjectType *objType) const
{
ETSChecker *checker = GetETSChecker();
if (objType == nullptr) {
if (!expr->PreferredType()->IsETSUnionType()) {
checker->LogError(diagnostic::CLASS_COMPOSITE_INVALID_TARGET, {expr->PreferredType()}, expr->Start());
}
return checker->GlobalTypeError();
}
if (objType->HasObjectFlag(checker::ETSObjectFlags::INTERFACE)) {
return HandleInterfaceType(checker, expr, objType);
}
if (IsMapOrRecordBuiltinType(checker, objType)) {
return HandleRecordOrMapType(checker, expr, objType);
}
if (!HasParameterlessConstructor(objType, checker, expr->Start())) {
expr->SetTsType(checker->TypeError(expr, diagnostic::NO_PARAMLESS_CTOR, {objType->Name()}, expr->Start()));
return expr->TsType();
}
CheckObjectExprProps(expr, objType,
checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD |
checker::PropertySearchFlags::SEARCH_IN_BASE |
checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD);
return objType;
}
static checker::Type *TryFindPublicBaseType(checker::ETSChecker *checker,
const std::vector<checker::ETSObjectType *> &matchingObjectTypes)
{
ES2PANDA_ASSERT(matchingObjectTypes.size() > 1);
Type *candidateBaseType = matchingObjectTypes.front();
for (auto *tp : matchingObjectTypes) {
if (checker->Relation()->IsSupertypeOf(tp, candidateBaseType)) {
candidateBaseType = tp;
}
}
for (auto *tp : matchingObjectTypes) {
if (!checker->Relation()->IsSupertypeOf(candidateBaseType, tp)) {
return nullptr;
}
}
return candidateBaseType;
}
checker::Type *ETSAnalyzer::CheckObjectExprBaseOnUnionType(ir::ObjectExpression *expr,
checker::ETSUnionType *preferredType) const
{
ETSChecker *checker = GetETSChecker();
std::vector<checker::ETSObjectType *> candidateObjectTypes;
for (auto *constituentType : preferredType->ConstituentTypes()) {
if (constituentType->IsETSObjectType()) {
candidateObjectTypes.push_back(constituentType->AsETSObjectType());
}
}
std::vector<checker::ETSObjectType *> matchingObjectTypes;
for (auto *potentialObjType : candidateObjectTypes) {
if (!IsObjectTypeCompatibleWithLiteral(checker, expr, potentialObjType)) {
continue;
}
expr->SetPreferredType(potentialObjType);
InferMatchContext specificTypeMatchCtx(checker, util::DiagnosticType::SEMANTIC, expr->Range(), false);
CheckObjectExprBaseOnObjectType(expr, potentialObjType);
if (IsMapOrRecordBuiltinType(checker, potentialObjType)) {
checker->CheckRecordType(expr, potentialObjType);
}
if (specificTypeMatchCtx.ValidMatchStatus()) {
matchingObjectTypes.emplace_back(potentialObjType);
}
expr->CleanCheckInformation();
expr->SetPreferredType(preferredType);
}
if (matchingObjectTypes.empty()) {
checker->LogError(diagnostic::CLASS_COMPOSITE_INVALID_TARGET, {expr->PreferredType()}, expr->Start());
return checker->GlobalTypeError();
}
if (matchingObjectTypes.size() > 1) {
if (auto *specificType = TryFindPublicBaseType(checker, matchingObjectTypes); specificType != nullptr) {
expr->SetPreferredType(specificType);
CheckObjectExprBaseOnObjectType(expr, specificType->AsETSObjectType());
return specificType;
}
checker->LogError(diagnostic::AMBIGUOUS_REFERENCE, {expr->PreferredType()->ToString()}, expr->Start());
return checker->GlobalTypeError();
}
expr->SetPreferredType(matchingObjectTypes.front());
CheckObjectExprBaseOnObjectType(expr, matchingObjectTypes.front()->AsETSObjectType());
return matchingObjectTypes.front();
}
checker::Type *ETSAnalyzer::Check(ir::ObjectExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
if (expr->PreferredType() == nullptr) {
checker->LogError(diagnostic::CLASS_COMPOSITE_UNKNOWN_TYPE, {}, expr->Start());
expr->SetTsType(checker->GlobalTypeError());
return expr->TsType();
}
auto actualPreferredType = expr->PreferredType();
if (actualPreferredType->IsETSTypeAliasType()) {
actualPreferredType = actualPreferredType->AsETSTypeAliasType()->GetTargetType();
expr->SetPreferredType(actualPreferredType);
}
if (!expr->PreferredType()->IsETSUnionType() && !ValidatePreferredType(checker, expr)) {
expr->SetTsType(checker->GlobalTypeError());
return expr->TsType();
}
checker::Type *tsType = nullptr;
if (actualPreferredType->IsETSUnionType()) {
tsType = CheckObjectExprBaseOnUnionType(expr, actualPreferredType->AsETSUnionType());
} else {
tsType = CheckObjectExprBaseOnObjectType(expr, ResolveObjectTypeFromPreferredType(expr));
}
expr->SetTsType(tsType);
return tsType;
}
static void CollectNonOptionalPropertyInterface(checker::ETSChecker *checker, const ETSObjectType *objType,
std::unordered_map<util::StringView, ETSObjectType *> &props)
{
auto const &methodMap = objType->InstanceMethods();
for (const auto &[propName, var] : methodMap) {
if (!checker->IsVariableGetterSetter(var)) {
continue;
}
auto propertyType = checker->GetTypeOfVariable(var);
if (propertyType->IsTypeError()) {
continue;
}
if (var->Declaration()->Node()->IsOptionalDeclaration()) {
continue;
}
props.insert({propName, const_cast<ETSObjectType *>(objType)});
}
}
static void CollectLateInitPropertyClass(const ETSObjectType *objType,
std::unordered_map<util::StringView, ETSObjectType *> &props)
{
auto const &fields = objType->InstanceFields();
for (const auto &[propName, var] : fields) {
if (!var->Declaration()->Node()->IsDefinite()) {
continue;
}
props.insert({propName, const_cast<ETSObjectType *>(objType)});
}
}
void ETSAnalyzer::CollectNonOptionalProperty(const ETSObjectType *objType,
std::unordered_map<util::StringView, ETSObjectType *> &props) const
{
ETSChecker *checker = GetETSChecker();
if (objType->HasObjectFlag(ETSObjectFlags::INTERFACE)) {
CollectNonOptionalPropertyInterface(checker, objType, props);
for (auto const *superInterface : objType->Interfaces()) {
CollectNonOptionalProperty(superInterface, props);
}
}
if (objType->HasObjectFlag(ETSObjectFlags::CLASS)) {
CollectLateInitPropertyClass(objType, props);
if (objType->SuperType() != nullptr) {
CollectNonOptionalProperty(objType->SuperType(), props);
}
}
}
static std::optional<util::StringView> GetNameForProperty(ETSChecker *checker, ir::Expression *const propExpr) noexcept
{
ir::Expression const *const key = propExpr->AsProperty()->Key();
if (key->IsStringLiteral()) {
checker->LogDiagnostic(diagnostic::CLASS_COMPOSITE_KEY_USE_STRING, {}, propExpr->Start());
return std::make_optional(key->AsStringLiteral()->Str());
}
if (key->IsIdentifier()) {
return std::make_optional(key->AsIdentifier()->Name());
}
checker->LogError(diagnostic::CLASS_COMPOSITE_INVALID_KEY, {}, propExpr->Start());
propExpr->SetTsType(checker->GlobalTypeError());
return std::nullopt;
}
static bool IsMethodPropertyAssignable(ETSChecker *const checker, std::string_view const propertyName,
Type const *const propertyType, ir::Expression *const value)
{
ES2PANDA_ASSERT(propertyType->IsETSMethodType() && !propertyType->AsETSFunctionType()->CallSignatures().empty());
Type *const valueType = value->TsType();
if (!valueType->IsETSArrowType()) {
checker->LogError(diagnostic::PROP_INCOMPAT, {valueType, propertyType, propertyName}, value->Start());
return false;
}
auto *const relation = checker->Relation();
auto *const sourceSignature = valueType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow()[0U];
std::string methodType {};
for (auto *const targetSignature : propertyType->AsETSFunctionType()->CallSignatures()) {
if (propertyName != targetSignature->Function()->Id()->Name().Utf8()) {
continue;
}
if (relation->CheckTypeParameterConstraints(sourceSignature->TypeParams(), targetSignature->TypeParams())) {
auto *const substSignature = checker->AdjustForTypeParameters(sourceSignature, targetSignature);
SavedTypeRelationFlagsContext savedFlagsCtx(relation, TypeRelationFlag::OVERRIDING_CONTEXT);
if (relation->SignatureIsSupertypeOf(substSignature, sourceSignature)) {
return true;
}
}
methodType += targetSignature->ToString() + " | ";
}
methodType.resize(methodType.size() - 3U);
checker->LogError(diagnostic::PROP_INCOMPAT, {sourceSignature->ToString(), methodType, propertyName},
value->Start());
return false;
}
static bool ReportInterfaceAccessorMismatchIfNeeded(ETSChecker *const checker, ir::Expression *const propExpr,
varbinder::LocalVariable *const lv,
const util::StringView &propertyName,
ETSObjectType const *const objectType)
{
if (!objectType->HasObjectFlag(ETSObjectFlags::INTERFACE)) {
return false;
}
if (lv->TsType() == nullptr) {
checker->GetTypeOfVariable(lv);
}
auto *const lvType = lv->TsType();
if (lvType != nullptr && lvType->IsETSFunctionType() &&
checker->ReportInterfaceAccessorTypeMismatchIfNeeded(lvType->AsETSFunctionType(), propertyName,
propExpr->Start())) {
propExpr->SetTsType(checker->GlobalTypeError());
return true;
}
return false;
}
static bool ReportReadonlyClassPropertyAssignmentIfNeeded(ETSChecker *const checker, ir::Expression *const propExpr,
varbinder::LocalVariable *const lv,
ETSObjectType const *const objectType)
{
if (objectType->HasObjectFlag(ETSObjectFlags::INTERFACE)) {
return false;
}
auto *const decl = lv->Declaration();
if (decl == nullptr || decl->Node() == nullptr || !decl->Node()->IsClassProperty()) {
return false;
}
auto *const classProp = decl->Node()->AsClassProperty();
if (!classProp->IsReadonly()) {
return false;
}
checker->LogError(diagnostic::FIELD_ASSIGN_TO_READONLY, {lv->Name()}, propExpr->Start());
propExpr->SetTsType(checker->GlobalTypeError());
return true;
}
static bool ReportObjectLiteralMethodRedefinitionIfNeeded(ETSChecker *const checker, ir::Expression *const propExpr,
Type *const propType, ETSObjectType const *const objectType)
{
if (!propType->IsETSMethodType() ||
objectType->HasObjectFlag(ETSObjectFlags::INTERFACE | ETSObjectFlags::ABSTRACT)) {
return false;
}
checker->LogError(diagnostic::OBJECT_LITERAL_METHOD_KEY, {}, propExpr->Start());
propExpr->SetTsType(checker->GlobalTypeError());
return true;
}
static bool IsPropertyAssignable(ETSChecker *const checker, ir::Expression *const propExpr,
varbinder::LocalVariable *const lv, const util::StringView &pname,
ETSObjectType const *const objectType)
{
if (ReportInterfaceAccessorMismatchIfNeeded(checker, propExpr, lv, pname, objectType)) {
return false;
}
if (ReportReadonlyClassPropertyAssignmentIfNeeded(checker, propExpr, lv, objectType)) {
return false;
}
auto *propType = checker->GetTypeOfVariable(lv);
if (auto *setterType = GetSetterType(lv, checker); setterType != nullptr) {
propType = setterType;
}
propExpr->SetTsType(propType);
ir::Expression *key = propExpr->AsProperty()->Key();
key->SetTsType(propType);
ir::Expression *value = propExpr->AsProperty()->Value();
value->SetPreferredType(propType);
if (ReportObjectLiteralMethodRedefinitionIfNeeded(checker, propExpr, propType, objectType)) {
return false;
}
Type *const valueType = value->Check(checker);
bool assignable;
if (!propType->IsETSMethodType()) {
assignable = checker::AssignmentContext(checker->Relation(), value, valueType, propType, value->Start(),
{{diagnostic::PROP_INCOMPAT, {valueType, propType, pname}}})
.IsAssignable();
} else {
assignable = IsMethodPropertyAssignable(checker, pname.Utf8(), propType, value);
}
if (!assignable) {
propExpr->SetTsType(checker->GlobalTypeError());
return false;
}
return true;
}
static void CheckObjectExprPropsHelper(ETSChecker *const checker, const ir::ObjectExpression *expr,
checker::ETSObjectType *objType, checker::PropertySearchFlags const searchFlags,
std::unordered_map<util::StringView, ETSObjectType *> &properties)
{
for (ir::Expression *propExpr : expr->Properties()) {
if (!propExpr->IsProperty()) {
checker->LogError(diagnostic::OBJECT_LITERAL_NOT_KV, {}, expr->Start());
propExpr->SetTsType(checker->GlobalTypeError());
continue;
}
std::optional<util::StringView> propertyName = GetNameForProperty(checker, propExpr);
if (!propertyName.has_value()) {
continue;
}
varbinder::LocalVariable *lv = objType->GetProperty(*propertyName, searchFlags);
if (lv == nullptr) {
checker->LogError(diagnostic::UNDEFINED_PROPERTY, {objType->Name(), *propertyName}, propExpr->Start());
propExpr->SetTsType(checker->GlobalTypeError());
continue;
}
properties.erase(*propertyName);
if (ir::Expression *key = propExpr->AsProperty()->Key(); key->IsIdentifier()) {
key->AsIdentifier()->SetVariable(lv);
}
checker->ValidatePropertyAccess(lv, objType, propExpr);
if (IsTypeError(lv->TsType())) {
propExpr->SetTsType(checker->GlobalTypeError());
continue;
}
if (!IsPropertyAssignable(checker, propExpr, lv, *propertyName, objType)) {
continue;
}
}
}
void ETSAnalyzer::CheckObjectExprProps(const ir::ObjectExpression *expr,
checker::ETSObjectType *objectTypeForProperties,
checker::PropertySearchFlags searchFlags) const
{
ETSChecker *checker = GetETSChecker();
checker::ETSObjectType *objType = objectTypeForProperties;
if (objType->IsGlobalETSObjectType() && !expr->Properties().empty()) {
checker->LogError(diagnostic::ERROR_ARKTS_NO_UNTYPED_OBJ_LITERALS, expr->Start());
}
std::unordered_map<util::StringView, ETSObjectType *> propertyWithNonOptionalType;
CollectNonOptionalProperty(objType, propertyWithNonOptionalType);
CheckObjectExprPropsHelper(checker, expr, objType, searchFlags, propertyWithNonOptionalType);
for (const auto &[propName, ownerType] : propertyWithNonOptionalType) {
if (objType == ownerType) {
checker->LogError(diagnostic::OBJECT_LITERAL_NON_OPTIONAL_PROP_LOST, {propName, objType}, expr->Start());
} else {
checker->LogError(diagnostic::OBJECT_LITERAL_NON_OPTIONAL_PROP_OF_SUPER_LOST,
{propName, ownerType, objType}, expr->Start());
}
}
if (objType->HasObjectFlag(ETSObjectFlags::REQUIRED)) {
checker->ValidateObjectLiteralForRequiredType(objType, expr);
}
}
checker::Type *ETSAnalyzer::Check(ir::OpaqueTypeNode *expr) const
{
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::BrokenTypeNode *expr) const
{
return GetETSChecker()->GlobalTypeError();
}
checker::Type *ETSAnalyzer::Check(ir::SequenceExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
for (auto *it : expr->Sequence()) {
it->Check(checker);
}
ES2PANDA_ASSERT(!expr->Sequence().empty());
return expr->SetTsType(expr->Sequence().back()->TsType());
}
checker::Type *ETSAnalyzer::Check(ir::SuperExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
return expr->SetTsType(
checker->CheckThisOrSuperAccess(expr, checker->Context().ContainingClass()->SuperType(), "super"));
}
checker::Type *ETSAnalyzer::Check(ir::TemplateLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
for (auto *it : expr->Expressions()) {
it->Check(checker);
}
if (expr->TsType() != nullptr) {
return expr->TsType();
}
if (expr->Quasis().size() != expr->Expressions().size() + 1U) {
checker->LogError(diagnostic::TEMPLATE_COUNT_MISMATCH, {}, expr->Start());
return expr->SetTsType(checker->GlobalTypeError());
}
for (auto *it : expr->Quasis()) {
it->Check(checker);
}
return expr->SetTsType(checker->CreateETSStringLiteralType(expr->GetMultilineString()));
}
checker::Type *ETSAnalyzer::Check(ir::ThisExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
example code:
```
class A {
prop
}
function A.method() {
let a = () => {
console.println(this.prop)
}
}
is identical to
function method(this: A) {
let a = () => {
console.println(this.prop)
}
}
```
here when "this" is used inside an extension function, we need to bind "this" to the first
parameter(MANDATORY_PARAM_THIS), and capture the parameter's variable other than containing class's variable
*/
auto *variable = checker->AsETSChecker()->Scope()->Find(varbinder::VarBinder::MANDATORY_PARAM_THIS).variable;
if (checker->HasStatus(checker::CheckerStatus::IN_EXTENSION_METHOD)) {
ES2PANDA_ASSERT(variable != nullptr);
expr->SetTsType(variable->TsType());
} else {
auto *thisContextType = checker->Context().ContainingClass();
if (auto *objectLiteralThisType = GetObjectLiteralMethodThisType(expr); objectLiteralThisType != nullptr) {
thisContextType = objectLiteralThisType;
}
expr->SetTsType(checker->CheckThisOrSuperAccess(expr, thisContextType, "this"));
}
return expr->TsType();
}
static checker::Type *checkUnboxedTypeKind(TypeFlag unboxedFlag, ETSChecker *checker)
{
switch (unboxedFlag) {
case TypeFlag::ETS_BOOLEAN:
return checker->CreateETSStringLiteralType("boolean");
case TypeFlag::BYTE:
return checker->CreateETSStringLiteralType("byte");
case TypeFlag::CHAR:
return checker->CreateETSStringLiteralType("char");
case TypeFlag::SHORT:
return checker->CreateETSStringLiteralType("short");
case TypeFlag::INT:
return checker->CreateETSStringLiteralType("int");
case TypeFlag::LONG:
return checker->CreateETSStringLiteralType("long");
case TypeFlag::FLOAT:
return checker->CreateETSStringLiteralType("float");
case TypeFlag::DOUBLE:
return checker->CreateETSStringLiteralType("number");
case TypeFlag::ETS_VOID:
return checker->CreateETSStringLiteralType("undefined");
default:
ES2PANDA_UNREACHABLE();
}
}
static checker::Type *GetTypeOfStringType(checker::Type *argType, ETSChecker *checker)
{
if (auto unboxed = checker->MaybeUnboxType(argType); unboxed->IsETSPrimitiveType()) {
return checkUnboxedTypeKind(checker->TypeKind(unboxed), checker);
}
if (argType->IsETSUndefinedType()) {
return checker->CreateETSStringLiteralType("undefined");
}
if (argType->IsETSArrayType() || argType->IsETSNullType() || argType->IsETSResizableArrayType()) {
return checker->CreateETSStringLiteralType("object");
}
if (argType->IsETSStringType()) {
return checker->CreateETSStringLiteralType("string");
}
if (argType->IsETSBigIntType()) {
return checker->CreateETSStringLiteralType("bigint");
}
if (argType->IsETSFunctionType()) {
return checker->CreateETSStringLiteralType("function");
}
if (argType->IsETSNumericEnumType()) {
auto unboxedType = checker->MaybeUnboxType(argType->AsETSEnumType()->Underlying());
return checkUnboxedTypeKind(checker->TypeKind(unboxedType), checker);
}
if (argType->IsETSStringEnumType()) {
return checker->CreateETSStringLiteralType("string");
}
return checker->GlobalBuiltinETSStringType();
}
static checker::Type *ComputeTypeOfType(ETSChecker *checker, checker::Type *argType)
{
checker::Type *ret = nullptr;
std::vector<checker::Type *> types;
ES2PANDA_ASSERT(argType != nullptr);
if (argType->IsETSUnionType()) {
for (auto *it : argType->AsETSUnionType()->ConstituentTypes()) {
checker::Type *elType = ComputeTypeOfType(checker, it);
types.push_back(elType);
}
ret = checker->CreateETSUnionType(std::move(types));
} else {
ret = GetTypeOfStringType(argType, checker);
}
return ret;
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::TypeofExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
auto argType = expr->Argument()->Check(checker);
if (argType->IsTypeError()) {
return expr->SetTsType(checker->GlobalTypeError());
}
return expr->SetTsType(ComputeTypeOfType(checker, argType));
}
checker::Type *ETSAnalyzer::Check(ir::UnaryExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
auto argType = expr->argument_->Check(checker);
const auto isCondExpr = expr->OperatorType() == lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK;
checker::Type *operandType = checker->ApplyUnaryOperatorPromotion(expr->argument_, argType, isCondExpr);
if (argType != nullptr && argType->IsETSBigIntType() && argType->HasTypeFlag(checker::TypeFlag::BIGINT_LITERAL)) {
switch (expr->OperatorType()) {
case lexer::TokenType::PUNCTUATOR_MINUS: {
auto *type = checker->GlobalETSBigIntType();
ES2PANDA_ASSERT(type != nullptr);
expr->argument_->SetTsType(type);
expr->SetTsType(type);
return expr->TsType();
}
default:
break;
}
}
if (argType != nullptr && argType->IsETSBigIntType()) {
switch (expr->OperatorType()) {
case lexer::TokenType::PUNCTUATOR_MINUS:
case lexer::TokenType::PUNCTUATOR_PLUS:
case lexer::TokenType::PUNCTUATOR_TILDE: {
expr->SetTsType(argType);
return expr->TsType();
}
default:
break;
}
}
SetTsTypeForUnaryExpression(checker, expr, operandType);
checker->Context().CheckUnarySmartCastCondition(expr);
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::UpdateExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
checker::Type *operandType = expr->argument_->Check(checker);
FORWARD_TYPE_ERROR(checker, operandType, expr);
if (expr->Argument()->IsIdentifier()) {
checker->ValidateUnaryOperatorOperand(expr->Argument()->AsIdentifier()->Variable(), expr);
} else if (expr->Argument()->IsTSAsExpression()) {
if (auto *const asExprVar = expr->Argument()->AsTSAsExpression()->Variable(); asExprVar != nullptr) {
checker->ValidateUnaryOperatorOperand(asExprVar, expr);
}
} else if (expr->Argument()->IsTSNonNullExpression()) {
if (auto *const nonNullExprVar = expr->Argument()->AsTSNonNullExpression()->Variable();
nonNullExprVar != nullptr) {
checker->ValidateUnaryOperatorOperand(nonNullExprVar, expr);
}
} else if (expr->Argument()->IsMemberExpression()) {
varbinder::LocalVariable *propVar = expr->argument_->AsMemberExpression()->PropVar();
if (propVar != nullptr) {
checker->ValidateUnaryOperatorOperand(propVar, expr);
}
} else {
ES2PANDA_ASSERT(checker->IsAnyError());
expr->Argument()->SetTsType(checker->GlobalTypeError());
return expr->SetTsType(checker->GlobalTypeError());
}
if (operandType->IsETSBigIntType()) {
return expr->SetTsType(operandType);
}
auto unboxedType = checker->MaybeUnboxInRelation(operandType);
if (unboxedType == nullptr || !unboxedType->HasTypeFlag(checker::TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC)) {
checker->LogError(diagnostic::OPERAND_NOT_NUMERIC, {}, expr->Argument()->Start());
return expr->SetTsType(checker->GlobalTypeError());
}
return expr->SetTsType(operandType);
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::BigIntLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
expr->SetTsType(checker->CreateETSBigIntLiteralType(expr->Str()));
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::BooleanLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() == nullptr) {
expr->SetTsType(checker->GetConstantBuiltinType(checker->GlobalETSBooleanBuiltinType()));
}
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::CharLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() == nullptr) {
expr->SetTsType(checker->GetConstantBuiltinType(checker->GlobalCharBuiltinType()));
}
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::NullLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() == nullptr) {
expr->SetTsType(checker->GlobalETSNullType());
}
return expr->TsType();
}
static bool CheckIfLiteralValueIsAppropriate(ETSChecker *checker, Type *type, ir::NumberLiteral *expr)
{
auto number = expr->Number();
auto relation = checker->Relation();
if (relation->IsSupertypeOf(checker->GetGlobalTypesHolder()->GlobalIntegralBuiltinType(), type)) {
if (number.IsReal()) {
return false;
}
auto val = number.GetValueAndCastTo<int64_t>();
if (relation->IsIdenticalTo(type, checker->GlobalByteBuiltinType())) {
return val >= std::numeric_limits<int8_t>::min() && val <= std::numeric_limits<int8_t>::max();
}
if (relation->IsIdenticalTo(type, checker->GlobalShortBuiltinType())) {
return val >= std::numeric_limits<int16_t>::min() && val <= std::numeric_limits<int16_t>::max();
}
if (relation->IsIdenticalTo(type, checker->GlobalIntBuiltinType())) {
return val >= std::numeric_limits<int32_t>::min() && val <= std::numeric_limits<int32_t>::max();
}
} else if (relation->IsIdenticalTo(type, checker->GlobalCharBuiltinType())) {
auto val = number.GetValueAndCastTo<int64_t>();
return !number.IsReal() && val >= std::numeric_limits<uint16_t>::min() &&
val <= std::numeric_limits<uint16_t>::max();
} else if (number.IsDouble()) {
if (relation->IsIdenticalTo(type, checker->GlobalFloatBuiltinType())) {
auto doubleVal = number.GetDouble();
if (doubleVal < std::numeric_limits<float>::min() || doubleVal > std::numeric_limits<float>::max()) {
return false;
}
auto floatVal = static_cast<float>(doubleVal);
return static_cast<double>(floatVal) == doubleVal;
}
return relation->IsIdenticalTo(checker->GlobalDoubleBuiltinType(), type);
}
return true;
}
checker::Type *ETSAnalyzer::Check(ir::NumberLiteral *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
Type *type;
if (auto *preferredType =
GetAppropriatePreferredType(expr->PreferredType(), [&](Type *tp) { return checker->CheckIfNumeric(tp); });
preferredType != nullptr && CheckIfLiteralValueIsAppropriate(checker, preferredType, expr)) {
type = preferredType;
} else if (expr->Number().IsDouble()) {
type = checker->GlobalDoubleBuiltinType();
} else if (expr->Number().IsFloat()) {
type = checker->GlobalFloatBuiltinType();
} else if (expr->Number().IsLong()) {
type = checker->GlobalLongBuiltinType();
} else if (expr->Number().IsInt()) {
type = checker->GlobalIntBuiltinType();
} else if (expr->Number().IsShort()) {
type = checker->GlobalShortBuiltinType();
} else if (expr->Number().IsByte()) {
type = checker->GlobalByteBuiltinType();
} else {
return checker->GlobalTypeError();
}
return expr->SetTsType(checker->GetConstantBuiltinType(type));
}
checker::Type *ETSAnalyzer::Check(ir::StringLiteral *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() == nullptr) {
expr->SetTsType(checker->CreateETSStringLiteralType(expr->Str()));
}
return expr->TsType();
}
static bool HasRealSourceLocation(const lexer::SourceRange &range)
{
return range.start.line != 0 || range.start.index != 0 || range.end.line != 0 || range.end.index != 0;
}
static void ValidateImportTypeUsage(ETSChecker *checker, ir::ImportDeclaration *st, ir::AstNode *spec)
{
if (!HasRealSourceLocation(spec->Range())) {
return;
}
if (st->IsTypeKind() && spec->IsImportSpecifier()) {
auto importSpec = spec->AsImportSpecifier();
if (importSpec->Local()->IsIdentifier() && importSpec->Local()->AsIdentifier()->Variable() != nullptr) {
auto var = importSpec->Local()->AsIdentifier()->Variable();
if (var->Declaration() != nullptr && var->Declaration()->Node()->IsAnnotationDeclaration()) {
checker->LogError(diagnostic::IMPORT_TYPE_NOT_ALLOWED, {}, spec->Start());
}
}
}
}
checker::Type *ETSAnalyzer::Check(ir::ImportDeclaration *st) const
{
ETSChecker *checker = GetETSChecker();
checker::Type *type = nullptr;
for (auto *spec : st->Specifiers()) {
ValidateImportTypeUsage(checker, st, spec);
if (spec->IsImportNamespaceSpecifier()) {
type = spec->AsImportNamespaceSpecifier()->Check(checker);
}
}
return type;
}
checker::Type *ETSAnalyzer::Check(ir::ImportNamespaceSpecifier *st) const
{
ETSChecker *checker = GetETSChecker();
if (st->Local()->Name().Empty()) {
return ReturnTypeForStatement(st);
}
if (st->Local()->AsIdentifier()->TsType() != nullptr) {
return st->Local()->TsType();
}
ir::ETSImportDeclaration *importDecl = nullptr;
if (st->Parent()->IsETSImportDeclaration()) {
importDecl = st->Parent()->AsETSImportDeclaration();
} else if (st->Parent()->IsETSReExportDeclaration()) {
importDecl = st->Parent()->AsETSReExportDeclaration()->GetETSImportDeclarations();
} else {
ES2PANDA_UNREACHABLE();
}
auto topScopeCtx = varbinder::TopScopeContext(checker->VarBinder(),
importDecl->Parent() != nullptr
? importDecl->Parent()->AsETSModule()->Scope()->AsGlobalScope()
: checker->VarBinder()->GetScope()->AsGlobalScope());
if (importDecl->IsPureDynamic()) {
auto *type = checker->GetImportSpecifierObjectType(importDecl, st->Local()->AsIdentifier())->AsETSObjectType();
checker->SetrModuleObjectTsType(st->Local(), type);
return type;
}
return checker->GetImportSpecifierObjectType(importDecl, st->Local()->AsIdentifier());
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::AssertStatement *st) const
{
ES2PANDA_UNREACHABLE();
}
checker::Type *ETSAnalyzer::Check(ir::BlockStatement *st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
for (std::size_t idx = 0; idx < st->Statements().size(); ++idx) {
auto *stmt = st->Statements()[idx];
stmt->Check(checker);
if (auto *const trailingBlock = st->SearchStatementInTrailingBlock(stmt); trailingBlock != nullptr) {
bool isReturnAllowed = !checker->HasStatus(CheckerStatus::RESTRICTED_RETURN_IN_BLOCK);
if (checker->Context().ContainingSignature() == nullptr && isReturnAllowed) {
checker->AddStatus(CheckerStatus::RESTRICTED_RETURN_IN_BLOCK);
}
trailingBlock->Check(checker);
if (isReturnAllowed) {
checker->RemoveStatus(CheckerStatus::RESTRICTED_RETURN_IN_BLOCK);
}
st->AddStatement(idx, trailingBlock);
++idx;
}
}
if (UNLIKELY(checker->GetDebugInfoPlugin() != nullptr)) {
checker->GetDebugInfoPlugin()->AddPrologueEpilogue(st);
}
auto const *const scope = st->Scope();
if (scope == nullptr) {
return ReturnTypeForStatement(st);
}
if (scope->IsFunctionScope() && st->Parent()->Parent()->Parent()->IsMethodDefinition()) {
checker->Context().ClearSmartCasts();
} else if (!scope->IsGlobalScope()) {
for (auto const *const decl : scope->Decls()) {
if (decl->IsLetOrConstDecl() && decl->Node() != nullptr && decl->Node()->IsIdentifier()) {
checker->Context().RemoveSmartCast(decl->Node()->AsIdentifier()->Variable());
}
}
}
if (st->IsETSModule() && st->AsETSModule()->Program()->Is<util::ModuleKind::PACKAGE>() &&
(checker->Context().Status() & checker::CheckerStatus::IN_EXTERNAL) == 0) {
CheckAllConstPropertyInitialized(checker, st->AsETSModule());
}
return ReturnTypeForStatement(st);
}
static void CheckJumpStatement(ir::AstNode *st, ETSChecker *checker)
{
const ir::AstNode *target = nullptr;
ir::Identifier *ident = nullptr;
if (st->IsContinueStatement()) {
target = st->AsContinueStatement()->Target();
ident = st->AsContinueStatement()->Ident();
} else {
target = st->AsBreakStatement()->Target();
ident = st->AsBreakStatement()->Ident();
}
auto getEnclosingMethod = [](const ir::AstNode *node) {
const ir::AstNode *enclosingMethod = node->Parent();
while (enclosingMethod != nullptr && !enclosingMethod->IsMethodDefinition() &&
!enclosingMethod->IsArrowFunctionExpression()) {
enclosingMethod = enclosingMethod->Parent();
}
return enclosingMethod;
};
if (ident != nullptr && getEnclosingMethod(st) != getEnclosingMethod(target)) {
checker->LogError(diagnostic::CONTINUE_OR_BREAK_TARGET_OUTSIDE_FUNCTION, {}, st->Start());
}
}
checker::Type *ETSAnalyzer::Check(ir::BreakStatement *st) const
{
ETSChecker *checker = GetETSChecker();
if (!st->HasTarget()) {
compiler::SetJumpTargetPhase setJumpTarget;
setJumpTarget.FindJumpTarget(st);
}
if (st->Target() == nullptr) {
return checker->GlobalTypeError();
}
CheckJumpStatement(st, checker);
checker->Context().OnBreakStatement(st);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ClassDeclaration *st) const
{
ETSChecker *checker = GetETSChecker();
st->Definition()->Check(checker);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::AnnotationDeclaration *st) const
{
if (st->Expr()->TsType() != nullptr) {
return ReturnTypeForStatement(st);
}
ETSChecker *checker = GetETSChecker();
st->Expr()->Check(checker);
checker->CheckAnnotations(st, true);
ScopeContext scopeCtx(checker, st->Scope());
for (auto *it : st->Properties()) {
auto *property = it->AsClassProperty();
if (checker::Type *propertyType = property->Check(checker); !propertyType->IsTypeError()) {
checker->CheckAnnotationPropertyType(property);
}
}
if (st->IsExported() || st->IsDefaultExported()) {
const auto checkTypeNode = [checker](const ir::AstNode *node) { CheckExportForTypeAlias(checker, node); };
for (auto *it : st->Properties()) {
auto *property = it->AsClassProperty();
if (property->TypeAnnotation() == nullptr) {
continue;
}
property->TypeAnnotation()->IterateRecursively(checkTypeNode);
}
}
auto baseName = st->GetBaseName();
if (!baseName->IsErrorPlaceHolder() && baseName->Variable()->Declaration()->Node()->IsAnnotationDeclaration()) {
auto *annoDecl = baseName->Variable()->Declaration()->Node()->AsAnnotationDeclaration();
if (annoDecl != st && annoDecl->IsDeclare()) {
checker->CheckAmbientAnnotation(st, annoDecl);
}
}
return ReturnTypeForStatement(st);
}
static void ProcessRequiredFields(ArenaUnorderedMap<util::StringView, ir::ClassProperty *> &fieldMap,
ir::AnnotationUsage *st, ETSChecker *checker)
{
for (const auto &entry : fieldMap) {
if (entry.second->Value() == nullptr) {
checker->LogError(diagnostic::ANNOT_FIELD_NO_VAL, {entry.first}, st->Start());
continue;
}
}
}
checker::Type *ETSAnalyzer::Check(ir::AnnotationUsage *st) const
{
if (st->Expr()->TsType() != nullptr) {
return ReturnTypeForStatement(st);
}
ETSChecker *checker = GetETSChecker();
st->Expr()->Check(checker);
auto *baseName = st->GetBaseName();
if (baseName->Variable() == nullptr || !baseName->Variable()->Declaration()->Node()->IsAnnotationDeclaration()) {
if (!baseName->IsErrorPlaceHolder()) {
checker->LogError(diagnostic::NOT_AN_ANNOTATION, {baseName->Name()}, baseName->Start());
}
ES2PANDA_ASSERT(checker->IsAnyError());
return ReturnTypeForStatement(st);
}
if (baseName->Name().Is(compiler::Signatures::ANNO_UNSAFE_VARIANCE) && !util::Helpers::IsStdLib(st->Program())) {
checker->LogError(diagnostic::UNSAFE_VARIANCE_ONLY_IN_STDLIB, {}, st->Start());
}
auto *annoDecl = baseName->Variable()->Declaration()->Node()->AsAnnotationDeclaration();
annoDecl->Check(checker);
auto *parentNode = st->Parent();
if (parentNode != nullptr && util::Helpers::IsExported(parentNode) &&
!(parentNode->IsClassProperty() && parentNode->AsClassProperty()->IsPrivate())) {
if (!util::Helpers::IsExported(annoDecl)) {
checker->LogError(diagnostic::USED_TYPE_IS_NOT_EXPORTED, {baseName->Name()}, st->Start());
}
}
ArenaUnorderedMap<util::StringView, ir::ClassProperty *> fieldMap {checker->ProgramAllocator()->Adapter()};
for (auto *it : annoDecl->Properties()) {
auto *field = it->AsClassProperty();
ES2PANDA_ASSERT(field->Id() != nullptr);
fieldMap.insert(std::make_pair(field->Id()->Name(), field));
}
if (annoDecl->Properties().size() < st->Properties().size()) {
checker->LogError(diagnostic::ANNOTATION_ARG_COUNT_MISMATCH, {}, st->Start());
return ReturnTypeForStatement(st);
}
if (st->Properties().size() == 1 && st->Properties().at(0)->AsClassProperty()->Id() != nullptr &&
st->Properties().at(0)->AsClassProperty()->Id()->Name() == compiler::Signatures::ANNOTATION_KEY_VALUE) {
checker->CheckSinglePropertyAnnotation(st, annoDecl);
fieldMap.clear();
} else {
checker->CheckMultiplePropertiesAnnotation(st, baseName->Name(), fieldMap);
}
ProcessRequiredFields(fieldMap, st, checker);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ContinueStatement *st) const
{
ETSChecker *checker = GetETSChecker();
if (!st->HasTarget()) {
compiler::SetJumpTargetPhase setJumpTarget;
setJumpTarget.FindJumpTarget(st);
}
if (st->Target() == nullptr) {
return checker->GlobalTypeError();
}
CheckJumpStatement(st, checker);
checker->AddStatus(CheckerStatus::MEET_CONTINUE);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::DoWhileStatement *st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
auto [smartCasts, clearFlag] = checker->Context().EnterLoop(*st, std::nullopt);
checker->CheckTruthinessOfType(st->Test());
st->Body()->Check(checker);
checker->Context().ExitLoop(smartCasts, clearFlag, st);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::EmptyStatement *st) const
{
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ExpressionStatement *st) const
{
ETSChecker *checker = GetETSChecker();
return st->GetExpression()->Check(checker);
}
static bool IsArrayOrStringIterableType(Type *type)
{
return type != nullptr && type->IsETSArrayType();
}
static bool IsNeutralIterableType(Type *type)
{
return type != nullptr && (type->IsETSResizableArrayType() || type->IsETSStringType());
}
static bool IsUnsupportedMixedIterableUnion(Type *type)
{
if (type == nullptr || !type->IsETSUnionType()) {
return false;
}
bool hasArrayOrString = false;
bool hasOtherIterable = false;
for (auto *const ct : type->AsETSUnionType()->ConstituentTypes()) {
if (IsNeutralIterableType(ct)) {
continue;
}
if (IsArrayOrStringIterableType(ct)) {
hasArrayOrString = true;
} else {
hasOtherIterable = true;
}
}
return hasArrayOrString && hasOtherIterable;
}
static bool ValidateAndProcessIteratorType(ETSChecker *checker, Type *elemType, ir::ForOfStatement *const st)
{
checker::Type *iterType = GetIteratorType(checker, elemType, st->Left());
if (iterType->IsTypeError()) {
return false;
}
const auto ident = st->Left()->IsVariableDeclaration()
? st->Left()->AsVariableDeclaration()->Declarators().front()->Id()->AsIdentifier()
: st->Left()->AsIdentifier();
auto *const relation = checker->Relation();
relation->SetFlags(checker::TypeRelationFlag::ASSIGNMENT_CONTEXT);
relation->SetNode(ident);
if (auto ctx = checker::AssignmentContext(checker->Relation(), ident, elemType, iterType, ident->Start(),
{{diagnostic::ITERATOR_ELEMENT_TYPE_MISMATCH, {elemType, iterType}}});
!ctx.IsAssignable()) {
return false;
}
relation->SetNode(nullptr);
relation->SetFlags(checker::TypeRelationFlag::NONE);
const auto variable = ident->Variable();
if (variable != nullptr) {
const auto smartType = checker->ResolveSmartType(elemType, variable->TsType());
checker->Context().SetSmartCast(variable, smartType);
}
return true;
}
checker::Type *ETSAnalyzer::Check(ir::ForOfStatement *const st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
auto [smartCasts, clearFlag] = checker->Context().EnterLoop(*st, std::nullopt);
checker::Type *const exprType = st->Right()->Check(checker);
if (IsUnsupportedMixedIterableUnion(exprType)) {
checker->LogError(diagnostic::FOROF_UNSUPPORTED_MIXED_ITERABLE_UNION, {exprType}, st->Right()->Start());
return checker->GlobalTypeError();
}
checker::Type *elemType = checker->GlobalTypeError();
if (exprType->IsETSStringType()) {
elemType = checker->GlobalBuiltinETSStringType();
} else if (exprType->IsETSArrayType() || exprType->IsETSResizableArrayType()) {
elemType = checker->GetElementTypeOfArray(exprType);
} else if (exprType->IsETSObjectType() || exprType->IsETSUnionType() || exprType->IsETSTypeParameter()) {
elemType = st->CheckIteratorMethod(checker);
} else if (exprType->IsETSTupleType()) {
elemType = checker->GlobalETSAnyType();
st->Right()->SetTsType(exprType->AsETSTupleType()->GetWrapperType());
}
if (elemType == checker->GlobalTypeError()) {
checker->LogError(diagnostic::FOROF_SOURCE_NONITERABLE, {}, st->Right()->Start());
return checker->GlobalTypeError();
}
st->Left()->Check(checker);
if (!ValidateAndProcessIteratorType(checker, elemType, st)) {
return checker->GlobalTypeError();
};
st->Body()->Check(checker);
checker->Context().ExitLoop(smartCasts, clearFlag, st);
return ReturnTypeForStatement(st);
}
static bool HasMissingInitOrType(ir::VariableDeclaration *varDecl, ETSChecker *checker)
{
for (auto *decl : varDecl->Declarators()) {
if (decl->Id()->IsIdentifier() && (decl->Id()->AsIdentifier()->TypeAnnotation() == nullptr) &&
(decl->Init() == nullptr)) {
auto *ident = decl->Id()->AsIdentifier();
checker->LogError(diagnostic::MISSING_INIT_OR_TYPE, {}, ident->Start());
return true;
}
}
return false;
}
checker::Type *ETSAnalyzer::Check(ir::ForUpdateStatement *st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
auto [smartCasts, clearFlag] = checker->Context().EnterLoop(*st, std::nullopt);
if (st->Init() != nullptr) {
st->Init()->Check(checker);
if (st->Init()->IsVariableDeclaration()) {
auto *varDecl = st->Init()->AsVariableDeclaration();
if (HasMissingInitOrType(varDecl, checker)) {
return checker->GlobalTypeError();
}
}
}
checker->Context().InvalidateSmartCastsForLoopHeader(*st, std::nullopt);
if (st->Test() != nullptr) {
checker->CheckTruthinessOfType(st->Test());
}
if (st->Update() != nullptr) {
st->Update()->Check(checker);
}
st->Body()->Check(checker);
checker->Context().ExitLoop(smartCasts, clearFlag, st);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::IfStatement *st) const
{
ETSChecker *const checker = GetETSChecker();
SmartCastArray smartCasts = checker->Context().EnterTestExpression();
checker->CheckTruthinessOfType(st->Test());
SmartCastTypes testedTypes = checker->Context().ExitTestExpression();
ApplyTestedSmartCasts(checker, testedTypes, true);
checker->Context().EnterPath();
st->Consequent()->Check(checker);
bool const consequentTerminated = checker->Context().ExitPath();
SmartCastArray consequentSmartCasts = checker->Context().CloneSmartCasts();
checker->Context().RestoreSmartCasts(smartCasts);
ApplyTestedSmartCasts(checker, testedTypes, false);
if (st->Alternate() != nullptr) {
checker->Context().EnterPath();
st->Alternate()->Check(checker);
bool const alternateTerminated = checker->Context().ExitPath();
if (alternateTerminated) {
if (!consequentTerminated) {
checker->Context().RestoreSmartCasts(consequentSmartCasts);
} else {
checker->Context().RestoreSmartCasts(smartCasts);
}
} else if (!consequentTerminated) {
checker->Context().CombineSmartCasts(consequentSmartCasts);
}
} else {
if (!consequentTerminated) {
checker->Context().CombineSmartCasts(consequentSmartCasts);
}
}
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::LabelledStatement *st) const
{
ETSChecker *checker = GetETSChecker();
st->body_->Check(checker);
return ReturnTypeForStatement(st);
}
static bool ValidateThisReturnOperand(ir::Expression *arg, ir::ReturnStatement *st, ETSChecker *checker)
{
if (arg->IsThisExpression()) {
return true;
}
if (arg->IsCallExpression()) {
auto *sig = arg->AsCallExpression()->Signature();
if (sig != nullptr && sig->HasSignatureFlag(SignatureFlags::THIS_RETURN_TYPE)) {
return true;
}
}
if (arg->IsConditionalExpression()) {
auto *br = arg->AsConditionalExpression();
return ValidateThisReturnOperand(br->Consequent(), st, checker) &&
ValidateThisReturnOperand(br->Alternate(), st, checker);
}
checker->LogError(diagnostic::RETURN_THIS_OUTSIDE_METHOD, {}, st->Start());
return false;
}
static bool CheckIsValidReturnTypeAnnotation(ir::ReturnStatement *st, ir::ScriptFunction *containingFunc,
ir::TypeNode *returnTypeAnnotation, ETSChecker *checker)
{
if (containingFunc->GetPreferredReturnType() != nullptr ||
(returnTypeAnnotation != nullptr && !returnTypeAnnotation->IsTSThisType())) {
return true;
}
if (containingFunc->HasReceiver() && containingFunc->ReturnTypeAnnotation()->IsTSThisType()) {
checker->LogError(diagnostic::THIS_INCORRECTLY_USED_AS_TYPE_ANNOTAITON, {},
containingFunc->ReturnTypeAnnotation()->Start());
return false;
}
if (!containingFunc->HasReceiver()) {
return true;
}
bool inValidNormalFuncReturnThisType = st->Argument() == nullptr || !st->Argument()->IsThisExpression();
bool inValidExtensionFuncReturnThisType =
!containingFunc->HasReceiver() ||
(containingFunc->HasReceiver() && (st->Argument() == nullptr || !st->Argument()->IsIdentifier() ||
!st->Argument()->AsIdentifier()->IsReceiver()));
if (inValidNormalFuncReturnThisType && inValidExtensionFuncReturnThisType) {
checker->LogError(diagnostic::RETURN_THIS_OUTSIDE_METHOD, {}, st->Start());
return false;
}
return true;
}
bool ETSAnalyzer::CheckInferredFunctionReturnType(ir::ReturnStatement *st, ir::ScriptFunction *containingFunc,
checker::Type *&funcReturnType, ir::TypeNode *returnTypeAnnotation,
ETSChecker *checker) const
{
if (!CheckIsValidReturnTypeAnnotation(st, containingFunc, returnTypeAnnotation, checker)) {
return false;
}
if (containingFunc->ReturnTypeAnnotation() != nullptr) {
* NOTE(knazarov): To not break compatibility with existing behaviour,
* we keep return type of the AsyncImpl methods as Object, so here we only check
* AsyncFunc itself.
*/
const auto retType = containingFunc->ReturnTypeAnnotation()->GetType(checker);
if (containingFunc->IsAsyncFunc()) {
if (!retType->IsETSObjectType() || !checker->IsPromiseType(retType->AsETSObjectType())) {
checker->LogError(diagnostic::ASYNC_FUNCTION_RETURN_TYPE, {},
containingFunc->ReturnTypeAnnotation()->Start());
return false;
}
}
funcReturnType = retType;
} else {
funcReturnType = containingFunc->GetPreferredReturnType();
}
if (st->Argument() == nullptr) {
ES2PANDA_ASSERT(funcReturnType != nullptr);
const auto undef = containingFunc->IsDeclaredAsync()
? checker->CreatePromiseOf(checker->GlobalETSUndefinedType())
: checker->GlobalETSUndefinedType();
const auto relation = checker->Relation();
const auto isReturnUndef = relation->IsSupertypeOf(funcReturnType, undef);
if (!isReturnUndef) {
checker->LogError(diagnostic::RETURN_WITHOUT_VALUE, {}, st->Start());
return false;
}
} else {
return CheckReturnStatementArgumentType(st, containingFunc, funcReturnType, checker);
}
return true;
}
bool ETSAnalyzer::CheckReturnStatementArgumentType(ir::ReturnStatement *st, ir::ScriptFunction *containingFunc,
checker::Type *funcReturnType, ETSChecker *checker) const
{
CheckMainFunctionReturnType(funcReturnType, checker, containingFunc, st);
checker::Type *preferredType = funcReturnType;
if (containingFunc->IsAsyncFunc() && checker->IsPromiseType(funcReturnType)) {
ES2PANDA_ASSERT(funcReturnType->IsETSObjectType() || funcReturnType->IsETSUnionType());
* Set PREFERRED type for the return statement to Promise<T> | T, as stated in spec;
*
* 16.3.1: An asynchronous function with the return type Promise<T> can explicitly return a Promise<T> instance
* (in this case, the returned value is returned "as is") or a value of type T, which is then automatically
* boxed in an instance of Promise<T>. Both options are allowed to be the expression of the return statement
* inside the async function body. T here is a subtype of Type Any.
*/
if (funcReturnType->IsETSObjectType()) {
preferredType = checker->CreateETSUnionType(
{funcReturnType, checker->PromiseTypeArg(funcReturnType->AsETSObjectType())});
} else {
* NOTE(knazarov): Since PREFERRED return type can be union (Promise<T1> | Promise<T2> <: Promise<Any>), we
* should unwrap it to the union of possible arguments (here, and ONLY here!); DO NOT implement such
* unwrapping anywhere else;
*/
ES2PANDA_ASSERT(containingFunc->ReturnTypeAnnotation() == nullptr);
ES2PANDA_ASSERT(funcReturnType == containingFunc->GetPreferredReturnType());
std::vector<Type *> constituentTypes = {};
for (const auto &ct : funcReturnType->AsETSUnionType()->ConstituentTypes()) {
ES2PANDA_ASSERT(ct->IsETSObjectType() && checker->IsPromiseType(ct));
constituentTypes.push_back(ct);
constituentTypes.push_back(checker->PromiseTypeArg(ct->AsETSObjectType()));
}
preferredType = checker->CreateETSUnionType(std::move(constituentTypes));
}
}
if (st->Argument()->IsMemberExpression()) {
checker->SetArrayPreferredTypeForNestedMemberExpressions(st->Argument()->AsMemberExpression(), preferredType);
} else {
st->Argument()->SetPreferredType(preferredType);
}
checker::Type *argumentType = st->Argument()->Check(checker);
auto *retAnn = containingFunc->ReturnTypeAnnotation();
if (retAnn != nullptr && retAnn->IsTSThisType() && !containingFunc->HasReceiver()) {
if (!ValidateThisReturnOperand(st->Argument(), st, checker)) {
return false;
}
}
if (funcReturnType->IsETSUnionType()) {
IsAmbiguousUnionInit(funcReturnType->AsETSUnionType(), st->Argument(), checker);
}
return CheckReturnType(checker, funcReturnType, argumentType, st->Argument(), containingFunc);
}
checker::Type *ETSAnalyzer::GetFunctionReturnType(ir::ReturnStatement *st, ir::ScriptFunction *containingFunc) const
{
ES2PANDA_ASSERT(containingFunc->ReturnTypeAnnotation() != nullptr ||
containingFunc->Signature()->ReturnType() != nullptr ||
containingFunc->GetPreferredReturnType() != nullptr);
ETSChecker *checker = GetETSChecker();
checker::Type *funcReturnType = nullptr;
if (auto *const returnTypeAnnotation = containingFunc->ReturnTypeAnnotation();
returnTypeAnnotation != nullptr || containingFunc->GetPreferredReturnType() != nullptr) {
if (!CheckInferredFunctionReturnType(st, containingFunc, funcReturnType, returnTypeAnnotation, checker)) {
return checker->GlobalTypeError();
}
} else {
if (containingFunc->Signature()->HasSignatureFlag(checker::SignatureFlags::NEED_RETURN_TYPE)) {
funcReturnType = InferReturnType(checker, containingFunc,
st->Argument());
} else {
funcReturnType = ProcessReturnStatements(checker, containingFunc, st, st->Argument());
}
}
if ((st->Argument() != nullptr) && st->Argument()->IsArrayExpression() && funcReturnType->IsArrayType()) {
checker->ModifyPreferredType(st->Argument()->AsArrayExpression(), funcReturnType);
st->Argument()->Check(checker);
}
return funcReturnType;
}
checker::Type *ETSAnalyzer::Check(ir::ReturnStatement *st) const
{
ETSChecker *checker = GetETSChecker();
if (checker->HasStatus(checker::CheckerStatus::RESTRICTED_RETURN_IN_BLOCK) &&
!checker->HasStatus(ark::es2panda::checker::CheckerStatus::IN_LAMBDA)) {
checker->LogError(diagnostic::RETURN_IN_FUN_BODY, {}, st->Start());
}
ir::AstNode *ancestor = util::Helpers::FindAncestorGivenByType(st, ir::AstNodeType::SCRIPT_FUNCTION);
ES2PANDA_ASSERT(ancestor != nullptr);
ES2PANDA_ASSERT(ancestor->IsScriptFunction());
auto *containingFunc = ancestor->AsScriptFunction();
containingFunc->AddFlag(ir::ScriptFunctionFlags::HAS_RETURN);
if (containingFunc->Signature() == nullptr) {
ES2PANDA_ASSERT(checker->IsAnyError());
return ReturnTypeForStatement(st);
}
checker->AddStatus(CheckerStatus::MEET_RETURN);
if (containingFunc->IsConstructor()) {
if (st->Argument() != nullptr) {
checker->LogError(diagnostic::NON_VOID_RETURN_IN_CONSTRUCTOR, {}, st->Start());
return checker->GlobalTypeError();
}
return ReturnTypeForStatement(st);
}
const auto functionRetType = GetFunctionReturnType(st, containingFunc);
if (!st->ReturnType() || !checker->Relation()->IsSupertypeOf(functionRetType, st->ReturnType())) {
st->SetReturnType(checker, functionRetType);
}
if (containingFunc->ReturnTypeAnnotation() == nullptr) {
containingFunc->AddReturnStatement(st);
}
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::SwitchStatement *st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
auto *comparedExprType = checker->CheckSwitchDiscriminant(st->Discriminant());
SmartCastArray smartCasts = checker->Context().CloneSmartCasts();
bool hasDefaultCase = false;
for (auto &it : st->Cases()) {
checker->Context().EnterPath();
it->CheckAndTestCase(checker, comparedExprType, st->Discriminant(), hasDefaultCase);
bool const caseTerminated = checker->Context().ExitPath();
if (it != st->Cases().back()) {
if (!caseTerminated) {
checker->Context().CombineSmartCasts(smartCasts);
} else {
checker->Context().RestoreSmartCasts(smartCasts);
}
} else {
if (!caseTerminated) {
checker->Context().AddBreakSmartCasts(st, checker->Context().CloneSmartCasts());
}
checker->Context().ClearSmartCasts();
}
}
if (!hasDefaultCase) {
checker->Context().AddBreakSmartCasts(st, std::move(smartCasts));
}
checker->Context().CombineBreakSmartCasts(st);
checker->CheckForSameSwitchCases(st->Cases());
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ThrowStatement *st) const
{
ETSChecker *checker = GetETSChecker();
const auto *arg = st->argument_;
checker::Type *argType = st->argument_->Check(checker);
bool isRethrow = false;
if (arg->IsIdentifier() && !catchParamStack_.empty()) {
const varbinder::Variable *sym = arg->AsIdentifier()->Variable();
ES2PANDA_ASSERT(sym != nullptr);
if (!catchParamStack_.empty() && sym == catchParamStack_.back()) {
isRethrow = true;
}
}
if (!isRethrow && !argType->IsTypeError()) {
checker->CheckExceptionOrErrorType(argType, st->Start());
}
checker->AddStatus(CheckerStatus::MEET_THROW);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::TryStatement *st) const
{
ETSChecker *checker = GetETSChecker();
std::vector<checker::ETSObjectType *> exceptions {};
std::vector<SmartCastArray> casts {};
auto smartCasts = checker->Context().CheckTryBlock(*st->Block());
checker->Context().EnterPath();
st->Block()->Check(checker);
bool const tryWillThrow = checker->HasStatus(CheckerStatus::MEET_THROW);
[[maybe_unused]] bool const tryTerminated = checker->Context().ExitPath();
bool defaultCatchFound = false;
bool allCatchClausesThrow = true;
for (auto *catchClause : st->CatchClauses()) {
if (defaultCatchFound) {
checker->LogError(diagnostic::CATCH_DEFAULT_NOT_LAST, {}, catchClause->Start());
return checker->GlobalTypeError();
}
checker->Context().RestoreSmartCasts(smartCasts);
checker->Context().EnterPath();
if (auto const exceptionType = catchClause->Check(checker); !exceptionType->IsTypeError()) {
auto *clauseType = exceptionType->AsETSObjectType();
checker->CheckExceptionClauseType(exceptions, catchClause, clauseType);
exceptions.emplace_back(clauseType);
}
bool const catchMeetThrow = checker->HasStatus(CheckerStatus::MEET_THROW);
[[maybe_unused]] bool const catchTerminated = checker->Context().ExitPath();
allCatchClausesThrow = allCatchClausesThrow && catchMeetThrow;
defaultCatchFound = catchClause->IsDefaultCatchClause();
casts.emplace_back(checker->Context().CloneSmartCasts());
}
checker->Context().RestoreSmartCasts(smartCasts);
if (!casts.empty()) {
for (auto const &cast : casts) {
checker->Context().CombineSmartCasts(cast);
}
}
if (tryWillThrow && allCatchClausesThrow) {
checker->AddStatus(CheckerStatus::MEET_THROW);
}
if (st->HasFinalizer()) {
st->FinallyBlock()->Check(checker);
}
return ReturnTypeForStatement(st);
}
checker::Type *InferSmartType(ETSChecker *checker, Type *variableType, ir::Identifier *ident, ir::Expression *init)
{
TypeRelation *relation = checker->Relation();
Type *smartType = variableType;
Type *initType = nullptr;
Type *undefinedType = checker->GlobalETSUndefinedType();
std::optional<double> value = {};
if (init != nullptr) {
initType = init->TsType();
if (init->IsNumberLiteral()) {
value = std::make_optional(init->AsNumberLiteral()->Number().GetDouble());
}
} else if (variableType != nullptr && relation->IsSupertypeOf(variableType, undefinedType)) {
initType = undefinedType;
}
if (initType != nullptr) {
smartType = checker->ResolveSmartType(initType, variableType, value);
if (!relation->IsIdenticalTo(variableType, smartType)) {
ident->SetTsType(smartType);
checker->Context().SetSmartCast(ident->Variable(), smartType);
}
}
return smartType;
}
checker::Type *ETSAnalyzer::Check(ir::VariableDeclarator *st) const
{
bool initChecked = st->Init() != nullptr ? st->Init()->TsType() != nullptr : true;
if (st->TsType() != nullptr && initChecked) {
return st->TsType();
}
ETSChecker *checker = GetETSChecker();
if (st->Id()->IsETSDestructuring()) {
return st->SetTsType(CheckDestructuringExpression(checker, st->Id()->AsETSDestructuring(), st->Init()));
}
ES2PANDA_ASSERT(st->Id()->IsIdentifier());
auto *const ident = st->Id()->AsIdentifier();
ir::ModifierFlags flags = ir::ModifierFlags::NONE;
if (ident->Parent()->Parent()->AsVariableDeclaration()->Kind() ==
ir::VariableDeclaration::VariableDeclarationKind::CONST) {
flags |= ir::ModifierFlags::CONST;
}
if (ident->IsOptionalDeclaration()) {
flags |= ir::ModifierFlags::OPTIONAL;
}
if (ident->Variable() == nullptr) {
ident->Check(checker);
}
auto *init = st->Init();
auto *variableType = checker->CheckVariableDeclaration(ident, ident->TypeAnnotation(), init, flags);
if (variableType != nullptr) {
if (variableType->IsTypeError()) {
return st->SetTsType(variableType);
}
if (variableType->IsETSWildcardType()) {
variableType = variableType->AsETSWildcardType()->GetUnderlying()->GetConstraintType();
ident->Variable()->SetTsType(variableType);
}
}
auto *smartType = InferSmartType(checker, variableType, ident, init);
if (variableType != nullptr && variableType->IsETSUnionType() && init != nullptr) {
IsAmbiguousUnionInit(variableType->AsETSUnionType(), init, checker);
}
return st->SetTsType(smartType);
}
checker::Type *ETSAnalyzer::Check(ir::VariableDeclaration *st) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(st);
for (auto *it : st->Declarators()) {
it->Check(checker);
}
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::WhileStatement *st) const
{
ETSChecker *checker = GetETSChecker();
checker::ScopeContext scopeCtx(checker, st->Scope());
const auto reassignedVars = checker->Context().GetReassignedVariablesInNode(st->Body());
for (const auto &[var, _] : reassignedVars) {
checker->Context().RemoveSmartCast(var);
}
SmartCastArray savedSmartCasts = checker->Context().EnterTestExpression();
checker->CheckTruthinessOfType(st->Test());
SmartCastTypes testedTypes = checker->Context().ExitTestExpression();
ApplyTestedSmartCasts(checker, testedTypes, true);
auto [smartCasts, clearFlag] = checker->Context().EnterLoop(*st, testedTypes);
st->Body()->Check(checker);
checker->Context().ExitLoop(savedSmartCasts, clearFlag, st);
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::TSArrayType *node) const
{
ETSChecker *checker = GetETSChecker();
checker->CheckAnnotations(node);
node->elementType_->Check(checker);
node->SetTsType(node->GetType(checker));
const auto *arrayType = node->TsType()->AsETSArrayType();
checker->CreateBuiltinArraySignature(arrayType, arrayType->Rank());
return node->TsType();
}
static bool ValueFitsTargetType(ir::TSAsExpression *expr)
{
if (!expr->Expr()->IsNumberLiteral() || !expr->TypeAnnotation()->IsETSPrimitiveType()) {
return true;
}
auto primitiveType = expr->TypeAnnotation()->AsETSPrimitiveType()->GetPrimitiveType();
lexer::Number number = expr->Expr()->AsNumberLiteral()->Number();
if (!std::isfinite(number.GetValue<double>())) {
return true;
}
if (number.IsReal() && primitiveType != ir::PrimitiveType::FLOAT && primitiveType != ir::PrimitiveType::DOUBLE) {
auto val = number.GetDouble();
val = val < 0 ? std::floor(val) : std::ceil(val);
number.SetValue(int64_t(val));
}
switch (primitiveType) {
case ir::PrimitiveType::BYTE:
return number.CanGetValue<int8_t>();
break;
case ir::PrimitiveType::SHORT:
return number.CanGetValue<int16_t>();
break;
case ir::PrimitiveType::INT:
return number.CanGetValue<int32_t>();
break;
case ir::PrimitiveType::LONG:
return number.CanGetValue<int64_t>();
break;
case ir::PrimitiveType::FLOAT:
return number.CanGetValue<float>();
break;
case ir::PrimitiveType::DOUBLE:
return number.CanGetValue<double>();
break;
default:
break;
}
return true;
}
static bool CheckTSAsExpressionInvalidCast(ir::TSAsExpression *expr, checker::Type *sourceType,
checker::Type *targetType, ETSChecker *checker)
{
if (sourceType->DefinitelyETSNullish() && !targetType->PossiblyETSNullish()) {
checker->LogError(diagnostic::NULLISH_CAST_TO_NONNULLISH, expr->Expr()->Start());
expr->SetTsType(targetType);
return false;
}
if (expr->Expr()->IsLiteral() && sourceType->IsBuiltinNumeric()) {
if (targetType->IsETSTypeParameter()) {
checker->LogError(diagnostic::INVALID_CAST, {sourceType, targetType}, expr->Expr()->Start());
expr->SetTsType(targetType);
return false;
} else if (targetType->IsETSUnionType() && targetType->AsETSUnionType()->AllOfConstituentTypes(
[](Type *type) { return type->IsETSTypeParameter(); })) {
checker->LogError(diagnostic::INVALID_CAST, {sourceType, targetType}, expr->Expr()->Start());
expr->SetTsType(targetType);
return false;
}
}
if (!ValueFitsTargetType(expr)) {
checker->LogError(diagnostic::TOO_LARGE_TO_CAST,
{expr->Expr()->AsNumberLiteral()->ToString(), expr->TypeAnnotation()->DumpEtsSrc()},
expr->Expr()->Start());
expr->SetTsType(targetType);
return false;
}
return true;
}
static checker::CastingContext const CheckTSAsExpressionCastable(ir::Expression *castExpr, checker::Type *sourceType,
checker::Type *targetType, ETSChecker *checker)
{
diagnostic::DiagnosticKind const *message = &diagnostic::INVALID_CAST;
util::DiagnosticMessageParams parameters = {sourceType, targetType};
if (sourceType->IsBuiltinNumeric() && targetType->IsBuiltinNumeric()) {
message = &diagnostic::IMPROPER_NUMERIC_CAST;
} else if (castExpr->IsArrayExpression() || castExpr->IsObjectExpression() || castExpr->IsNumberLiteral() ||
castExpr->IsStringLiteral()) {
message = &diagnostic::INVALID_LITERAL_CAST;
char const *literalType = "array";
if (castExpr->IsObjectExpression()) {
literalType = "object";
} else if (castExpr->IsNumberLiteral()) {
literalType = "number";
} else if (castExpr->IsStringLiteral()) {
literalType = "string";
}
parameters = util::DiagnosticMessageParams {literalType, targetType};
}
const checker::CastingContext ctx(
checker->Relation(), *message, parameters,
checker::CastingContext::ConstructorData {castExpr, sourceType, targetType, castExpr->Start()});
if (checker->Relation()->IsTrue() && !ctx.TrivialCast() && targetType->IsETSObjectType() &&
targetType->AsETSObjectType()->IsGeneric() && !castExpr->IsArrayExpression() &&
!castExpr->IsObjectExpression() && !compiler::IsSyntheticIdentifier(castExpr) &&
compiler::GetPhaseManager()->CurrentPhase()->Name() == compiler::CheckerPhase::NAME &&
std::any_of(targetType->AsETSObjectType()->TypeArguments().begin(),
targetType->AsETSObjectType()->TypeArguments().end(),
[](Type *item) { return !item->IsETSTypeParameter() && !item->IsETSWildcardType(); })) {
checker->LogDiagnostic(diagnostic::GENERIC_TYPE_CAST, {targetType->ToString()}, castExpr->Start());
}
return ctx;
}
checker::Type *ETSAnalyzer::Check(ir::TSAsExpression *expr) const
{
ETSChecker *checker = GetETSChecker();
if (expr->TsType() != nullptr) {
return expr->TsType();
}
checker->CheckAnnotations(expr->TypeAnnotation());
auto *const targetType = expr->TypeAnnotation()->AsTypeNode()->GetType(checker);
FORWARD_TYPE_ERROR(checker, targetType, expr);
auto *castExpr = expr->Expr();
castExpr->SetPreferredType(targetType);
if (targetType == checker->GetGlobalTypesHolder()->GlobalETSNeverType()) {
return expr->SetTsType(checker->TypeError(expr, diagnostic::CAST_TO_NEVER, expr->Start()));
}
auto const sourceType = castExpr->Check(checker);
if (sourceType->IsTypeError() && checker->HasStatus(checker::CheckerStatus::IN_TYPE_INFER)) {
return expr->SetTsType(checker->GlobalTypeError());
}
FORWARD_TYPE_ERROR(checker, sourceType, expr);
if (!CheckTSAsExpressionInvalidCast(expr, sourceType, targetType, checker)) {
return expr->TsType();
}
if (castExpr->IsArrayExpression() && targetType->IsETSObjectType() &&
targetType->AsETSObjectType()->IsInterface() &&
GetPreferredTypeFromArraySupertypes(checker, targetType) == nullptr) {
checker->LogError(diagnostic::NOT_A_SUPERINTERFACE_OF_ARRAY, {castExpr->DumpEtsSrc(), targetType, targetType},
expr->Start());
return expr->SetTsType(targetType);
}
const checker::CastingContext ctx = CheckTSAsExpressionCastable(castExpr, sourceType, targetType, checker);
expr->isUncheckedCast_ = ctx.UncheckedCast();
if (!expr->isUncheckedCast_ && targetType->IsETSArrayType()) {
const auto *const targetArrayType = targetType->AsETSArrayType();
checker->CreateBuiltinArraySignature(targetArrayType, targetArrayType->Rank());
}
return expr->SetTsType(targetType);
}
checker::Type *ETSAnalyzer::Check(ir::TSEnumDeclaration *st) const
{
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::TSInterfaceDeclaration *st) const
{
if (st->TsType() != nullptr) {
return st->TsType();
}
ETSChecker *checker = GetETSChecker();
auto *stmtType = checker->BuildBasicInterfaceProperties(st);
ES2PANDA_ASSERT(stmtType != nullptr);
FORWARD_TYPE_ERROR(checker, stmtType, st);
auto *interfaceType = stmtType->AsETSObjectType();
checker->CheckInterfaceAnnotations(st);
if (!interfaceType->IsGradual()) {
interfaceType->SetSuperType(checker->GlobalETSObjectType());
}
checker->CheckInvokeMethodsLegitimacy(interfaceType);
st->SetTsType(stmtType);
checker->CheckDynamicInheritanceAndImplement(interfaceType->AsETSObjectType());
checker::ScopeContext scopeCtx(checker, st->Scope());
auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::IN_INTERFACE, interfaceType);
for (auto *it : st->Body()->Body()) {
it->Check(checker);
}
checker->CheckTypeParameterVariance(st);
return st->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::TSNonNullExpression *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
auto exprType = expr->expr_->Check(checker);
checker::Type *currentType = exprType;
while (currentType->IsETSTypeParameter()) {
currentType = currentType->AsETSTypeParameter()->GetConstraintType();
}
if (currentType->DefinitelyETSNullish()) {
checker->LogDiagnostic(diagnostic::NULLISH_OPERAND, {"ensure-not-nullish"}, expr->Expr()->Start());
if (expr->expr_->IsIdentifier()) {
ES2PANDA_ASSERT(expr->expr_->AsIdentifier()->Variable() != nullptr);
auto originalType = expr->expr_->AsIdentifier()->Variable()->TsType();
if (originalType != nullptr) {
expr->SetTsType(checker->GetNonNullishType(originalType));
}
}
} else if (currentType->DefinitelyNotETSNullish()) {
checker->LogDiagnostic(diagnostic::NON_NULLISH_OPERAND, {"ensure-not-nullish"}, expr->Expr()->Start());
}
if (expr->TsType() == nullptr) {
expr->SetTsType(checker->GetNonNullishType(exprType));
}
expr->SetOriginalType(expr->TsType());
return expr->TsType();
}
checker::Type *ETSAnalyzer::Check(ir::TSThisType *node) const
{
ETSChecker *checker = GetETSChecker();
return node->GetType(checker);
}
static varbinder::Variable *FindInReExports(ETSObjectType *baseType, util::StringView &searchName)
{
for (auto *reExport : baseType->ReExports()) {
PropertySearchFlags flags = PropertySearchFlags::SEARCH_STATIC_FIELD | PropertySearchFlags::SEARCH_STATIC_DECL;
if (auto *var = reExport->GetProperty(searchName, flags); var != nullptr) {
return var;
}
auto *result = FindInReExports(reExport, searchName);
if (result != nullptr) {
return result;
}
}
return nullptr;
}
static varbinder::Variable *FindNameForImportNamespace(ETSChecker *checker, util::StringView &searchName,
ETSObjectType *baseType)
{
* ./file1.ets:
* import * as name1 from "./file2"
*
* ./file2.ets:
* import * as name2 from "./file3"
* import {A} from "./file3"
* export {name2}
* export {A}
*
* ./file3.ets
* export class A{}
*
* 1. Find in file2->program->ast->scope first
* 2. Find in varbinder->selectiveExportAliasMultimap second
* if both found, return variable
*/
auto declNode = baseType->GetDeclNode();
if (!declNode->IsIdentifier()) {
return nullptr;
}
if (declNode->Parent() == nullptr || declNode->Parent()->Parent() == nullptr) {
return nullptr;
}
auto importDeclNode = declNode->Parent()->Parent();
if (!importDeclNode->IsETSImportDeclaration()) {
return nullptr;
}
auto importDecl = importDeclNode->AsETSImportDeclaration();
parser::Program *program = checker->VarBinder()->AsETSBinder()->GetExternalProgram(importDecl);
auto &bindings = program->Ast()->Scope()->Bindings();
if (auto result = bindings.find(searchName); result != bindings.end()) {
auto &sMap = checker->VarBinder()
->AsETSBinder()
->GetSelectiveExportAliasMultimap()
.find(importDecl->ImportInfo().ResolvedSource())
->second;
if (auto it = sMap.find(searchName); it != sMap.end()) {
return result->second;
}
}
return FindInReExports(baseType, searchName);
}
checker::Type *ETSAnalyzer::Check(ir::TSQualifiedName *expr) const
{
if (expr->TsType() != nullptr) {
return expr->TsType();
}
ETSChecker *checker = GetETSChecker();
checker::Type *baseType = expr->Left()->Check(checker);
if (baseType->IsETSObjectType()) {
auto searchName = expr->Right()->Name();
if (searchName.Empty()) {
searchName = expr->Right()->Name();
}
varbinder::Variable *prop =
baseType->AsETSObjectType()->GetProperty(searchName, PropertySearchFlags::SEARCH_DECL);
if (prop == nullptr) {
prop = FindNameForImportNamespace(GetETSChecker(), searchName, baseType->AsETSObjectType());
}
if (prop == nullptr) {
checker->LogError(diagnostic::NONEXISTENT_TYPE, {expr->Right()->Name()}, expr->Right()->Start());
return checker->GlobalTypeError();
}
checker->ValidateNamespaceProperty(prop, baseType->AsETSObjectType(), expr->Right());
expr->Right()->SetVariable(prop);
return checker->GetTypeOfVariable(prop);
}
checker->LogError(diagnostic::NONEXISTENT_TYPE, {expr->Right()->Name()}, expr->Right()->Start());
return checker->GlobalTypeError();
}
checker::Type *ETSAnalyzer::Check(ir::TSTypeAliasDeclaration *st) const
{
ETSChecker *checker = GetETSChecker();
auto checkerContext = SavedCheckerContext(checker, CheckerStatus::NO_OPTS, checker->Context().ContainingClass());
checker->CheckAnnotations(st);
if (st->TypeParams() == nullptr) {
if (st->TypeAnnotation()->TsType() == nullptr) {
st->TypeAnnotation()->Check(checker);
}
if ((st->IsExported() || st->IsDefaultExported()) && st->TypeAnnotation()->TsType() != nullptr) {
CheckExport(checker, st->TypeAnnotation()->TsType());
}
return ReturnTypeForStatement(st);
}
if (st->TypeParameterTypes().empty()) {
auto [typeParamTypes, ok] = checker->CreateUnconstrainedTypeParameters(st->TypeParams());
for (auto *tpt : typeParamTypes) {
tpt->AsETSTypeParameter()->SetUnderInference();
}
st->SetTypeParameterTypes(std::move(typeParamTypes));
if (ok) {
ok = checker->ValidateTypeParameterConstraints(st->TypeParams());
}
if (ok) {
checker->AssignTypeParameterConstraints(st->TypeParams());
}
}
if (st->TypeAnnotation()->TsType() == nullptr) {
st->TypeAnnotation()->Check(checker);
}
if ((st->IsExported() || st->IsDefaultExported()) && st->TypeAnnotation()->TsType() != nullptr) {
CheckExport(checker, st->TypeAnnotation()->TsType());
}
return ReturnTypeForStatement(st);
}
checker::Type *ETSAnalyzer::Check(ir::ETSGenericInstantiatedNode *expr) const
{
ES2PANDA_ASSERT(expr->GetExpression()->IsIdentifier() || expr->GetExpression()->IsMemberExpression());
ETSChecker *checker = GetETSChecker();
auto exprType = expr->GetExpression()->Check(checker);
if (exprType->IsTypeError()) {
expr->SetTsType(exprType);
}
if (!exprType->IsETSFunctionType()) {
return exprType;
}
auto *funcType = exprType->AsETSFunctionType();
if (funcType->CallSignaturesOfMethodOrArrow().size() != 1) {
checker->LogError(diagnostic::OVERLOADED_METHOD_AS_VALUE, expr->Start());
return checker->GlobalBuiltinErrorType();
}
auto *callSig = funcType->CallSignaturesOfMethodOrArrow().front();
if (funcType->CallSignaturesOfMethodOrArrow().front()->TypeParams().empty()) {
checker->LogError(diagnostic::TYPE_ARGS_FOR_NON_GENERIC_SIGNATURE, {callSig, expr->TypeParams()->DumpEtsSrc()},
expr->Start());
return checker->GlobalBuiltinErrorType();
}
const auto newSub = checker->CheckTypeParamsAndBuildSubstitutionIfValid(callSig, expr->TypeParams()->Params(),
expr->TypeParams()->Start());
if (!newSub.has_value()) {
return checker->GlobalBuiltinErrorType();
}
auto *const substitutedType = exprType->Substitute(checker->Relation(), &newSub.value());
auto *const substitutedSig = substitutedType->AsETSFunctionType()->CallSignaturesOfMethodOrArrow().front();
substitutedSig->TypeParams().clear();
return expr->SetTsType(substitutedType);
}
checker::Type *ETSAnalyzer::ReturnTypeForStatement([[maybe_unused]] const ir::Statement *const st) const
{
ES2PANDA_ASSERT(st->IsStatement());
return nullptr;
}
}