diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index ad1b41851cc0af2da4a55f1360cd575c55a08a76..00aa86eb010f74b6e5d0c402cbd6b34620e4057c 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -47,10 +47,12 @@ libes2panda_sources = [ "checker/ets/narrowingWideningConverter.cpp", "checker/ets/object.cpp", "checker/ets/primitiveWrappers.cpp", + "checker/ets/typeCheckingHelpers.cpp", "checker/ets/typeConverter.cpp", "checker/ets/typeCreation.cpp", "checker/ets/typeRelationContext.cpp", "checker/ets/unboxingConverter.cpp", + "checker/ets/validateHelpers.cpp", "checker/ets/wideningConverter.cpp", "checker/ts/binaryLikeExpression.cpp", "checker/ts/destructuringContext.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 6a662e36a53ab20d753d63248b583dbee691beb2..0d4ddc0fbcbe331b6f9d0988b83e6c65a02851c7 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -360,6 +360,8 @@ set(ES2PANDA_LIB_SRC checker/ets/function.cpp checker/ets/enum.cpp checker/ets/helpers.cpp + checker/ets/validateHelpers.cpp + checker/ets/typeCheckingHelpers.cpp checker/ets/narrowingConverter.cpp checker/ets/narrowingWideningConverter.cpp checker/ets/object.cpp diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index e909286f9379778b42a2def9136e029356ee7fb0..2d9990c9e2e6648ed33f7123c6cd26cf4b7a6bf7 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -131,6 +131,9 @@ public: Type *CheckTypeCached(ir::Expression *expr) override; void ResolveStructuredTypeMembers([[maybe_unused]] Type *type) override {} Type *GetTypeOfVariable([[maybe_unused]] varbinder::Variable *var) override; + Type *GetTypeForGetterSetter(varbinder::Variable *var); + Type *GetTypeForDynamicModuleVariable(varbinder::Variable *var); + void SetUpContextForNodeHierarchy(ir::AstNode *iter); Type *GuaranteedTypeForUncheckedCast(Type *base, Type *substituted); Type *GuaranteedTypeForUncheckedCallReturn(Signature *sig); Type *GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *prop); diff --git a/ets2panda/checker/ets/helpers.cpp b/ets2panda/checker/ets/helpers.cpp index 41e52fafab41dfa69f6e896b7839b31b51e0f803..49581d3c779c9abd9f87576247d74456df4fb115 100644 --- a/ets2panda/checker/ets/helpers.cpp +++ b/ets2panda/checker/ets/helpers.cpp @@ -72,368 +72,6 @@ #include "util/helpers.h" namespace panda::es2panda::checker { -void ETSChecker::CheckTruthinessOfType(ir::Expression *expr) -{ - checker::Type *type = expr->Check(this); - auto *unboxedType = ETSBuiltinTypeAsConditionalType(type); - - if (unboxedType == nullptr) { - ThrowTypeError("Condition must be of possible condition type", expr->Start()); - } - - if (unboxedType == GlobalBuiltinVoidType() || unboxedType->IsETSVoidType()) { - ThrowTypeError("An expression of type 'void' cannot be tested for truthiness", expr->Start()); - } - - if (!unboxedType->IsConditionalExprType()) { - ThrowTypeError("Condition must be of possible condition type", expr->Start()); - } - - if (unboxedType != nullptr && unboxedType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { - FlagExpressionWithUnboxing(type, unboxedType, expr); - } - expr->SetTsType(unboxedType); -} - -// NOTE: vpukhov. this entire function is isolated work-around until nullish type are not unions -Type *ETSChecker::CreateNullishType(Type *type, checker::TypeFlag nullishFlags, ArenaAllocator *allocator, - TypeRelation *relation, GlobalTypesHolder *globalTypes) -{ - ASSERT((nullishFlags & ~TypeFlag::NULLISH) == 0); - - auto *const nullish = type->Instantiate(allocator, relation, globalTypes); - - // Doesnt work for primitive array types, because instantiated type is equal to original one - - if ((nullishFlags & TypeFlag::NULL_TYPE) != 0) { - nullish->AddTypeFlag(checker::TypeFlag::NULL_TYPE); - } - if ((nullishFlags & TypeFlag::UNDEFINED) != 0) { - nullish->AddTypeFlag(checker::TypeFlag::UNDEFINED); - if (nullish->IsETSObjectType()) { - nullish->AsETSObjectType()->SetAssemblerName(GlobalETSObjectType()->AssemblerName()); - } - } - ASSERT(!nullish->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)); - return nullish; -} - -void ETSChecker::CheckNonNullishType([[maybe_unused]] Type *type, [[maybe_unused]] lexer::SourcePosition lineInfo) -{ - // NOTE: vpukhov. enable check when type inference is implemented - (void)type; -} - -// NOTE: vpukhov. rewrite with union types -Type *ETSChecker::GetNonNullishType(Type *type) const -{ - if (type->IsETSArrayType()) { - return type; // give up - } - if (type->IsETSTypeParameter()) { - return type->AsETSTypeParameter()->GetOriginal(); - } - - while (type->IsNullish()) { - type = type->AsETSObjectType()->GetBaseType(); - ASSERT(type != nullptr); - } - return type; -} - -// NOTE: vpukhov. rewrite with union types -const Type *ETSChecker::GetNonNullishType(const Type *type) const -{ - if (type->IsETSArrayType()) { - return type; // give up - } - if (type->IsETSTypeParameter()) { - return type->AsETSTypeParameter()->GetOriginal(); - } - - while (type->IsNullish()) { - type = type->AsETSObjectType()->GetBaseType(); - ASSERT(type != nullptr); - } - return type; -} - -Type *ETSChecker::CreateOptionalResultType(Type *type) -{ - if (type->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) { - type = PrimitiveTypeAsETSBuiltinType(type); - ASSERT(type->IsETSObjectType()); - Relation()->GetNode()->AddBoxingUnboxingFlags(GetBoxingFlag(type)); - } - - return CreateNullishType(type, checker::TypeFlag::UNDEFINED, Allocator(), Relation(), GetGlobalTypesHolder()); -} - -// NOTE(vpukhov): #14595 could be implemented with relation -template -static bool MatchConstitutentOrConstraint(P const &pred, const Type *type) -{ - if (pred(type)) { - return true; - } - if (type->IsETSUnionType()) { - for (auto const &ctype : type->AsETSUnionType()->ConstituentTypes()) { - if (MatchConstitutentOrConstraint(pred, ctype)) { - return true; - } - } - return false; - } - if (type->IsETSTypeParameter()) { - return MatchConstitutentOrConstraint(pred, type->AsETSTypeParameter()->GetConstraintType()); - } - return false; -} - -bool ETSChecker::MayHaveNullValue(const Type *type) const -{ - const auto pred = [](const Type *t) { return t->ContainsNull() || t->IsETSNullType(); }; - return MatchConstitutentOrConstraint(pred, type); -} - -bool ETSChecker::MayHaveUndefinedValue(const Type *type) const -{ - const auto pred = [](const Type *t) { return t->ContainsUndefined() || t->IsETSUndefinedType(); }; - return MatchConstitutentOrConstraint(pred, type); -} - -bool ETSChecker::MayHaveNulllikeValue(const Type *type) const -{ - const auto pred = [](const Type *t) { return t->IsNullishOrNullLike(); }; - return MatchConstitutentOrConstraint(pred, type); -} - -bool ETSChecker::IsConstantExpression(ir::Expression *expr, Type *type) -{ - return (type->HasTypeFlag(TypeFlag::CONSTANT) && (expr->IsIdentifier() || expr->IsMemberExpression())); -} - -Type *ETSChecker::GetNonConstantTypeFromPrimitiveType(Type *type) -{ - if (type->IsETSStringType()) { - // NOTE: vpukhov. remove when nullish types are unions - ASSERT(!type->IsNullish()); - return GlobalBuiltinETSStringType(); - } - - if (!type->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { - return type; - } - - // NOTE: vpukhov. remove when nullish types are unions - ASSERT(!type->IsNullish()); - - if (type->HasTypeFlag(TypeFlag::LONG)) { - return GlobalLongType(); - } - - if (type->HasTypeFlag(TypeFlag::BYTE)) { - return GlobalByteType(); - } - - if (type->HasTypeFlag(TypeFlag::SHORT)) { - return GlobalShortType(); - } - - if (type->HasTypeFlag(TypeFlag::CHAR)) { - return GlobalCharType(); - } - - if (type->HasTypeFlag(TypeFlag::INT)) { - return GlobalIntType(); - } - - if (type->HasTypeFlag(TypeFlag::FLOAT)) { - return GlobalFloatType(); - } - - if (type->HasTypeFlag(TypeFlag::DOUBLE)) { - return GlobalDoubleType(); - } - - if (type->IsETSBooleanType()) { - return GlobalETSBooleanType(); - } - return type; -} - -Type *ETSChecker::GetTypeOfVariable(varbinder::Variable *const var) -{ - if (IsVariableGetterSetter(var)) { - auto *propType = var->TsType()->AsETSFunctionType(); - if (propType->HasTypeFlag(checker::TypeFlag::GETTER)) { - return propType->FindGetter()->ReturnType(); - } - return propType->FindSetter()->Params()[0]->TsType(); - } - - if (var->TsType() != nullptr) { - return var->TsType(); - } - - // NOTE: kbaladurin. forbid usage of imported entities as types without declarations - if (VarBinder()->AsETSBinder()->IsDynamicModuleVariable(var)) { - auto *importData = VarBinder()->AsETSBinder()->DynamicImportDataForVar(var); - if (importData->import->IsPureDynamic()) { - return GlobalBuiltinDynamicType(importData->import->Language()); - } - } - - varbinder::Decl *decl = var->Declaration(); - - // Before computing the given variables type, we have to make a new checker context frame so that the checking is - // done in the proper context, and have to enter the scope where the given variable is declared, so reference - // resolution works properly - checker::SavedCheckerContext savedContext(this, CheckerStatus::NO_OPTS); - checker::ScopeContext scopeCtx(this, var->GetScope()); - auto *iter = decl->Node()->Parent(); - while (iter != nullptr) { - if (iter->IsMethodDefinition()) { - auto *methodDef = iter->AsMethodDefinition(); - ASSERT(methodDef->TsType()); - Context().SetContainingSignature(methodDef->Function()->Signature()); - } - - if (iter->IsClassDefinition()) { - auto *classDef = iter->AsClassDefinition(); - ETSObjectType *containingClass {}; - - if (classDef->TsType() == nullptr) { - containingClass = BuildClassProperties(classDef); - } else { - containingClass = classDef->TsType()->AsETSObjectType(); - } - - ASSERT(classDef->TsType()); - Context().SetContainingClass(containingClass); - } - - iter = iter->Parent(); - } - - switch (decl->Type()) { - case varbinder::DeclType::CLASS: { - auto *classDef = decl->Node()->AsClassDefinition(); - BuildClassProperties(classDef); - return classDef->TsType(); - } - case varbinder::DeclType::ENUM_LITERAL: - case varbinder::DeclType::CONST: - case varbinder::DeclType::LET: - case varbinder::DeclType::VAR: { - auto *declNode = decl->Node(); - - if (decl->Node()->IsIdentifier()) { - declNode = declNode->Parent(); - } - - return declNode->Check(this); - } - case varbinder::DeclType::FUNC: { - return decl->Node()->Check(this); - } - case varbinder::DeclType::IMPORT: { - return decl->Node()->Check(this); - } - case varbinder::DeclType::TYPE_ALIAS: { - return GetTypeFromTypeAliasReference(var); - } - case varbinder::DeclType::INTERFACE: { - return BuildInterfaceProperties(decl->Node()->AsTSInterfaceDeclaration()); - } - default: { - UNREACHABLE(); - } - } - - return var->TsType(); -} - -// Determine if unchecked cast is needed and yield guaranteed source type -Type *ETSChecker::GuaranteedTypeForUncheckedCast(Type *base, Type *substituted) -{ - if (!base->IsETSTypeParameter()) { - return nullptr; - } - auto *constr = base->AsETSTypeParameter()->GetConstraintType(); - // Constraint is supertype of TypeArg AND TypeArg is supertype of Constraint - return Relation()->IsIdenticalTo(substituted, constr) ? nullptr : constr; -} - -// Determine if substituted property access requires cast from erased type -Type *ETSChecker::GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *const prop) -{ - if (IsVariableStatic(prop)) { - return nullptr; - } - if (IsVariableGetterSetter(prop)) { - auto *method = prop->TsType()->AsETSFunctionType(); - if (!method->HasTypeFlag(checker::TypeFlag::GETTER)) { - return nullptr; - } - return GuaranteedTypeForUncheckedCallReturn(method->FindGetter()); - } - // NOTE(vpukhov): mark ETSDynamicType properties - if (prop->Declaration() == nullptr || prop->Declaration()->Node() == nullptr) { - return nullptr; - } - - auto *baseProp = prop->Declaration()->Node()->AsClassProperty()->Id()->Variable(); - if (baseProp == prop) { - return nullptr; - } - return GuaranteedTypeForUncheckedCast(GetTypeOfVariable(baseProp), GetTypeOfVariable(prop)); -} - -// Determine if substituted method cast requires cast from erased type -Type *ETSChecker::GuaranteedTypeForUncheckedCallReturn(Signature *sig) -{ - if (sig->HasSignatureFlag(checker::SignatureFlags::THIS_RETURN_TYPE)) { - return sig->ReturnType(); - } - auto *baseSig = sig->Function()->Signature(); - if (baseSig == sig) { - return nullptr; - } - return GuaranteedTypeForUncheckedCast(baseSig->ReturnType(), sig->ReturnType()); -} - -void ETSChecker::ValidatePropertyAccess(varbinder::Variable *var, ETSObjectType *obj, const lexer::SourcePosition &pos) -{ - if ((Context().Status() & CheckerStatus::IGNORE_VISIBILITY) != 0U) { - return; - } - if (var->HasFlag(varbinder::VariableFlags::METHOD)) { - return; - } - - if (var->HasFlag(varbinder::VariableFlags::PRIVATE) || var->HasFlag(varbinder::VariableFlags::PROTECTED)) { - if (Context().ContainingClass() == obj && obj->IsPropertyInherited(var)) { - return; - } - - if (var->HasFlag(varbinder::VariableFlags::PROTECTED) && Context().ContainingClass()->IsDescendantOf(obj) && - obj->IsPropertyInherited(var)) { - return; - } - - auto *currentOutermost = Context().ContainingClass()->OutermostClass(); - auto *objOutermost = obj->OutermostClass(); - - if (currentOutermost != nullptr && objOutermost != nullptr && currentOutermost == objOutermost && - obj->IsPropertyInherited(var)) { - return; - } - - ThrowTypeError({"Property ", var->Name(), " is not visible here."}, pos); - } -} - varbinder::Variable *ETSChecker::FindVariableInFunctionScope(const util::StringView name) { return Scope()->FindInFunctionScope(name, varbinder::ResolveBindingOptions::ALL).variable; @@ -476,23 +114,6 @@ void ETSChecker::ThrowError(ir::Identifier *const ident) ThrowTypeError({"Unresolved reference ", ident->Name()}, ident->Start()); } -void ETSChecker::CheckEtsFunctionType(ir::Identifier *const ident, ir::Identifier const *const id, - ir::TypeNode const *const annotation) -{ - if (annotation == nullptr) { - ThrowTypeError( - {"Cannot infer type for ", id->Name(), " because method reference needs an explicit target type"}, - id->Start()); - } - - const auto *const targetType = GetTypeOfVariable(id->Variable()); - ASSERT(targetType != nullptr); - - if (!targetType->IsETSObjectType() || !targetType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) { - ThrowError(ident); - } -} - void ETSChecker::NotResolvedError(ir::Identifier *const ident) { const auto [class_var, class_type] = FindVariableInClassOrEnclosing(ident->Name(), Context().ContainingClass()); @@ -509,41 +130,6 @@ void ETSChecker::NotResolvedError(ir::Identifier *const ident) } } -void ETSChecker::ValidateCallExpressionIdentifier(ir::Identifier *const ident, Type *const type) -{ - if (ident->Parent()->AsCallExpression()->Callee() == ident && !type->IsETSFunctionType() && - !type->IsETSDynamicType() && - (!type->IsETSObjectType() || !type->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) && - !TryTransformingToStaticInvoke(ident, type)) { - ThrowError(ident); - } -} - -void ETSChecker::ValidateNewClassInstanceIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved) -{ - if (ident->Parent()->AsETSNewClassInstanceExpression()->GetTypeRef() == ident && - !resolved->HasFlag(varbinder::VariableFlags::CLASS_OR_INTERFACE)) { - ThrowError(ident); - } -} - -void ETSChecker::ValidateMemberIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved, - Type *const type) -{ - if (ident->Parent()->AsMemberExpression()->IsComputed()) { - if (!resolved->Declaration()->PossibleTDZ()) { - ThrowError(ident); - } - - return; - } - - if (!IsReferenceType(type) && !type->IsETSEnumType() && !type->IsETSStringEnumType() && - !type->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { - ThrowError(ident); - } -} - std::pair ETSChecker::GetTargetIdentifierAndType(ir::Identifier *const ident) { if (ident->Parent()->IsClassProperty()) { @@ -556,115 +142,6 @@ std::pair ETSChecker::GetTargetIdentifie return std::make_pair(variableDecl->Id()->AsIdentifier(), variableDecl->Id()->AsIdentifier()->TypeAnnotation()); } -void ETSChecker::ValidatePropertyOrDeclaratorIdentifier(ir::Identifier *const ident, - varbinder::Variable *const resolved) -{ - const auto [target_ident, typeAnnotation] = GetTargetIdentifierAndType(ident); - - if (resolved->TsType()->IsETSFunctionType()) { - CheckEtsFunctionType(ident, target_ident, typeAnnotation); - return; - } - - if (!resolved->Declaration()->PossibleTDZ()) { - ThrowError(ident); - } -} - -void ETSChecker::ValidateAssignmentIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved, - Type *const type) -{ - const auto *const assignmentExpr = ident->Parent()->AsAssignmentExpression(); - if (assignmentExpr->Left() == ident && !resolved->Declaration()->PossibleTDZ()) { - ThrowError(ident); - } - - if (assignmentExpr->Right() == ident) { - const auto *const targetType = assignmentExpr->Left()->TsType(); - ASSERT(targetType != nullptr); - - if (targetType->IsETSObjectType() && targetType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) { - if (!type->IsETSFunctionType() && - !(type->IsETSObjectType() && type->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL))) { - ThrowTypeError({"Assigning a non-functional variable \"", ident->Name(), "\" to a functional type"}, - ident->Start()); - } - - return; - } - - if (!resolved->Declaration()->PossibleTDZ()) { - ThrowError(ident); - } - } -} - -bool ETSChecker::ValidateBinaryExpressionIdentifier(ir::Identifier *const ident, Type *const type) -{ - const auto *const binaryExpr = ident->Parent()->AsBinaryExpression(); - bool isFinished = false; - if (binaryExpr->OperatorType() == lexer::TokenType::KEYW_INSTANCEOF && binaryExpr->Right() == ident) { - if (!type->IsETSObjectType()) { - ThrowError(ident); - } - isFinished = true; - } - return isFinished; -} - -void ETSChecker::ValidateResolvedIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved) -{ - if (resolved == nullptr) { - NotResolvedError(ident); - } - - auto *const resolvedType = ETSChecker::GetApparentType(GetTypeOfVariable(resolved)); - - switch (ident->Parent()->Type()) { - case ir::AstNodeType::CALL_EXPRESSION: { - ValidateCallExpressionIdentifier(ident, resolvedType); - break; - } - case ir::AstNodeType::ETS_NEW_CLASS_INSTANCE_EXPRESSION: { - ValidateNewClassInstanceIdentifier(ident, resolved); - break; - } - case ir::AstNodeType::MEMBER_EXPRESSION: { - ValidateMemberIdentifier(ident, resolved, resolvedType); - break; - } - case ir::AstNodeType::BINARY_EXPRESSION: { - if (ValidateBinaryExpressionIdentifier(ident, resolvedType)) { - return; - } - - [[fallthrough]]; - } - case ir::AstNodeType::UPDATE_EXPRESSION: - case ir::AstNodeType::UNARY_EXPRESSION: { - if (!resolved->Declaration()->PossibleTDZ()) { - ThrowError(ident); - } - break; - } - case ir::AstNodeType::CLASS_PROPERTY: - case ir::AstNodeType::VARIABLE_DECLARATOR: { - ValidatePropertyOrDeclaratorIdentifier(ident, resolved); - break; - } - case ir::AstNodeType::ASSIGNMENT_EXPRESSION: { - ValidateAssignmentIdentifier(ident, resolved, resolvedType); - break; - } - default: { - if (!resolved->Declaration()->PossibleTDZ() && !resolvedType->IsETSFunctionType()) { - ThrowError(ident); - } - break; - } - } -} - void ETSChecker::SaveCapturedVariable(varbinder::Variable *const var, const lexer::SourcePosition &pos) { if (!HasStatus(CheckerStatus::IN_LAMBDA)) { @@ -729,26 +206,6 @@ Type *ETSChecker::ResolveIdentifier(ir::Identifier *const ident) return GetTypeOfVariable(resolved); } -void ETSChecker::ValidateUnaryOperatorOperand(varbinder::Variable *variable) -{ - if (IsVariableGetterSetter(variable)) { - return; - } - - if (variable->Declaration()->IsConstDecl()) { - if (HasStatus(CheckerStatus::IN_CONSTRUCTOR | CheckerStatus::IN_STATIC_BLOCK) && - !variable->HasFlag(varbinder::VariableFlags::EXPLICIT_INIT_REQUIRED)) { - ThrowTypeError({"Cannot reassign constant field ", variable->Name()}, - variable->Declaration()->Node()->Start()); - } - if (!HasStatus(CheckerStatus::IN_CONSTRUCTOR | CheckerStatus::IN_STATIC_BLOCK) && - !variable->HasFlag(varbinder::VariableFlags::EXPLICIT_INIT_REQUIRED)) { - ThrowTypeError({"Cannot assign to a constant variable ", variable->Name()}, - variable->Declaration()->Node()->Start()); - } - } -} - std::tuple ETSChecker::ApplyBinaryOperatorPromotion(Type *left, Type *right, TypeFlag test, bool doPromotion) { @@ -1023,33 +480,6 @@ void ETSChecker::ResolveReturnStatement(checker::Type *funcReturnType, checker:: } } -checker::Type *ETSChecker::CheckArrayElements(ir::Identifier *ident, ir::ArrayExpression *init) -{ - ArenaVector elements = init->AsArrayExpression()->Elements(); - checker::Type *annotationType = nullptr; - if (elements.empty()) { - annotationType = Allocator()->New(GlobalETSObjectType()); - } else { - auto type = elements[0]->Check(this); - auto const primType = ETSBuiltinTypeAsPrimitiveType(type); - for (auto element : elements) { - auto const eType = element->Check(this); - auto const primEType = ETSBuiltinTypeAsPrimitiveType(eType); - if (primEType != nullptr && primType != nullptr && primEType->HasTypeFlag(TypeFlag::ETS_NUMERIC) && - primType->HasTypeFlag(TypeFlag::ETS_NUMERIC)) { - type = GlobalDoubleType(); - } else if (IsTypeIdenticalTo(type, eType)) { - continue; - } else { - // NOTE: Create union type when implemented here - ThrowTypeError({"Union type is not implemented yet!"}, ident->Start()); - } - } - annotationType = Allocator()->New(type); - } - return annotationType; -} - checker::Type *ETSChecker::CheckVariableDeclaration(ir::Identifier *ident, ir::TypeNode *typeAnnotation, ir::Expression *init, ir::ModifierFlags flags) { @@ -1199,91 +629,6 @@ void ETSChecker::SetArrayPreferredTypeForNestedMemberExpressions(ir::MemberExpre } } -Type *ETSChecker::GetTypeFromTypeAliasReference(varbinder::Variable *var) -{ - if (var->TsType() != nullptr) { - return var->TsType(); - } - - auto *const aliasTypeNode = var->Declaration()->Node()->AsTSTypeAliasDeclaration(); - TypeStackElement tse(this, aliasTypeNode, "Circular type alias reference", aliasTypeNode->Start()); - aliasTypeNode->Check(this); - auto *const aliasedType = GetTypeFromTypeAnnotation(aliasTypeNode->TypeAnnotation()); - - var->SetTsType(aliasedType); - return aliasedType; -} - -Type *ETSChecker::GetTypeFromInterfaceReference(varbinder::Variable *var) -{ - if (var->TsType() != nullptr) { - return var->TsType(); - } - - auto *interfaceType = BuildInterfaceProperties(var->Declaration()->Node()->AsTSInterfaceDeclaration()); - var->SetTsType(interfaceType); - return interfaceType; -} - -Type *ETSChecker::GetTypeFromClassReference(varbinder::Variable *var) -{ - if (var->TsType() != nullptr) { - return var->TsType(); - } - - auto *classType = BuildClassProperties(var->Declaration()->Node()->AsClassDefinition()); - var->SetTsType(classType); - return classType; -} - -void ETSChecker::ValidateGenericTypeAliasForClonedNode(ir::TSTypeAliasDeclaration *const typeAliasNode, - const ir::TSTypeParameterInstantiation *const exactTypeParams) -{ - auto *const clonedNode = typeAliasNode->TypeAnnotation()->Clone(Allocator(), typeAliasNode); - - // Basic check, we really don't want to change the original type nodes, more precise checking should be made - ASSERT(clonedNode != typeAliasNode->TypeAnnotation()); - - // Currently only reference types are checked. This should be extended for other types in a follow up patch, but for - // complete usability, if the type isn't a simple reference type, then doN't check type alias declaration at all. - bool checkTypealias = true; - - // Only transforming a temporary cloned node, so no modification is made in the AST - clonedNode->TransformChildrenRecursively( - [&checkTypealias, &exactTypeParams, typeAliasNode](ir::AstNode *const node) -> ir::AstNode * { - if (!node->IsETSTypeReference()) { - return node; - } - - const auto *const nodeIdent = node->AsETSTypeReference()->Part()->Name()->AsIdentifier(); - - size_t typeParamIdx = 0; - for (const auto *const typeParam : typeAliasNode->TypeParams()->Params()) { - if (typeParam->Name()->AsIdentifier()->Variable() == nodeIdent->Variable()) { - break; - } - typeParamIdx++; - } - - if (typeParamIdx == typeAliasNode->TypeParams()->Params().size()) { - return node; - } - - auto *const typeParamType = exactTypeParams->Params().at(typeParamIdx); - - if (!typeParamType->IsETSTypeReference()) { - checkTypealias = false; - return node; - } - - return typeParamType; - }); - - if (checkTypealias) { - clonedNode->Check(this); - } -} - Type *ETSChecker::HandleTypeAlias(ir::Expression *const name, const ir::TSTypeParameterInstantiation *const typeParams) { ASSERT(name->IsIdentifier() && name->AsIdentifier()->Variable() && @@ -1328,34 +673,6 @@ Type *ETSChecker::HandleTypeAlias(ir::Expression *const name, const ir::TSTypePa return aliasType->Substitute(Relation(), aliasSub); } -Type *ETSChecker::GetTypeFromEnumReference([[maybe_unused]] varbinder::Variable *var) -{ - if (var->TsType() != nullptr) { - return var->TsType(); - } - - auto const *const enumDecl = var->Declaration()->Node()->AsTSEnumDeclaration(); - if (auto *const itemInit = enumDecl->Members().front()->AsTSEnumMember()->Init(); itemInit->IsNumberLiteral()) { - return CreateETSEnumType(enumDecl); - } else if (itemInit->IsStringLiteral()) { // NOLINT(readability-else-after-return) - return CreateETSStringEnumType(enumDecl); - } else { // NOLINT(readability-else-after-return) - ThrowTypeError("Invalid enumeration value type.", enumDecl->Start()); - } -} - -Type *ETSChecker::GetTypeFromTypeParameterReference(varbinder::LocalVariable *var, const lexer::SourcePosition &pos) -{ - ASSERT(var->Declaration()->Node()->IsTSTypeParameter()); - if ((var->Declaration()->Node()->AsTSTypeParameter()->Parent()->Parent()->IsClassDefinition() || - var->Declaration()->Node()->AsTSTypeParameter()->Parent()->Parent()->IsTSInterfaceDeclaration()) && - HasStatus(CheckerStatus::IN_STATIC_CONTEXT)) { - ThrowTypeError({"Cannot make a static reference to the non-static type ", var->Name()}, pos); - } - - return var->TsType(); -} - std::vector ETSChecker::GetNameForSynteticObjectType(const util::StringView &source) { const std::string str = source.Mutf8(); @@ -1428,52 +745,6 @@ void ETSChecker::SetrModuleObjectTsType(ir::Identifier *local, checker::ETSObjec } } -Type *ETSChecker::GetReferencedTypeFromBase([[maybe_unused]] Type *baseType, [[maybe_unused]] ir::Expression *name) -{ - return nullptr; -} - -Type *ETSChecker::GetReferencedTypeBase(ir::Expression *name) -{ - if (name->IsTSQualifiedName()) { - auto *qualified = name->AsTSQualifiedName(); - return qualified->Check(this); - } - - ASSERT(name->IsIdentifier() && name->AsIdentifier()->Variable() != nullptr); - - // NOTE: kbaladurin. forbid usage imported entities as types without declarations - auto *importData = VarBinder()->AsETSBinder()->DynamicImportDataForVar(name->AsIdentifier()->Variable()); - if (importData != nullptr && importData->import->IsPureDynamic()) { - return GlobalBuiltinDynamicType(importData->import->Language()); - } - - auto *refVar = name->AsIdentifier()->Variable()->AsLocalVariable(); - - switch (refVar->Declaration()->Node()->Type()) { - case ir::AstNodeType::TS_INTERFACE_DECLARATION: { - return GetTypeFromInterfaceReference(refVar); - } - case ir::AstNodeType::CLASS_DECLARATION: - case ir::AstNodeType::STRUCT_DECLARATION: - case ir::AstNodeType::CLASS_DEFINITION: { - return GetTypeFromClassReference(refVar); - } - case ir::AstNodeType::TS_ENUM_DECLARATION: { - return GetTypeFromEnumReference(refVar); - } - case ir::AstNodeType::TS_TYPE_PARAMETER: { - return GetTypeFromTypeParameterReference(refVar, name->Start()); - } - case ir::AstNodeType::TS_TYPE_ALIAS_DECLARATION: { - return GetTypeFromTypeAliasReference(refVar); - } - default: { - UNREACHABLE(); - } - } -} - void ETSChecker::ConcatConstantString(util::UString &target, Type *type) { switch (ETSType(type)) { @@ -1650,34 +921,6 @@ util::StringView ETSChecker::GetContainingObjectNameFromSignature(Signature *sig return {""}; } -bool ETSChecker::IsTypeBuiltinType(const Type *type) const -{ - if (!type->IsETSObjectType()) { - return false; - } - - switch (type->AsETSObjectType()->BuiltInKind()) { - case ETSObjectFlags::BUILTIN_BOOLEAN: - case ETSObjectFlags::BUILTIN_BYTE: - case ETSObjectFlags::BUILTIN_SHORT: - case ETSObjectFlags::BUILTIN_CHAR: - case ETSObjectFlags::BUILTIN_INT: - case ETSObjectFlags::BUILTIN_LONG: - case ETSObjectFlags::BUILTIN_FLOAT: - case ETSObjectFlags::BUILTIN_DOUBLE: { - return true; - } - default: - return false; - } -} - -bool ETSChecker::IsReferenceType(const Type *type) -{ - return type->HasTypeFlag(checker::TypeFlag::ETS_ARRAY_OR_OBJECT) || type->IsETSNullLike() || - type->IsETSStringType() || type->IsETSTypeParameter() || type->IsETSUnionType() || type->IsETSBigIntType(); -} - const ir::AstNode *ETSChecker::FindJumpTarget(ir::AstNodeType nodeType, const ir::AstNode *node, const ir::Identifier *target) { @@ -2372,32 +1615,6 @@ ETSObjectType *ETSChecker::GetOriginalBaseType(Type *const object) return object->AsETSObjectType()->GetOriginalBaseType(); } -Type *ETSChecker::GetTypeFromTypeAnnotation(ir::TypeNode *const typeAnnotation) -{ - auto *type = typeAnnotation->GetType(this); - - if (!typeAnnotation->IsNullAssignable() && !typeAnnotation->IsUndefinedAssignable()) { - return type; - } - - if (!IsReferenceType(type)) { - ThrowTypeError("Non reference types cannot be nullish.", typeAnnotation->Start()); - } - - if (type->IsNullish()) { - return type; - } - - TypeFlag nullishFlags {0}; - if (typeAnnotation->IsNullAssignable()) { - nullishFlags |= TypeFlag::NULL_TYPE; - } - if (typeAnnotation->IsUndefinedAssignable()) { - nullishFlags |= TypeFlag::UNDEFINED; - } - return CreateNullishType(type, nullishFlags, Allocator(), Relation(), GetGlobalTypesHolder()); -} - void ETSChecker::CheckValidGenericTypeParameter(Type *const argType, const lexer::SourcePosition &pos) { if (!argType->IsETSEnumType() && !argType->IsETSStringEnumType()) { @@ -2603,15 +1820,6 @@ bool ETSChecker::ExtensionETSFunctionType(checker::Type *type) return false; } -void ETSChecker::ValidateTupleMinElementSize(ir::ArrayExpression *const arrayExpr, ETSTupleType *tuple) -{ - if (arrayExpr->Elements().size() < static_cast(tuple->GetMinTupleSize())) { - ThrowTypeError({"Few elements in array initializer for tuple with size of ", - static_cast(tuple->GetMinTupleSize())}, - arrayExpr->Start()); - } -} - void ETSChecker::ModifyPreferredType(ir::ArrayExpression *const arrayExpr, Type *const newPreferredType) { // After modifying the preferred type of an array expression, it needs to be rechecked at the call site diff --git a/ets2panda/checker/ets/typeCheckingHelpers.cpp b/ets2panda/checker/ets/typeCheckingHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dbb0fb99f518d4856084f1f9e0e4197d5ecb7e1 --- /dev/null +++ b/ets2panda/checker/ets/typeCheckingHelpers.cpp @@ -0,0 +1,629 @@ +/** + * Copyright (c) 2021-2024 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 "compiler/lowering/scopesInit/scopesInitPhase.h" +#include "varbinder/variableFlags.h" +#include "checker/checker.h" +#include "checker/checkerContext.h" +#include "checker/ets/narrowingWideningConverter.h" +#include "checker/types/globalTypesHolder.h" +#include "checker/types/ets/etsObjectType.h" +#include "ir/astNode.h" +#include "lexer/token/tokenType.h" +#include "ir/base/catchClause.h" +#include "ir/expression.h" +#include "ir/typeNode.h" +#include "ir/base/scriptFunction.h" +#include "ir/base/classProperty.h" +#include "ir/base/methodDefinition.h" +#include "ir/statements/blockStatement.h" +#include "ir/statements/classDeclaration.h" +#include "ir/statements/variableDeclarator.h" +#include "ir/statements/switchCaseStatement.h" +#include "ir/expressions/identifier.h" +#include "ir/expressions/arrayExpression.h" +#include "ir/expressions/objectExpression.h" +#include "ir/expressions/callExpression.h" +#include "ir/expressions/memberExpression.h" +#include "ir/expressions/literals/booleanLiteral.h" +#include "ir/expressions/literals/charLiteral.h" +#include "ir/expressions/binaryExpression.h" +#include "ir/expressions/assignmentExpression.h" +#include "ir/expressions/arrowFunctionExpression.h" +#include "ir/expressions/literals/numberLiteral.h" +#include "ir/expressions/literals/undefinedLiteral.h" +#include "ir/expressions/literals/nullLiteral.h" +#include "ir/statements/labelledStatement.h" +#include "ir/statements/tryStatement.h" +#include "ir/ets/etsFunctionType.h" +#include "ir/ets/etsNewClassInstanceExpression.h" +#include "ir/ets/etsParameterExpression.h" +#include "ir/ts/tsAsExpression.h" +#include "ir/ts/tsTypeAliasDeclaration.h" +#include "ir/ts/tsEnumMember.h" +#include "ir/ts/tsTypeParameter.h" +#include "ir/ets/etsTypeReference.h" +#include "ir/ets/etsTypeReferencePart.h" +#include "ir/ets/etsPrimitiveType.h" +#include "ir/ts/tsQualifiedName.h" +#include "varbinder/variable.h" +#include "varbinder/scope.h" +#include "varbinder/declaration.h" +#include "parser/ETSparser.h" +#include "parser/program/program.h" +#include "checker/ETSchecker.h" +#include "varbinder/ETSBinder.h" +#include "checker/ets/typeRelationContext.h" +#include "checker/ets/boxingConverter.h" +#include "checker/ets/unboxingConverter.h" +#include "checker/types/ets/types.h" +#include "util/helpers.h" + +namespace panda::es2panda::checker { +void ETSChecker::CheckTruthinessOfType(ir::Expression *expr) +{ + checker::Type *type = expr->Check(this); + auto *unboxedType = ETSBuiltinTypeAsConditionalType(type); + + if (unboxedType == nullptr) { + ThrowTypeError("Condition must be of possible condition type", expr->Start()); + } + + if (unboxedType == GlobalBuiltinVoidType() || unboxedType->IsETSVoidType()) { + ThrowTypeError("An expression of type 'void' cannot be tested for truthiness", expr->Start()); + } + + if (!unboxedType->IsConditionalExprType()) { + ThrowTypeError("Condition must be of possible condition type", expr->Start()); + } + + if (unboxedType != nullptr && unboxedType->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { + FlagExpressionWithUnboxing(type, unboxedType, expr); + } + expr->SetTsType(unboxedType); +} + +// NOTE: vpukhov. this entire function is isolated work-around until nullish type are not unions +Type *ETSChecker::CreateNullishType(Type *type, checker::TypeFlag nullishFlags, ArenaAllocator *allocator, + TypeRelation *relation, GlobalTypesHolder *globalTypes) +{ + ASSERT((nullishFlags & ~TypeFlag::NULLISH) == 0); + + auto *const nullish = type->Instantiate(allocator, relation, globalTypes); + + // Doesnt work for primitive array types, because instantiated type is equal to original one + + if ((nullishFlags & TypeFlag::NULL_TYPE) != 0) { + nullish->AddTypeFlag(checker::TypeFlag::NULL_TYPE); + } + if ((nullishFlags & TypeFlag::UNDEFINED) != 0) { + nullish->AddTypeFlag(checker::TypeFlag::UNDEFINED); + if (nullish->IsETSObjectType()) { + nullish->AsETSObjectType()->SetAssemblerName(GlobalETSObjectType()->AssemblerName()); + } + } + ASSERT(!nullish->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)); + return nullish; +} + +void ETSChecker::CheckNonNullishType([[maybe_unused]] Type *type, [[maybe_unused]] lexer::SourcePosition lineInfo) +{ + // NOTE: vpukhov. enable check when type inference is implemented + (void)type; +} + +// NOTE: vpukhov. rewrite with union types +Type *ETSChecker::GetNonNullishType(Type *type) const +{ + if (type->IsETSArrayType()) { + return type; // give up + } + if (type->IsETSTypeParameter()) { + return type->AsETSTypeParameter()->GetOriginal(); + } + + while (type->IsNullish()) { + type = type->AsETSObjectType()->GetBaseType(); + ASSERT(type != nullptr); + } + return type; +} + +// NOTE: vpukhov. rewrite with union types +const Type *ETSChecker::GetNonNullishType(const Type *type) const +{ + if (type->IsETSArrayType()) { + return type; // give up + } + if (type->IsETSTypeParameter()) { + return type->AsETSTypeParameter()->GetOriginal(); + } + + while (type->IsNullish()) { + type = type->AsETSObjectType()->GetBaseType(); + ASSERT(type != nullptr); + } + return type; +} + +Type *ETSChecker::CreateOptionalResultType(Type *type) +{ + if (type->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) { + type = PrimitiveTypeAsETSBuiltinType(type); + ASSERT(type->IsETSObjectType()); + Relation()->GetNode()->AddBoxingUnboxingFlags(GetBoxingFlag(type)); + } + + return CreateNullishType(type, checker::TypeFlag::UNDEFINED, Allocator(), Relation(), GetGlobalTypesHolder()); +} + +// NOTE(vpukhov): #14595 could be implemented with relation +template +static bool MatchConstitutentOrConstraint(P const &pred, const Type *type) +{ + if (pred(type)) { + return true; + } + if (type->IsETSUnionType()) { + for (auto const &ctype : type->AsETSUnionType()->ConstituentTypes()) { + if (MatchConstitutentOrConstraint(pred, ctype)) { + return true; + } + } + return false; + } + if (type->IsETSTypeParameter()) { + return MatchConstitutentOrConstraint(pred, type->AsETSTypeParameter()->GetConstraintType()); + } + return false; +} + +bool ETSChecker::MayHaveNullValue(const Type *type) const +{ + const auto pred = [](const Type *t) { return t->ContainsNull() || t->IsETSNullType(); }; + return MatchConstitutentOrConstraint(pred, type); +} + +bool ETSChecker::MayHaveUndefinedValue(const Type *type) const +{ + const auto pred = [](const Type *t) { return t->ContainsUndefined() || t->IsETSUndefinedType(); }; + return MatchConstitutentOrConstraint(pred, type); +} + +bool ETSChecker::MayHaveNulllikeValue(const Type *type) const +{ + const auto pred = [](const Type *t) { return t->IsNullishOrNullLike(); }; + return MatchConstitutentOrConstraint(pred, type); +} + +bool ETSChecker::IsConstantExpression(ir::Expression *expr, Type *type) +{ + return (type->HasTypeFlag(TypeFlag::CONSTANT) && (expr->IsIdentifier() || expr->IsMemberExpression())); +} + +Type *ETSChecker::GetNonConstantTypeFromPrimitiveType(Type *type) +{ + if (type->IsETSStringType()) { + // NOTE: vpukhov. remove when nullish types are unions + ASSERT(!type->IsNullish()); + return GlobalBuiltinETSStringType(); + } + + if (!type->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { + return type; + } + + // NOTE: vpukhov. remove when nullish types are unions + ASSERT(!type->IsNullish()); + + if (type->HasTypeFlag(TypeFlag::LONG)) { + return GlobalLongType(); + } + + if (type->HasTypeFlag(TypeFlag::BYTE)) { + return GlobalByteType(); + } + + if (type->HasTypeFlag(TypeFlag::SHORT)) { + return GlobalShortType(); + } + + if (type->HasTypeFlag(TypeFlag::CHAR)) { + return GlobalCharType(); + } + + if (type->HasTypeFlag(TypeFlag::INT)) { + return GlobalIntType(); + } + + if (type->HasTypeFlag(TypeFlag::FLOAT)) { + return GlobalFloatType(); + } + + if (type->HasTypeFlag(TypeFlag::DOUBLE)) { + return GlobalDoubleType(); + } + + if (type->IsETSBooleanType()) { + return GlobalETSBooleanType(); + } + return type; +} + +Type *ETSChecker::GetTypeOfVariable(varbinder::Variable *const var) +{ + if (IsVariableGetterSetter(var)) { + return GetTypeForGetterSetter(var); + } + + if (var->TsType() != nullptr) { + return var->TsType(); + } + + // NOTE: kbaladurin. forbid usage of imported entities as types without declarations + if (VarBinder()->AsETSBinder()->IsDynamicModuleVariable(var)) { + return GetTypeForDynamicModuleVariable(var); + } + + varbinder::Decl *decl = var->Declaration(); + + // Before computing the given variables type, we have to make a new checker context frame so that the checking is + // done in the proper context, and have to enter the scope where the given variable is declared, so reference + // resolution works properly + checker::SavedCheckerContext savedContext(this, CheckerStatus::NO_OPTS); + checker::ScopeContext scopeCtx(this, var->GetScope()); + SetUpContextForNodeHierarchy(decl->Node()->Parent()); + + switch (decl->Type()) { + case varbinder::DeclType::CLASS: { + auto *classDef = decl->Node()->AsClassDefinition(); + BuildClassProperties(classDef); + return classDef->TsType(); + } + case varbinder::DeclType::ENUM_LITERAL: + case varbinder::DeclType::CONST: + case varbinder::DeclType::LET: + case varbinder::DeclType::VAR: { + auto *declNode = decl->Node(); + + if (decl->Node()->IsIdentifier()) { + declNode = declNode->Parent(); + } + + return declNode->Check(this); + } + case varbinder::DeclType::FUNC: { + return decl->Node()->Check(this); + } + case varbinder::DeclType::IMPORT: { + return decl->Node()->Check(this); + } + case varbinder::DeclType::TYPE_ALIAS: { + return GetTypeFromTypeAliasReference(var); + } + case varbinder::DeclType::INTERFACE: { + return BuildInterfaceProperties(decl->Node()->AsTSInterfaceDeclaration()); + } + default: { + UNREACHABLE(); + } + } + + return var->TsType(); +} + +Type *ETSChecker::GetTypeForGetterSetter(varbinder::Variable *const var) +{ + auto *propType = var->TsType()->AsETSFunctionType(); + if (propType->HasTypeFlag(checker::TypeFlag::GETTER)) { + return propType->FindGetter()->ReturnType(); + } + return propType->FindSetter()->Params()[0]->TsType(); +} + +Type *ETSChecker::GetTypeForDynamicModuleVariable(varbinder::Variable *const var) +{ + auto *importData = VarBinder()->AsETSBinder()->DynamicImportDataForVar(var); + if (importData->import->IsPureDynamic()) { + return GlobalBuiltinDynamicType(importData->import->Language()); + } + return nullptr; +} + +void ETSChecker::SetUpContextForNodeHierarchy(ir::AstNode *iter) +{ + while (iter != nullptr) { + if (iter->IsMethodDefinition()) { + auto *methodDef = iter->AsMethodDefinition(); + ASSERT(methodDef->TsType()); + Context().SetContainingSignature(methodDef->Function()->Signature()); + } + + if (iter->IsClassDefinition()) { + auto *classDef = iter->AsClassDefinition(); + ETSObjectType *containingClass {}; + + if (classDef->TsType() == nullptr) { + containingClass = BuildClassProperties(classDef); + } else { + containingClass = classDef->TsType()->AsETSObjectType(); + } + + ASSERT(classDef->TsType()); + Context().SetContainingClass(containingClass); + } + + iter = iter->Parent(); + } +} + +// Determine if unchecked cast is needed and yield guaranteed source type +Type *ETSChecker::GuaranteedTypeForUncheckedCast(Type *base, Type *substituted) +{ + if (!base->IsETSTypeParameter()) { + return nullptr; + } + auto *constr = base->AsETSTypeParameter()->GetConstraintType(); + // Constraint is supertype of TypeArg AND TypeArg is supertype of Constraint + return Relation()->IsIdenticalTo(substituted, constr) ? nullptr : constr; +} + +// Determine if substituted property access requires cast from erased type +Type *ETSChecker::GuaranteedTypeForUncheckedPropertyAccess(varbinder::Variable *const prop) +{ + if (IsVariableStatic(prop)) { + return nullptr; + } + if (IsVariableGetterSetter(prop)) { + auto *method = prop->TsType()->AsETSFunctionType(); + if (!method->HasTypeFlag(checker::TypeFlag::GETTER)) { + return nullptr; + } + return GuaranteedTypeForUncheckedCallReturn(method->FindGetter()); + } + // NOTE(vpukhov): mark ETSDynamicType properties + if (prop->Declaration() == nullptr || prop->Declaration()->Node() == nullptr) { + return nullptr; + } + + auto *baseProp = prop->Declaration()->Node()->AsClassProperty()->Id()->Variable(); + if (baseProp == prop) { + return nullptr; + } + return GuaranteedTypeForUncheckedCast(GetTypeOfVariable(baseProp), GetTypeOfVariable(prop)); +} + +// Determine if substituted method cast requires cast from erased type +Type *ETSChecker::GuaranteedTypeForUncheckedCallReturn(Signature *sig) +{ + if (sig->HasSignatureFlag(checker::SignatureFlags::THIS_RETURN_TYPE)) { + return sig->ReturnType(); + } + auto *baseSig = sig->Function()->Signature(); + if (baseSig == sig) { + return nullptr; + } + return GuaranteedTypeForUncheckedCast(baseSig->ReturnType(), sig->ReturnType()); +} + +bool ETSChecker::IsTypeBuiltinType(const Type *type) const +{ + if (!type->IsETSObjectType()) { + return false; + } + + switch (type->AsETSObjectType()->BuiltInKind()) { + case ETSObjectFlags::BUILTIN_BOOLEAN: + case ETSObjectFlags::BUILTIN_BYTE: + case ETSObjectFlags::BUILTIN_SHORT: + case ETSObjectFlags::BUILTIN_CHAR: + case ETSObjectFlags::BUILTIN_INT: + case ETSObjectFlags::BUILTIN_LONG: + case ETSObjectFlags::BUILTIN_FLOAT: + case ETSObjectFlags::BUILTIN_DOUBLE: { + return true; + } + default: + return false; + } +} + +bool ETSChecker::IsReferenceType(const Type *type) +{ + return type->HasTypeFlag(checker::TypeFlag::ETS_ARRAY_OR_OBJECT) || type->IsETSNullLike() || + type->IsETSStringType() || type->IsETSTypeParameter() || type->IsETSUnionType() || type->IsETSBigIntType(); +} + +Type *ETSChecker::GetTypeFromTypeAliasReference(varbinder::Variable *var) +{ + if (var->TsType() != nullptr) { + return var->TsType(); + } + + auto *const aliasTypeNode = var->Declaration()->Node()->AsTSTypeAliasDeclaration(); + TypeStackElement tse(this, aliasTypeNode, "Circular type alias reference", aliasTypeNode->Start()); + aliasTypeNode->Check(this); + auto *const aliasedType = GetTypeFromTypeAnnotation(aliasTypeNode->TypeAnnotation()); + + var->SetTsType(aliasedType); + return aliasedType; +} + +Type *ETSChecker::GetTypeFromInterfaceReference(varbinder::Variable *var) +{ + if (var->TsType() != nullptr) { + return var->TsType(); + } + + auto *interfaceType = BuildInterfaceProperties(var->Declaration()->Node()->AsTSInterfaceDeclaration()); + var->SetTsType(interfaceType); + return interfaceType; +} + +Type *ETSChecker::GetTypeFromClassReference(varbinder::Variable *var) +{ + if (var->TsType() != nullptr) { + return var->TsType(); + } + + auto *classType = BuildClassProperties(var->Declaration()->Node()->AsClassDefinition()); + var->SetTsType(classType); + return classType; +} + +Type *ETSChecker::GetTypeFromEnumReference([[maybe_unused]] varbinder::Variable *var) +{ + if (var->TsType() != nullptr) { + return var->TsType(); + } + + auto const *const enumDecl = var->Declaration()->Node()->AsTSEnumDeclaration(); + if (auto *const itemInit = enumDecl->Members().front()->AsTSEnumMember()->Init(); itemInit->IsNumberLiteral()) { + return CreateETSEnumType(enumDecl); + } else if (itemInit->IsStringLiteral()) { // NOLINT(readability-else-after-return) + return CreateETSStringEnumType(enumDecl); + } else { // NOLINT(readability-else-after-return) + ThrowTypeError("Invalid enumeration value type.", enumDecl->Start()); + } +} + +Type *ETSChecker::GetTypeFromTypeParameterReference(varbinder::LocalVariable *var, const lexer::SourcePosition &pos) +{ + ASSERT(var->Declaration()->Node()->IsTSTypeParameter()); + if ((var->Declaration()->Node()->AsTSTypeParameter()->Parent()->Parent()->IsClassDefinition() || + var->Declaration()->Node()->AsTSTypeParameter()->Parent()->Parent()->IsTSInterfaceDeclaration()) && + HasStatus(CheckerStatus::IN_STATIC_CONTEXT)) { + ThrowTypeError({"Cannot make a static reference to the non-static type ", var->Name()}, pos); + } + + return var->TsType(); +} + +void ETSChecker::CheckEtsFunctionType(ir::Identifier *const ident, ir::Identifier const *const id, + ir::TypeNode const *const annotation) +{ + if (annotation == nullptr) { + ThrowTypeError( + {"Cannot infer type for ", id->Name(), " because method reference needs an explicit target type"}, + id->Start()); + } + + const auto *const targetType = GetTypeOfVariable(id->Variable()); + ASSERT(targetType != nullptr); + + if (!targetType->IsETSObjectType() || !targetType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) { + ThrowError(ident); + } +} + +checker::Type *ETSChecker::CheckArrayElements(ir::Identifier *ident, ir::ArrayExpression *init) +{ + ArenaVector elements = init->AsArrayExpression()->Elements(); + checker::Type *annotationType = nullptr; + if (elements.empty()) { + annotationType = Allocator()->New(GlobalETSObjectType()); + } else { + auto type = elements[0]->Check(this); + auto const primType = ETSBuiltinTypeAsPrimitiveType(type); + for (auto element : elements) { + auto const eType = element->Check(this); + auto const primEType = ETSBuiltinTypeAsPrimitiveType(eType); + if (primEType != nullptr && primType != nullptr && primEType->HasTypeFlag(TypeFlag::ETS_NUMERIC) && + primType->HasTypeFlag(TypeFlag::ETS_NUMERIC)) { + type = GlobalDoubleType(); + } else if (IsTypeIdenticalTo(type, eType)) { + continue; + } else { + // NOTE: Create union type when implemented here + ThrowTypeError({"Union type is not implemented yet!"}, ident->Start()); + } + } + annotationType = Allocator()->New(type); + } + return annotationType; +} + +Type *ETSChecker::GetReferencedTypeBase(ir::Expression *name) +{ + if (name->IsTSQualifiedName()) { + auto *qualified = name->AsTSQualifiedName(); + return qualified->Check(this); + } + + ASSERT(name->IsIdentifier() && name->AsIdentifier()->Variable() != nullptr); + + // NOTE: kbaladurin. forbid usage imported entities as types without declarations + auto *importData = VarBinder()->AsETSBinder()->DynamicImportDataForVar(name->AsIdentifier()->Variable()); + if (importData != nullptr && importData->import->IsPureDynamic()) { + return GlobalBuiltinDynamicType(importData->import->Language()); + } + + auto *refVar = name->AsIdentifier()->Variable()->AsLocalVariable(); + + switch (refVar->Declaration()->Node()->Type()) { + case ir::AstNodeType::TS_INTERFACE_DECLARATION: { + return GetTypeFromInterfaceReference(refVar); + } + case ir::AstNodeType::CLASS_DECLARATION: + case ir::AstNodeType::STRUCT_DECLARATION: + case ir::AstNodeType::CLASS_DEFINITION: { + return GetTypeFromClassReference(refVar); + } + case ir::AstNodeType::TS_ENUM_DECLARATION: { + return GetTypeFromEnumReference(refVar); + } + case ir::AstNodeType::TS_TYPE_PARAMETER: { + return GetTypeFromTypeParameterReference(refVar, name->Start()); + } + case ir::AstNodeType::TS_TYPE_ALIAS_DECLARATION: { + return GetTypeFromTypeAliasReference(refVar); + } + default: { + UNREACHABLE(); + } + } +} + +Type *ETSChecker::GetReferencedTypeFromBase([[maybe_unused]] Type *baseType, [[maybe_unused]] ir::Expression *name) +{ + return nullptr; +} + +Type *ETSChecker::GetTypeFromTypeAnnotation(ir::TypeNode *const typeAnnotation) +{ + auto *type = typeAnnotation->GetType(this); + + if (!typeAnnotation->IsNullAssignable() && !typeAnnotation->IsUndefinedAssignable()) { + return type; + } + + if (!IsReferenceType(type)) { + ThrowTypeError("Non reference types cannot be nullish.", typeAnnotation->Start()); + } + + if (type->IsNullish()) { + return type; + } + + TypeFlag nullishFlags {0}; + if (typeAnnotation->IsNullAssignable()) { + nullishFlags |= TypeFlag::NULL_TYPE; + } + if (typeAnnotation->IsUndefinedAssignable()) { + nullishFlags |= TypeFlag::UNDEFINED; + } + return CreateNullishType(type, nullishFlags, Allocator(), Relation(), GetGlobalTypesHolder()); +} +} // namespace panda::es2panda::checker diff --git a/ets2panda/checker/ets/validateHelpers.cpp b/ets2panda/checker/ets/validateHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f9b4f74ec6b650ee3db0ffdb3eebd989a0d13815 --- /dev/null +++ b/ets2panda/checker/ets/validateHelpers.cpp @@ -0,0 +1,327 @@ +/** + * Copyright (c) 2021-2024 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 "compiler/lowering/scopesInit/scopesInitPhase.h" +#include "varbinder/variableFlags.h" +#include "checker/checker.h" +#include "checker/checkerContext.h" +#include "checker/ets/narrowingWideningConverter.h" +#include "checker/types/globalTypesHolder.h" +#include "checker/types/ets/etsObjectType.h" +#include "ir/astNode.h" +#include "lexer/token/tokenType.h" +#include "ir/base/catchClause.h" +#include "ir/expression.h" +#include "ir/typeNode.h" +#include "ir/base/scriptFunction.h" +#include "ir/base/classProperty.h" +#include "ir/base/methodDefinition.h" +#include "ir/statements/blockStatement.h" +#include "ir/statements/classDeclaration.h" +#include "ir/statements/variableDeclarator.h" +#include "ir/statements/switchCaseStatement.h" +#include "ir/expressions/identifier.h" +#include "ir/expressions/arrayExpression.h" +#include "ir/expressions/objectExpression.h" +#include "ir/expressions/callExpression.h" +#include "ir/expressions/memberExpression.h" +#include "ir/expressions/literals/booleanLiteral.h" +#include "ir/expressions/literals/charLiteral.h" +#include "ir/expressions/binaryExpression.h" +#include "ir/expressions/assignmentExpression.h" +#include "ir/expressions/arrowFunctionExpression.h" +#include "ir/expressions/literals/numberLiteral.h" +#include "ir/expressions/literals/undefinedLiteral.h" +#include "ir/expressions/literals/nullLiteral.h" +#include "ir/statements/labelledStatement.h" +#include "ir/statements/tryStatement.h" +#include "ir/ets/etsFunctionType.h" +#include "ir/ets/etsNewClassInstanceExpression.h" +#include "ir/ets/etsParameterExpression.h" +#include "ir/ts/tsAsExpression.h" +#include "ir/ts/tsTypeAliasDeclaration.h" +#include "ir/ts/tsEnumMember.h" +#include "ir/ts/tsTypeParameter.h" +#include "ir/ets/etsTypeReference.h" +#include "ir/ets/etsTypeReferencePart.h" +#include "ir/ets/etsPrimitiveType.h" +#include "ir/ts/tsQualifiedName.h" +#include "varbinder/variable.h" +#include "varbinder/scope.h" +#include "varbinder/declaration.h" +#include "parser/ETSparser.h" +#include "parser/program/program.h" +#include "checker/ETSchecker.h" +#include "varbinder/ETSBinder.h" +#include "checker/ets/typeRelationContext.h" +#include "checker/ets/boxingConverter.h" +#include "checker/ets/unboxingConverter.h" +#include "checker/types/ets/types.h" +#include "util/helpers.h" + +namespace panda::es2panda::checker { +void ETSChecker::ValidatePropertyAccess(varbinder::Variable *var, ETSObjectType *obj, const lexer::SourcePosition &pos) +{ + if ((Context().Status() & CheckerStatus::IGNORE_VISIBILITY) != 0U) { + return; + } + if (var->HasFlag(varbinder::VariableFlags::METHOD)) { + return; + } + + if (var->HasFlag(varbinder::VariableFlags::PRIVATE) || var->HasFlag(varbinder::VariableFlags::PROTECTED)) { + if (Context().ContainingClass() == obj && obj->IsPropertyInherited(var)) { + return; + } + + if (var->HasFlag(varbinder::VariableFlags::PROTECTED) && Context().ContainingClass()->IsDescendantOf(obj) && + obj->IsPropertyInherited(var)) { + return; + } + + auto *currentOutermost = Context().ContainingClass()->OutermostClass(); + auto *objOutermost = obj->OutermostClass(); + + if (currentOutermost != nullptr && objOutermost != nullptr && currentOutermost == objOutermost && + obj->IsPropertyInherited(var)) { + return; + } + + ThrowTypeError({"Property ", var->Name(), " is not visible here."}, pos); + } +} + +void ETSChecker::ValidateCallExpressionIdentifier(ir::Identifier *const ident, Type *const type) +{ + if (ident->Parent()->AsCallExpression()->Callee() == ident && !type->IsETSFunctionType() && + !type->IsETSDynamicType() && + (!type->IsETSObjectType() || !type->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) && + !TryTransformingToStaticInvoke(ident, type)) { + ThrowError(ident); + } +} + +void ETSChecker::ValidateNewClassInstanceIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved) +{ + if (ident->Parent()->AsETSNewClassInstanceExpression()->GetTypeRef() == ident && (resolved != nullptr) && + !resolved->HasFlag(varbinder::VariableFlags::CLASS_OR_INTERFACE)) { + ThrowError(ident); + } +} + +void ETSChecker::ValidateMemberIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved, + Type *const type) +{ + if (ident->Parent()->AsMemberExpression()->IsComputed()) { + if ((resolved != nullptr) && !resolved->Declaration()->PossibleTDZ()) { + ThrowError(ident); + } + + return; + } + + if (!IsReferenceType(type) && !type->IsETSEnumType() && !type->IsETSStringEnumType() && + !type->HasTypeFlag(TypeFlag::ETS_PRIMITIVE)) { + ThrowError(ident); + } +} + +void ETSChecker::ValidatePropertyOrDeclaratorIdentifier(ir::Identifier *const ident, + varbinder::Variable *const resolved) +{ + const auto [target_ident, typeAnnotation] = GetTargetIdentifierAndType(ident); + + if ((resolved != nullptr) && resolved->TsType()->IsETSFunctionType()) { + CheckEtsFunctionType(ident, target_ident, typeAnnotation); + return; + } + + if ((resolved != nullptr) && !resolved->Declaration()->PossibleTDZ()) { + ThrowError(ident); + } +} + +void ETSChecker::ValidateAssignmentIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved, + Type *const type) +{ + const auto *const assignmentExpr = ident->Parent()->AsAssignmentExpression(); + if (assignmentExpr->Left() == ident && (resolved != nullptr) && !resolved->Declaration()->PossibleTDZ()) { + ThrowError(ident); + } + + if (assignmentExpr->Right() == ident) { + const auto *const targetType = assignmentExpr->Left()->TsType(); + ASSERT(targetType != nullptr); + + if (targetType->IsETSObjectType() && targetType->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL)) { + if (!type->IsETSFunctionType() && + !(type->IsETSObjectType() && type->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::FUNCTIONAL))) { + ThrowTypeError({"Assigning a non-functional variable \"", ident->Name(), "\" to a functional type"}, + ident->Start()); + } + + return; + } + + if ((resolved != nullptr) && !resolved->Declaration()->PossibleTDZ()) { + ThrowError(ident); + } + } +} + +bool ETSChecker::ValidateBinaryExpressionIdentifier(ir::Identifier *const ident, Type *const type) +{ + const auto *const binaryExpr = ident->Parent()->AsBinaryExpression(); + bool isFinished = false; + if (binaryExpr->OperatorType() == lexer::TokenType::KEYW_INSTANCEOF && binaryExpr->Right() == ident) { + if (!type->IsETSObjectType()) { + ThrowError(ident); + } + isFinished = true; + } + return isFinished; +} + +void ETSChecker::ValidateResolvedIdentifier(ir::Identifier *const ident, varbinder::Variable *const resolved) +{ + if (resolved == nullptr) { + NotResolvedError(ident); + } + + auto *const resolvedType = ETSChecker::GetApparentType(GetTypeOfVariable(resolved)); + + switch (ident->Parent()->Type()) { + case ir::AstNodeType::CALL_EXPRESSION: { + ValidateCallExpressionIdentifier(ident, resolvedType); + break; + } + case ir::AstNodeType::ETS_NEW_CLASS_INSTANCE_EXPRESSION: { + ValidateNewClassInstanceIdentifier(ident, resolved); + break; + } + case ir::AstNodeType::MEMBER_EXPRESSION: { + ValidateMemberIdentifier(ident, resolved, resolvedType); + break; + } + case ir::AstNodeType::BINARY_EXPRESSION: { + if (ValidateBinaryExpressionIdentifier(ident, resolvedType)) { + return; + } + + [[fallthrough]]; + } + case ir::AstNodeType::UPDATE_EXPRESSION: + case ir::AstNodeType::UNARY_EXPRESSION: { + if ((resolved != nullptr) && !resolved->Declaration()->PossibleTDZ()) { + ThrowError(ident); + } + break; + } + case ir::AstNodeType::CLASS_PROPERTY: + case ir::AstNodeType::VARIABLE_DECLARATOR: { + ValidatePropertyOrDeclaratorIdentifier(ident, resolved); + break; + } + case ir::AstNodeType::ASSIGNMENT_EXPRESSION: { + ValidateAssignmentIdentifier(ident, resolved, resolvedType); + break; + } + default: { + if ((resolved != nullptr) && !resolved->Declaration()->PossibleTDZ() && + !resolvedType->IsETSFunctionType()) { + ThrowError(ident); + } + break; + } + } +} + +void ETSChecker::ValidateUnaryOperatorOperand(varbinder::Variable *variable) +{ + if (IsVariableGetterSetter(variable)) { + return; + } + + if (variable->Declaration()->IsConstDecl()) { + if (HasStatus(CheckerStatus::IN_CONSTRUCTOR | CheckerStatus::IN_STATIC_BLOCK) && + !variable->HasFlag(varbinder::VariableFlags::EXPLICIT_INIT_REQUIRED)) { + ThrowTypeError({"Cannot reassign constant field ", variable->Name()}, + variable->Declaration()->Node()->Start()); + } + if (!HasStatus(CheckerStatus::IN_CONSTRUCTOR | CheckerStatus::IN_STATIC_BLOCK) && + !variable->HasFlag(varbinder::VariableFlags::EXPLICIT_INIT_REQUIRED)) { + ThrowTypeError({"Cannot assign to a constant variable ", variable->Name()}, + variable->Declaration()->Node()->Start()); + } + } +} + +void ETSChecker::ValidateGenericTypeAliasForClonedNode(ir::TSTypeAliasDeclaration *const typeAliasNode, + const ir::TSTypeParameterInstantiation *const exactTypeParams) +{ + auto *const clonedNode = typeAliasNode->TypeAnnotation()->Clone(Allocator(), typeAliasNode); + + // Basic check, we really don't want to change the original type nodes, more precise checking should be made + ASSERT(clonedNode != typeAliasNode->TypeAnnotation()); + + // Currently only reference types are checked. This should be extended for other types in a follow up patch, but for + // complete usability, if the type isn't a simple reference type, then doN't check type alias declaration at all. + bool checkTypealias = true; + + // Only transforming a temporary cloned node, so no modification is made in the AST + clonedNode->TransformChildrenRecursively( + [&checkTypealias, &exactTypeParams, typeAliasNode](ir::AstNode *const node) -> ir::AstNode * { + if (!node->IsETSTypeReference()) { + return node; + } + + const auto *const nodeIdent = node->AsETSTypeReference()->Part()->Name()->AsIdentifier(); + + size_t typeParamIdx = 0; + for (const auto *const typeParam : typeAliasNode->TypeParams()->Params()) { + if (typeParam->Name()->AsIdentifier()->Variable() == nodeIdent->Variable()) { + break; + } + typeParamIdx++; + } + + if (typeParamIdx == typeAliasNode->TypeParams()->Params().size()) { + return node; + } + + auto *const typeParamType = exactTypeParams->Params().at(typeParamIdx); + + if (!typeParamType->IsETSTypeReference()) { + checkTypealias = false; + return node; + } + + return typeParamType; + }); + + if (checkTypealias) { + clonedNode->Check(this); + } +} + +void ETSChecker::ValidateTupleMinElementSize(ir::ArrayExpression *const arrayExpr, ETSTupleType *tuple) +{ + if (arrayExpr->Elements().size() < static_cast(tuple->GetMinTupleSize())) { + ThrowTypeError({"Few elements in array initializer for tuple with size of ", + static_cast(tuple->GetMinTupleSize())}, + arrayExpr->Start()); + } +} +} // namespace panda::es2panda::checker