diff --git a/ets2panda/compiler/core/ASTVerifier.cpp b/ets2panda/compiler/core/ASTVerifier.cpp index fce72cee39540ca260c2bfe99921bce78fc74bef..a42f0470217e115a9c6627c6167b99526c9b72a6 100644 --- a/ets2panda/compiler/core/ASTVerifier.cpp +++ b/ets2panda/compiler/core/ASTVerifier.cpp @@ -16,37 +16,15 @@ #include "ASTVerifier.h" #include "es2panda.h" -#include "varbinder/variableFlags.h" #include "varbinder/scope.h" #include "ir/astNode.h" -#include "ir/base/catchClause.h" #include "ir/base/classDefinition.h" -#include "ir/base/classStaticBlock.h" -#include "ir/base/methodDefinition.h" -#include "ir/base/scriptFunction.h" -#include "ir/ets/etsFunctionType.h" -#include "ir/ets/etsNewClassInstanceExpression.h" -#include "ir/ets/etsPackageDeclaration.h" -#include "ir/ets/etsParameterExpression.h" -#include "ir/ets/etsTypeReference.h" -#include "ir/ets/etsTypeReferencePart.h" -#include "ir/expressions/callExpression.h" -#include "ir/expressions/functionExpression.h" #include "ir/expressions/identifier.h" -#include "ir/expressions/memberExpression.h" -#include "ir/expressions/literals/numberLiteral.h" -#include "ir/expressions/literals/stringLiteral.h" #include "ir/statements/blockStatement.h" -#include "ir/statements/classDeclaration.h" -#include "ir/statements/expressionStatement.h" -#include "ir/statements/throwStatement.h" -#include "ir/statements/tryStatement.h" +#include "ir/statements/forInStatement.h" +#include "ir/statements/forOfStatement.h" +#include "ir/statements/forUpdateStatement.h" #include "ir/statements/variableDeclaration.h" -#include "ir/statements/variableDeclarator.h" -#include "ir/ts/tsClassImplements.h" -#include "ir/ts/tsTypeParameter.h" -#include "ir/ts/tsTypeParameterDeclaration.h" -#include "ir/ts/tsTypeParameterInstantiation.h" namespace panda::es2panda::compiler { @@ -75,233 +53,17 @@ bool ASTVerifier::IsCorrectProgram(const parser::Program *program) } is_correct &= HaveScopes(program->GlobalClass()); + for (auto *statement : program->Ast()->Statements()) { + is_correct &= AreForLoopsCorrectInitialized(statement); + } + is_correct &= AreForLoopsCorrectInitialized(program->GlobalClass()); + #ifndef NDEBUG std::for_each(error_messages_.begin(), error_messages_.end(), [](auto const msg) { LOG(INFO, COMMON) << msg; }); #endif // NDEBUG return is_correct; } -std::string ToStringHelper(const varbinder::ScopeType type) -{ - switch (type) { - case varbinder::ScopeType::CATCH: { - return "CATCH"; - } - case varbinder::ScopeType::CATCH_PARAM: { - return "CATCH_PARAM"; - } - case varbinder::ScopeType::CLASS: { - return "CLASS"; - } - case varbinder::ScopeType::FUNCTION: { - return "FUNCTION"; - } - case varbinder::ScopeType::FUNCTION_PARAM: { - return "FUNCTION_PARAM"; - } - case varbinder::ScopeType::GLOBAL: { - return "GLOBAL"; - } - case varbinder::ScopeType::LOCAL: { - return "LOCAL"; - } - case varbinder::ScopeType::LOOP: { - return "LOOP"; - } - case varbinder::ScopeType::LOOP_DECL: { - return "LOOP_DECL"; - } - case varbinder::ScopeType::MODULE: { - return "MODULE"; - } - case varbinder::ScopeType::PARAM: { - return "PARAM"; - } - default: { - return "MUST BE UNREACHABLE"; - } - } -} - -std::string ToStringHelper(const util::StringView &name) -{ - return name == nullptr ? "" : name.Mutf8(); -} - -std::string ToStringHelper(const varbinder::Scope *scope) -{ - if (scope == nullptr) { - return ""; - } - - switch (scope->Type()) { - case varbinder::ScopeType::FUNCTION: { - return "FUNC_SCOPE " + ToStringHelper(scope->AsFunctionScope()->Name()); - } - case varbinder::ScopeType::LOCAL: { - return "LOCAL_SCOPE "; - } - case varbinder::ScopeType::CATCH: { - return "CATCH_SCOPE "; - } - default: { - return "MUST BE UNREACHABLE"; - } - } -} - -std::string ToStringHelper(const varbinder::Variable *var) -{ - if (var == nullptr) { - return ""; - } - - switch (var->Type()) { - case varbinder::VariableType::LOCAL: { - return "LOCAL_VAR " + ToStringHelper(var->Name()); - } - case varbinder::VariableType::MODULE: { - return "MODULE_VAR " + ToStringHelper(var->Name()); - } - case varbinder::VariableType::GLOBAL: { - return "GLOBAL_VAR " + ToStringHelper(var->Name()); - } - case varbinder::VariableType::ENUM: { - return "ENUM_VAR " + ToStringHelper(var->Name()); - } - default: { - return "MUST BE UNREACHABLE"; - } - } -} - -template -std::string ToStringParamsHelper(const ir::AstNode *parent, const ArenaVector ¶ms) -{ - std::string name; - if (parent != nullptr) { - name = ToStringHelper(parent) + " "; - } - - name += "("; - for (auto const *param : params) { - name += ToStringHelper(param); - } - - return name + ")"; -} - -std::string ToStringHelper(const ir::AstNode *ast) -{ - if (ast == nullptr) { - return ""; - } - - switch (ast->Type()) { - case ir::AstNodeType::IDENTIFIER: { - return "ID " + ToStringHelper(ast->AsIdentifier()->Name()); - } - case ir::AstNodeType::CLASS_DEFINITION: { - return "CLS_DEF " + ToStringHelper(ast->AsClassDefinition()->Ident()); - } - case ir::AstNodeType::CLASS_DECLARATION: { - return "CLS_DECL " + ToStringHelper(ast->AsClassDeclaration()->Definition()); - } - case ir::AstNodeType::BLOCK_STATEMENT: { - return "BLOCK " + ToStringHelper(ast->AsBlockStatement()->Scope()); - } - case ir::AstNodeType::SCRIPT_FUNCTION: { - auto const *sf = ast->AsScriptFunction(); - return "SCRIPT_FUN " + ToStringHelper(sf->Scope()) + "::" + ToStringHelper(sf->Id()); - } - case ir::AstNodeType::FUNCTION_EXPRESSION: { - return "FUN_EXPR " + ToStringHelper(ast->AsFunctionExpression()->Function()); - } - case ir::AstNodeType::METHOD_DEFINITION: { - return "METHOD_DEF " + ToStringHelper(ast->AsMethodDefinition()->Value()); - } - case ir::AstNodeType::ETS_TYPE_REFERENCE_PART: { - return "TYPE_REF_PART " + ToStringHelper(ast->AsETSTypeReferencePart()->Name()); - } - case ir::AstNodeType::ETS_TYPE_REFERENCE: { - return "TYPE_REF " + ToStringHelper(ast->AsETSTypeReference()->Part()); - } - case ir::AstNodeType::VARIABLE_DECLARATOR: { - return "VAR_DECLARATOR " + ToStringHelper(ast->AsVariableDeclarator()->Id()); - } - case ir::AstNodeType::VARIABLE_DECLARATION: { - if (ast->AsVariableDeclaration()->Declarators().empty()) { - return "VAR_DECLARATION "; - } - return "VAR_DECLARATION " + ToStringHelper(ast->AsVariableDeclaration()->Declarators().at(0)); - } - case ir::AstNodeType::CALL_EXPRESSION: { - return "CALL_EXPR " + ToStringHelper(ast->AsCallExpression()->Callee()) + "(...)"; - } - case ir::AstNodeType::EXPRESSION_STATEMENT: { - return "EXPR_STMT " + ToStringHelper(ast->AsExpressionStatement()->GetExpression()); - } - case ir::AstNodeType::MEMBER_EXPRESSION: { - auto const *me = ast->AsMemberExpression(); - return "MEMBER_EXPR " + ToStringHelper(me->Object()) + "." + ToStringHelper(me->Property()); - } - case ir::AstNodeType::CLASS_STATIC_BLOCK: { - return "CLS_STATIC_BLOCK " + ToStringHelper(ast->AsClassStaticBlock()->Function()); - } - case ir::AstNodeType::ETS_PACKAGE_DECLARATION: { - return "PKG_DECL "; - } - case ir::AstNodeType::TS_TYPE_PARAMETER_DECLARATION: { - auto params = ast->AsTSTypeParameterDeclaration()->Params(); - return "PARAM_DECL " + ToStringParamsHelper(ast->Parent(), params); - } - case ir::AstNodeType::TS_TYPE_PARAMETER: { - return "TYPE_PARAM " + ToStringHelper(ast->AsTSTypeParameter()->Name()); - } - case ir::AstNodeType::TS_TYPE_PARAMETER_INSTANTIATION: { - return "PARAM_INSTANTIATION " + - ToStringParamsHelper(ast->Parent(), ast->AsTSTypeParameterInstantiation()->Params()); - } - case ir::AstNodeType::THROW_STATEMENT: { - return "THROW_STMT " + ToStringHelper(ast->AsThrowStatement()->Argument()); - } - case ir::AstNodeType::ETS_NEW_CLASS_INSTANCE_EXPRESSION: { - return "NEW_CLS_INSTANCE " + ToStringHelper(ast->AsETSNewClassInstanceExpression()->GetTypeRef()); - } - case ir::AstNodeType::STRING_LITERAL: { - return "STR_LITERAL " + ToStringHelper(ast->AsStringLiteral()->Str()); - } - case ir::AstNodeType::TRY_STATEMENT: { - return "TRY_STMT " + ToStringHelper(ast->AsTryStatement()->Block()); - } - case ir::AstNodeType::CATCH_CLAUSE: { - return "CATCH_CLAUSE "; - } - case ir::AstNodeType::NUMBER_LITERAL: { - return "NUMBER_LITERAL " + ToStringHelper(ast->AsNumberLiteral()->Str()); - } - case ir::AstNodeType::ETS_PARAMETER_EXPRESSION: { - return "ETS_PARAM_EXPR " + ToStringHelper(ast->AsETSParameterExpression()->Ident()); - } - case ir::AstNodeType::TS_INTERFACE_DECLARATION: { - return "TS_INTERFACE_DECL " + ToStringHelper(ast->AsTSInterfaceDeclaration()->Id()); - } - case ir::AstNodeType::TS_INTERFACE_BODY: { - return "TS_INTERFACE_BODY "; - } - case ir::AstNodeType::ETS_FUNCTION_TYPE: { - return "ETS_FUNC_TYPE " + - ToStringParamsHelper(ast->Parent(), ast->AsETSFunctionType()->Params()); - } - case ir::AstNodeType::TS_CLASS_IMPLEMENTS: { - return "TS_CLASS_IMPL " + ToStringHelper(ast->AsTSClassImplements()->Expr()); - } - default: { - return "MUST BE UNREACHABLE"; - } - } -} - bool ASTVerifier::HasParent(const ir::AstNode *ast) { if (ast == nullptr) { @@ -309,7 +71,7 @@ bool ASTVerifier::HasParent(const ir::AstNode *ast) } if (ast->Parent() == nullptr) { - error_messages_.push_back("NULL_PARENT: " + ToStringHelper(ast)); + error_messages_.push_back("NULL_PARENT: " + ast->DumpJSON()); return false; } @@ -334,7 +96,7 @@ bool ASTVerifier::HasType(const ir::AstNode *ast) } if (ast->IsTyped() && static_cast(ast)->TsType() == nullptr) { - error_messages_.push_back("NULL_TS_TYPE: " + ToStringHelper(ast)); + error_messages_.push_back("NULL_TS_TYPE: " + ast->DumpJSON()); return false; } return true; @@ -361,7 +123,7 @@ bool ASTVerifier::HasVariable(const ir::AstNode *ast) return true; } - error_messages_.push_back("NULL_VARIABLE: " + ToStringHelper(ast->AsIdentifier())); + error_messages_.push_back("NULL_VARIABLE: " + ast->AsIdentifier()->DumpJSON()); return false; } @@ -385,10 +147,11 @@ bool ASTVerifier::HasScope(const ir::AstNode *ast) if (!ast->IsIdentifier()) { return true; // we will check only Identifier } + // we will check only local variables of identifiers - if (HasVariable(ast) && ast->AsIdentifier()->Variable()->IsLocalVariable() && - ast->AsIdentifier()->Variable()->AsLocalVariable()->GetScope() == nullptr) { - error_messages_.push_back("NULL_SCOPE_LOCAL_VAR: " + ToStringHelper(ast)); + auto const *variable = ast->AsIdentifier()->Variable(); + if (!HasVariable(ast) || (variable->IsLocalVariable() && variable->AsLocalVariable()->GetScope() == nullptr)) { + error_messages_.push_back("NULL_SCOPE_LOCAL_VAR: " + ast->DumpJSON()); return false; } // NOTE(tatiana): Add check that the scope enclose this identifier @@ -406,4 +169,80 @@ bool ASTVerifier::HaveScopes(const ir::AstNode *ast) return has_scope; } +bool ASTVerifier::IsForLoopCorrectInitialized(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + if (ast->IsForInStatement()) { + auto const *left = ast->AsForInStatement()->Left(); + if (left == nullptr) { + error_messages_.push_back("NULL FOR-IN-LEFT: " + ast->DumpJSON()); + return false; + } + + if (!left->IsIdentifier() && !left->IsVariableDeclaration()) { + error_messages_.push_back("INCORRECT FOR-IN-LEFT: " + ast->DumpJSON()); + return false; + } + } + + if (ast->IsForOfStatement()) { + auto const *left = ast->AsForOfStatement()->Left(); + if (left == nullptr) { + error_messages_.push_back("NULL FOR-OF-LEFT: " + ast->DumpJSON()); + return false; + } + + if (!left->IsIdentifier() && !left->IsVariableDeclaration()) { + error_messages_.push_back("INCORRECT FOR-OF-LEFT: " + ast->DumpJSON()); + return false; + } + } + + if (ast->IsForUpdateStatement()) { + // The most important part of for-loop is the test. + // But it also can be null. Then there must be break;(return) in the body. + auto const *test = ast->AsForUpdateStatement()->Test(); + if (test == nullptr) { + auto const *body = ast->AsForUpdateStatement()->Body(); + if (body == nullptr) { + error_messages_.push_back("NULL FOR-TEST AND FOR-BODY: " + ast->DumpJSON()); + return false; + } + bool has_exit = body->IsBreakStatement() || body->IsReturnStatement(); + body->IterateRecursively([&has_exit](ir::AstNode *child) { + has_exit |= child->IsBreakStatement() || child->IsReturnStatement(); + }); + if (!has_exit) { + // an infinite loop + error_messages_.push_back("WARNING: NULL FOR-TEST AND FOR-BODY doesn't exit: " + ast->DumpJSON()); + } + return true; + } + + if (test->IsExpression()) { + auto const *var = test->AsExpression(); + if (var == nullptr) { + error_messages_.push_back("NULL FOR VAR: " + ast->DumpJSON()); + return false; + } + } + } + return true; +} + +bool ASTVerifier::AreForLoopsCorrectInitialized(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + bool is_for_initialized = IsForLoopCorrectInitialized(ast); + ast->IterateRecursively( + [this, &is_for_initialized](ir::AstNode *child) { is_for_initialized &= IsForLoopCorrectInitialized(child); }); + return is_for_initialized; +} + } // namespace panda::es2panda::compiler diff --git a/ets2panda/compiler/core/ASTVerifier.h b/ets2panda/compiler/core/ASTVerifier.h index d5cbb7f2d5923998392a6a02d4cc482d1424d013..605f8bdc211b8c7ae3416384b90959ea1c1f63d2 100644 --- a/ets2panda/compiler/core/ASTVerifier.h +++ b/ets2panda/compiler/core/ASTVerifier.h @@ -38,6 +38,8 @@ public: bool HasVariable(const ir::AstNode *ast); bool HasScope(const ir::AstNode *ast); bool HaveScopes(const ir::AstNode *ast); + bool IsForLoopCorrectInitialized(const ir::AstNode *ast); + bool AreForLoopsCorrectInitialized(const ir::AstNode *ast); ErrorMessages GetErrorMessages() { @@ -48,8 +50,6 @@ private: ErrorMessages error_messages_; }; -std::string ToStringHelper(const ir::AstNode *ast); - } // namespace panda::es2panda::compiler #endif // ES2PANDA_COMPILER_CORE_ASTVERIFIER_H diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index f3b2ff83a6ee0aba42f56ab2082eb0483dc834ac..b1c52b9cf79d1c7f0ab21c67aedc669456490eea 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -61,10 +61,6 @@ bool Phase::Apply(CompilerContext *ctx, parser::Program *program) } #ifndef NDEBUG - ASTVerifier ast_before; - if (!ast_before.IsCorrectProgram(program)) { - // NOTE(tatiana): Add some error processing - } if (!Precondition(ctx, program)) { ctx->Checker()->ThrowTypeError({"Precondition check failed for ", util::StringView {Name()}}, lexer::SourcePosition {}); diff --git a/ets2panda/test/public/ast_verifier_test.cpp b/ets2panda/test/public/ast_verifier_test.cpp index dd36a1937988cfe8ea1ae3622f6bfae857a57f81..5320b6a286a1fa24bba9ae39dcf49d5f0484db24 100644 --- a/ets2panda/test/public/ast_verifier_test.cpp +++ b/ets2panda/test/public/ast_verifier_test.cpp @@ -42,7 +42,7 @@ TEST_F(ASTVerifierTest, NullParent) ASSERT_EQ(has_parent, false); ASSERT_NE(messages.size(), 0); - ASSERT_EQ(messages[0], "NULL_PARENT: STR_LITERAL "); + ASSERT_EQ(messages[0], "NULL_PARENT: {\n \"type\": \"StringLiteral\",\n \"value\": \"\"\n}"); } TEST_F(ASTVerifierTest, NullType) @@ -55,7 +55,7 @@ TEST_F(ASTVerifierTest, NullType) ASSERT_EQ(has_type, false); ASSERT_NE(messages.size(), 0); - ASSERT_EQ(messages[0], "NULL_TS_TYPE: STR_LITERAL "); + ASSERT_EQ(messages[0], "NULL_TS_TYPE: {\n \"type\": \"StringLiteral\",\n \"value\": \"\"\n}"); } TEST_F(ASTVerifierTest, WithoutScope)