From 8bb1d8a98beb9f8b4e1ef139c619bbdd9c66ba96 Mon Sep 17 00:00:00 2001 From: Tatiana Date: Thu, 12 Oct 2023 16:36:22 +0300 Subject: [PATCH 1/3] Add AST Verifier feature Signed-off-by: Tatiana --- BUILD.gn | 1 + CMakeLists.txt | 1 + compiler/core/ASTVerifier.cpp | 409 ++++++++++++++++++++++++++++++ compiler/core/ASTVerifier.h | 55 ++++ compiler/lowering/phase.cpp | 9 + ir/astNode.h | 10 + test/CMakeLists.txt | 12 + test/public/ast_verifier_test.cpp | 71 ++++++ 8 files changed, 568 insertions(+) create mode 100644 compiler/core/ASTVerifier.cpp create mode 100644 compiler/core/ASTVerifier.h create mode 100644 test/public/ast_verifier_test.cpp diff --git a/BUILD.gn b/BUILD.gn index 99b4a26ef..8663df872 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -138,6 +138,7 @@ libes2panda_sources = [ "compiler/core/JSCompiler.cpp", "compiler/core/JSemitter.cpp", "compiler/core/codeGen.cpp", + "compiler/core/ASTVerifier.cpp", "compiler/core/compileJob.cpp", "compiler/core/compileQueue.cpp", "compiler/core/compilerContext.cpp", diff --git a/CMakeLists.txt b/CMakeLists.txt index e49880048..b34882d04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ set(ES2PANDA_LIB_SRC compiler/base/literals.cpp compiler/base/lreference.cpp compiler/base/optionalChain.cpp + compiler/core/ASTVerifier.cpp compiler/core/codeGen.cpp compiler/core/compileJob.cpp compiler/core/compileQueue.cpp diff --git a/compiler/core/ASTVerifier.cpp b/compiler/core/ASTVerifier.cpp new file mode 100644 index 000000000..f59438c30 --- /dev/null +++ b/compiler/core/ASTVerifier.cpp @@ -0,0 +1,409 @@ +/** + * Copyright (c) 2021-2023 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 "ASTVerifier.h" + +#include "es2panda.h" +#include "binder/variableFlags.h" +#include "binder/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/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 { + +bool ASTVerifier::IsCorrectProgram(const parser::Program *program) +{ + bool is_correct = true; + error_messages_.clear(); + + for (auto *statement : program->Ast()->Statements()) { + is_correct &= HaveParents(statement); + } + is_correct &= HaveParents(program->GlobalClass()); + + for (auto *statement : program->Ast()->Statements()) { + is_correct &= HaveTypes(statement); + } + is_correct &= HaveTypes(program->GlobalClass()); + + for (auto *statement : program->Ast()->Statements()) { + is_correct &= HaveVariables(statement); + } + is_correct &= HaveVariables(program->GlobalClass()); + + for (auto *statement : program->Ast()->Statements()) { + is_correct &= HaveScopes(statement); + } + is_correct &= HaveScopes(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 binder::ScopeType type) +{ + switch (type) { + case binder::ScopeType::CATCH: { + return "CATCH"; + } + case binder::ScopeType::CATCH_PARAM: { + return "CATCH_PARAM"; + } + case binder::ScopeType::CLASS: { + return "CLASS"; + } + case binder::ScopeType::FUNCTION: { + return "FUNCTION"; + } + case binder::ScopeType::FUNCTION_PARAM: { + return "FUNCTION_PARAM"; + } + case binder::ScopeType::GLOBAL: { + return "GLOBAL"; + } + case binder::ScopeType::LOCAL: { + return "LOCAL"; + } + case binder::ScopeType::LOOP: { + return "LOOP"; + } + case binder::ScopeType::LOOP_DECL: { + return "LOOP_DECL"; + } + case binder::ScopeType::MODULE: { + return "MODULE"; + } + case binder::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 binder::Scope *scope) +{ + if (scope == nullptr) { + return ""; + } + + switch (scope->Type()) { + case binder::ScopeType::FUNCTION: { + return "FUNC_SCOPE " + ToStringHelper(scope->AsFunctionScope()->Name()); + } + case binder::ScopeType::LOCAL: { + return "LOCAL_SCOPE "; + } + case binder::ScopeType::CATCH: { + return "CATCH_SCOPE "; + } + default: { + return "MUST BE UNREACHABLE"; + } + } +} + +std::string ToStringHelper(const binder::Variable *var) +{ + if (var == nullptr) { + return ""; + } + + switch (var->Type()) { + case binder::VariableType::LOCAL: { + return "LOCAL_VAR " + ToStringHelper(var->Name()); + } + case binder::VariableType::MODULE: { + return "MODULE_VAR " + ToStringHelper(var->Name()); + } + case binder::VariableType::GLOBAL: { + return "GLOBAL_VAR " + ToStringHelper(var->Name()); + } + case binder::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: { + return "PARAM_DECL " + ToStringParamsHelper( + ast->Parent(), ast->AsTSTypeParameterDeclaration()->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) { + return false; + } + + if (ast->Parent() == nullptr) { + error_messages_.push_back("NULL_PARENT: " + ToStringHelper(ast)); + return false; + } + + return true; +} + +bool ASTVerifier::HaveParents(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + bool has_parent = HasParent(ast); + ast->IterateRecursively([this, &has_parent](ir::AstNode *child) { has_parent &= HasParent(child); }); + return has_parent; +} + +bool ASTVerifier::HasType(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + if (ast->IsTyped() && static_cast(ast)->TsType() == nullptr) { + error_messages_.push_back("NULL_TS_TYPE: " + ToStringHelper(ast)); + return false; + } + return true; +} + +bool ASTVerifier::HaveTypes(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + bool has_type = HasType(ast); + ast->IterateRecursively([this, &has_type](ir::AstNode *child) { has_type &= HasType(child); }); + return has_type; +} + +bool ASTVerifier::HasVariable(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + if (!ast->IsIdentifier() || ast->AsIdentifier()->Variable() != nullptr) { + return true; + } + + error_messages_.push_back("NULL_VARIABLE: " + ToStringHelper(ast->AsIdentifier())); + return false; +} + +bool ASTVerifier::HaveVariables(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + bool has_variable = HasVariable(ast); + ast->IterateRecursively([this, &has_variable](ir::AstNode *child) { has_variable &= HasVariable(child); }); + return has_variable; +} + +bool ASTVerifier::HasScope(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + 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)); + return false; + } + // TODO(tatiana): Add check that the scope enclose this identifier + return true; +} + +bool ASTVerifier::HaveScopes(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } + + bool has_scope = HasScope(ast); + ast->IterateRecursively([this, &has_scope](ir::AstNode *child) { has_scope &= HasScope(child); }); + return has_scope; +} + +} // namespace panda::es2panda::compiler diff --git a/compiler/core/ASTVerifier.h b/compiler/core/ASTVerifier.h new file mode 100644 index 000000000..d9bdba118 --- /dev/null +++ b/compiler/core/ASTVerifier.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021-2022 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. + */ + +#ifndef ES2PANDA_COMPILER_CORE_ASTVERIFIER_H +#define ES2PANDA_COMPILER_CORE_ASTVERIFIER_H + +#include "parser/program/program.h" + +namespace panda::es2panda::compiler { + +class ASTVerifier { +public: + using ErrorMessages = std::vector; + NO_COPY_SEMANTIC(ASTVerifier); + NO_MOVE_SEMANTIC(ASTVerifier); + + ASTVerifier() = default; + ~ASTVerifier() = default; + + bool IsCorrectProgram(const parser::Program *program); + bool HaveParents(const ir::AstNode *ast); + bool HasParent(const ir::AstNode *ast); + bool HaveTypes(const ir::AstNode *ast); + bool HasType(const ir::AstNode *ast); + bool HaveVariables(const ir::AstNode *ast); + bool HasVariable(const ir::AstNode *ast); + bool HasScope(const ir::AstNode *ast); + bool HaveScopes(const ir::AstNode *ast); + + ErrorMessages GetErrorMessages() + { + return error_messages_; + } + +private: + ErrorMessages error_messages_; +}; + +std::string ToStringHelper(const ir::AstNode *ast); + +} // namespace panda::es2panda::compiler + +#endif // ES2PANDA_COMPILER_CORE_ASTVERIFIER_H diff --git a/compiler/lowering/phase.cpp b/compiler/lowering/phase.cpp index 5877c6e49..0b7c30057 100644 --- a/compiler/lowering/phase.cpp +++ b/compiler/lowering/phase.cpp @@ -15,6 +15,7 @@ #include "phase.h" #include "checker/checker.h" +#include "compiler/core/ASTVerifier.h" #include "compiler/core/compilerContext.h" #include "lexer/token/sourceLocation.h" #include "compiler/lowering/checkerPhase.h" @@ -57,6 +58,10 @@ bool Phase::Apply(CompilerContext *ctx, parser::Program *program) } #ifndef NDEBUG + ASTVerifier ast_before; + if (!ast_before.IsCorrectProgram(program)) { + // TODO(tatiana): Add some error processing + } if (!Precondition(ctx, program)) { ctx->Checker()->ThrowTypeError({"Precondition check failed for ", Name()}, lexer::SourcePosition {}); } @@ -72,6 +77,10 @@ bool Phase::Apply(CompilerContext *ctx, parser::Program *program) } #ifndef NDEBUG + ASTVerifier ast_after; + if (!ast_after.IsCorrectProgram(program)) { + // TODO(tatiana): Add some error processing + } if (!Postcondition(ctx, program)) { ctx->Checker()->ThrowTypeError({"Postcondition check failed for ", Name()}, lexer::SourcePosition {}); } diff --git a/ir/astNode.h b/ir/astNode.h index b9558663a..5accd1e72 100644 --- a/ir/astNode.h +++ b/ir/astNode.h @@ -227,6 +227,11 @@ public: return false; } + virtual bool IsTyped() const + { + return false; + } + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DECLARE_AS_CASTS(nodeType, className) \ className *As##className() \ @@ -587,6 +592,11 @@ public: ts_type_ = ts_type; } + bool IsTyped() const override + { + return true; + } + protected: explicit Typed(AstNodeType const type) : T(type) {} explicit Typed(AstNodeType const type, ModifierFlags const flags) : T(type, flags) {} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f40aed24b..ff14df7c4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -92,5 +92,17 @@ if(PANDA_WITH_ETS) add_dependencies(ets_tests es2panda_tests) endif() + panda_add_gtest( + NAME es2panda_astverifier_tests + SOURCES + public/ast_verifier_test.cpp + LIBRARIES + es2panda-lib + INCLUDE_DIRS + ${ES2PANDA_PATH} + SANITIZERS + ${PANDA_SANITIZERS_LIST} + ) + add_subdirectory(tsconfig) endif() diff --git a/test/public/ast_verifier_test.cpp b/test/public/ast_verifier_test.cpp new file mode 100644 index 000000000..dd36a1937 --- /dev/null +++ b/test/public/ast_verifier_test.cpp @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2021-2023 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 +#include +#include "macros.h" + +#include "compiler/core/ASTVerifier.h" +#include "ir/astDump.h" +#include "ir/expressions/literals/stringLiteral.h" + +class ASTVerifierTest : public testing::Test { +public: + ASTVerifierTest() = default; + ~ASTVerifierTest() override = default; + + NO_COPY_SEMANTIC(ASTVerifierTest); + NO_MOVE_SEMANTIC(ASTVerifierTest); + +private: +}; + +TEST_F(ASTVerifierTest, NullParent) +{ + panda::es2panda::compiler::ASTVerifier verifier {}; + panda::es2panda::ir::StringLiteral empty_node; + + bool has_parent = verifier.HasParent(&empty_node); + auto messages = verifier.GetErrorMessages(); + + ASSERT_EQ(has_parent, false); + ASSERT_NE(messages.size(), 0); + ASSERT_EQ(messages[0], "NULL_PARENT: STR_LITERAL "); +} + +TEST_F(ASTVerifierTest, NullType) +{ + panda::es2panda::compiler::ASTVerifier verifier {}; + panda::es2panda::ir::StringLiteral empty_node; + + bool has_type = verifier.HasType(&empty_node); + auto messages = verifier.GetErrorMessages(); + + ASSERT_EQ(has_type, false); + ASSERT_NE(messages.size(), 0); + ASSERT_EQ(messages[0], "NULL_TS_TYPE: STR_LITERAL "); +} + +TEST_F(ASTVerifierTest, WithoutScope) +{ + panda::es2panda::compiler::ASTVerifier verifier {}; + panda::es2panda::ir::StringLiteral empty_node; + + bool has_scope = verifier.HasScope(&empty_node); + auto messages = verifier.GetErrorMessages(); + + ASSERT_EQ(has_scope, true); + ASSERT_EQ(messages.size(), 0); +} -- Gitee From 7cb76c51440da041b8d42b03ad5e96fe1f77c618 Mon Sep 17 00:00:00 2001 From: Vadim Lomovtsev Date: Tue, 24 Oct 2023 10:57:57 +0300 Subject: [PATCH 2/3] Fix processing of chain operator while accessing class member function. In order to inform the ETS codegen about optional functin call it is required to set the 'optional_' field to true while processing the call expression at the 'parser/ETSparser.cpp' file. Also, it was found that for optional call to class member function the detected chain expression flag is not count (while for member field it is processed correctly) by the ETS codegen. This commitis to provide the info with respect to detected chain expression operand to subsequent parsing of function call while calling ParseCallExpression() and update the bytecode generation for class member function call with respect to actual value of the 'optional_' field. Signed-off-by: Vadim Lomovtsev --- ir/expressions/callExpression.cpp | 27 ++++++++++--------- parser/ETSparser.cpp | 13 +++++++-- ...onal_chaining_nested_property-expected.txt | 4 +-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ir/expressions/callExpression.cpp b/ir/expressions/callExpression.cpp index 715e07065..f466998fb 100644 --- a/ir/expressions/callExpression.cpp +++ b/ir/expressions/callExpression.cpp @@ -328,21 +328,22 @@ void CallExpression::Compile(compiler::ETSGen *etsg) const if (signature_->ReturnType() != TsType()) { etsg->ApplyConversion(this, TsType()); } - } else if (!is_reference && callee_->IsIdentifier()) { - if (!is_static) { - etsg->LoadThis(this); - etsg->StoreAccumulator(this, callee_reg); - } - emit_arguments(); - } else if (!is_reference && callee_->IsMemberExpression()) { - if (!is_static) { - callee_->AsMemberExpression()->Object()->Compile(etsg); + } else { + if (!is_reference && callee_->IsIdentifier()) { + if (!is_static) { + etsg->LoadThis(this); + etsg->StoreAccumulator(this, callee_reg); + } + } else if (!is_reference && callee_->IsMemberExpression()) { + if (!is_static) { + callee_->AsMemberExpression()->Object()->Compile(etsg); + etsg->StoreAccumulator(this, callee_reg); + } + } else { + callee_->Compile(etsg); etsg->StoreAccumulator(this, callee_reg); } - emit_arguments(); - } else { - callee_->Compile(etsg); - etsg->StoreAccumulator(this, callee_reg); + if (optional_) { compiler::Label *end_label = etsg->AllocLabel(); etsg->BranchIfNull(this, end_label); diff --git a/parser/ETSparser.cpp b/parser/ETSparser.cpp index bccc01c60..6bf90e205 100644 --- a/parser/ETSparser.cpp +++ b/parser/ETSparser.cpp @@ -3709,7 +3709,15 @@ ir::Expression *ETSParser::ParsePostPrimaryExpression(ir::Expression *primary_ex [[maybe_unused]] bool *is_chain_expression) { ir::Expression *return_expression = primary_expr; - + // NOTE: why we don't set the is_chain_expression here? + // For ETS we process it internally later, by passing specific flag, and so + // we don't need to set is_chain_expression as it would trigger the + // 'ChainExpression' class alloc at the caller (ParseMemberExpression()), + // which in turn generate irrelevant byte-code. + // + // From the other hand, we need to inform consequent call to ParseCallExpression + // to let it properly process optional call (by '?.') to class member function. + bool is_optional_expression = false; while (true) { switch (Lexer()->GetToken().Type()) { case lexer::TokenType::PUNCTUATOR_QUESTION_DOT: { @@ -3725,6 +3733,7 @@ ir::Expression *ETSParser::ParsePostPrimaryExpression(ir::Expression *primary_ex continue; } + is_optional_expression = true; return_expression = ParsePropertyAccess(return_expression, true); continue; } @@ -3752,7 +3761,7 @@ ir::Expression *ETSParser::ParsePostPrimaryExpression(ir::Expression *primary_ex break; } - return_expression = ParseCallExpression(return_expression, false, false); + return_expression = ParseCallExpression(return_expression, is_optional_expression, false); continue; } case lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK: { diff --git a/test/parser/ets/optional_chaining_nested_property-expected.txt b/test/parser/ets/optional_chaining_nested_property-expected.txt index 8aa877a56..b8f942f31 100644 --- a/test/parser/ets/optional_chaining_nested_property-expected.txt +++ b/test/parser/ets/optional_chaining_nested_property-expected.txt @@ -639,7 +639,7 @@ } }, "arguments": [], - "optional": false, + "optional": true, "loc": { "start": { "line": 27, @@ -892,7 +892,7 @@ } }, "arguments": [], - "optional": false, + "optional": true, "loc": { "start": { "line": 27, -- Gitee From f381d86bd11e0ab9a9f310bde0c8910c84ff3039 Mon Sep 17 00:00:00 2001 From: Vadim Lomovtsev Date: Wed, 25 Oct 2023 17:23:16 +0300 Subject: [PATCH 3/3] [SQUASHME] make changes only for IsMemberExpression() branch Signed-off-by: Vadim Lomovtsev --- ir/expressions/callExpression.cpp | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/ir/expressions/callExpression.cpp b/ir/expressions/callExpression.cpp index f466998fb..116941b6a 100644 --- a/ir/expressions/callExpression.cpp +++ b/ir/expressions/callExpression.cpp @@ -328,22 +328,28 @@ void CallExpression::Compile(compiler::ETSGen *etsg) const if (signature_->ReturnType() != TsType()) { etsg->ApplyConversion(this, TsType()); } - } else { - if (!is_reference && callee_->IsIdentifier()) { - if (!is_static) { - etsg->LoadThis(this); - etsg->StoreAccumulator(this, callee_reg); - } - } else if (!is_reference && callee_->IsMemberExpression()) { - if (!is_static) { - callee_->AsMemberExpression()->Object()->Compile(etsg); - etsg->StoreAccumulator(this, callee_reg); - } - } else { - callee_->Compile(etsg); + } else if (!is_reference && callee_->IsIdentifier()) { + if (!is_static) { + etsg->LoadThis(this); etsg->StoreAccumulator(this, callee_reg); } - + emit_arguments(); + } else if (!is_reference && callee_->IsMemberExpression()) { + if (!is_static) { + callee_->AsMemberExpression()->Object()->Compile(etsg); + etsg->StoreAccumulator(this, callee_reg); + } + if (optional_) { + compiler::Label *end_label = etsg->AllocLabel(); + etsg->BranchIfNull(this, end_label); + emit_arguments(); + etsg->SetLabel(this, end_label); + } else { + emit_arguments(); + } + } else { + callee_->Compile(etsg); + etsg->StoreAccumulator(this, callee_reg); if (optional_) { compiler::Label *end_label = etsg->AllocLabel(); etsg->BranchIfNull(this, end_label); -- Gitee