/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . */ #include "tolk.h" #include "platform-utils.h" #include "compiler-state.h" #include "src-file.h" #include "generics-helpers.h" #include "ast.h" #include "ast-visitor.h" #include "type-system.h" #include /* * This pipe resolves identifiers (local variables and types) in all functions bodies. * It happens before type inferring, but after all global symbols are registered. * It means, that for any symbol `x` we can look up whether it's a global name or not. * * About resolving variables. * Example: `var x = 10; x = 20;` both `x` point to one LocalVarData. * Example: `x = 20` undefined symbol `x` is also here (unless it's a global) * Variables scoping and redeclaration are also here. * Note, that `x` is stored as `ast_reference (ast_identifier "x")`. More formally, "references" are resolved. * "Reference" in AST, besides the identifier, stores optional generics instantiation. `x` is grammar-valid. * * About resolving types. At the moment of parsing, `int`, `cell` and other predefined are parsed as TypeDataInt, etc. * All the others are stored as TypeDataUnresolved, to be resolved here, after global symtable is filled. * Example: `var x: T = 0` unresolved "T" is replaced by TypeDataGenericT inside `f`. * Example: `f()` unresolved "MyAlias" is replaced by TypeDataAlias inside the reference. * Example: `fun f(): KKK` unresolved "KKK" fires an error "unknown type name". * When structures and type aliases are implemented, their resolving will also be done here. * See finalize_type_data(). * * Note, that functions/methods binding is NOT here. * In other words, for ast_function_call `beginCell()` and `t.tupleAt(0)`, their fun_ref is NOT filled here. * Functions/methods binding is done later, simultaneously with type inferring and generics instantiation. * For instance, to call a generic function `t.tuplePush(1)`, we need types of `t` and `1` to be inferred, * as well as `tuplePush` to be instantiated, and fun_ref to point at that exact instantiations. * * As a result of this step, * * every V::sym is filled, pointing either to a local var/parameter, or to a global symbol * (exceptional for function calls and methods, their references are bound later) * * all TypeData in all symbols is ready for analyzing, TypeDataUnresolved won't occur later in pipeline */ namespace tolk { GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_undefined_symbol(V v) { if (v->name == "self") { v->error("using `self` in a non-member function (it does not accept the first `self` parameter)"); } else { v->error("undefined symbol `" + static_cast(v->name) + "`"); } } GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_unknown_type_name(SrcLocation loc, const std::string &text) { throw ParseError(loc, "unknown type name `" + text + "`"); } static void check_import_exists_when_using_sym(AnyV v_usage, const Symbol* used_sym) { SrcLocation sym_loc = used_sym->loc; if (!v_usage->loc.is_symbol_from_same_or_builtin_file(sym_loc)) { const SrcFile* declared_in = sym_loc.get_src_file(); bool has_import = false; for (const SrcFile::ImportDirective& import : v_usage->loc.get_src_file()->imports) { if (import.imported_file == declared_in) { has_import = true; } } if (!has_import) { v_usage->error("Using a non-imported symbol `" + used_sym->name + "`. Forgot to import \"" + declared_in->rel_filename + "\"?"); } } } struct NameAndScopeResolver { std::vector> scopes; static uint64_t key_hash(std::string_view name_key) { return std::hash{}(name_key); } void open_scope([[maybe_unused]] SrcLocation loc) { // std::cerr << "open_scope " << scopes.size() + 1 << " at " << loc << std::endl; scopes.emplace_back(); } void close_scope([[maybe_unused]] SrcLocation loc) { // std::cerr << "close_scope " << scopes.size() << " at " << loc << std::endl; if (UNLIKELY(scopes.empty())) { throw Fatal{"cannot close the outer scope"}; } scopes.pop_back(); } const Symbol* lookup_symbol(std::string_view name) const { uint64_t key = key_hash(name); for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) { // NOLINT(*-loop-convert) const auto& scope = *it; if (auto it_sym = scope.find(key); it_sym != scope.end()) { return it_sym->second; } } return G.symtable.lookup(name); } void add_local_var(const LocalVarData* v_sym) { if (UNLIKELY(scopes.empty())) { throw Fatal("unexpected scope_level = 0"); } if (v_sym->name.empty()) { // underscore return; } uint64_t key = key_hash(v_sym->name); const auto& [_, inserted] = scopes.rbegin()->emplace(key, v_sym); if (UNLIKELY(!inserted)) { throw ParseError(v_sym->loc, "redeclaration of local variable `" + v_sym->name + "`"); } } }; struct TypeDataResolver { GNU_ATTRIBUTE_NOINLINE static TypePtr resolve_identifiers_in_type_data(TypePtr type_data, const GenericsDeclaration* genericTs) { return type_data->replace_children_custom([genericTs](TypePtr child) { if (const TypeDataUnresolved* un = child->try_as()) { if (genericTs && genericTs->has_nameT(un->text)) { std::string nameT = un->text; return TypeDataGenericT::create(std::move(nameT)); } if (un->text == "auto") { throw ParseError(un->loc, "`auto` type does not exist; just omit a type for local variable (will be inferred from assignment); parameters should always be typed"); } if (un->text == "self") { throw ParseError(un->loc, "`self` type can be used only as a return type of a function (enforcing it to be chainable)"); } fire_error_unknown_type_name(un->loc, un->text); } return child; }); } }; static TypePtr finalize_type_data(TypePtr type_data, const GenericsDeclaration* genericTs) { if (!type_data || !type_data->has_unresolved_inside()) { return type_data; } return TypeDataResolver::resolve_identifiers_in_type_data(type_data, genericTs); } class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { // more correctly this field shouldn't be static, but currently there is no need to make it a part of state static NameAndScopeResolver current_scope; static const FunctionData* current_function; static const LocalVarData* create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) { LocalVarData* v_sym = new LocalVarData(static_cast(name), loc, declared_type, immutable * LocalVarData::flagImmutable, -1); current_scope.add_local_var(v_sym); return v_sym; } static void process_catch_variable(AnyExprV catch_var) { if (auto v_ref = catch_var->try_as()) { const LocalVarData* var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true); v_ref->mutate()->assign_sym(var_ref); } } protected: void visit(V v) override { if (v->marked_as_redef) { const Symbol* sym = current_scope.lookup_symbol(v->get_name()); if (sym == nullptr) { v->error("`redef` for unknown variable"); } const LocalVarData* var_ref = sym->try_as(); if (!var_ref) { v->error("`redef` for unknown variable"); } v->mutate()->assign_var_ref(var_ref); } else { TypePtr declared_type = finalize_type_data(v->declared_type, current_function->genericTs); const LocalVarData* var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable); v->mutate()->assign_resolved_type(declared_type); v->mutate()->assign_var_ref(var_ref); } } void visit(V v) override { parent::visit(v->get_rhs()); // in this order, so that `var x = x` is invalid, "x" on the right unknown parent::visit(v->get_lhs()); } void visit(V v) override { const Symbol* sym = current_scope.lookup_symbol(v->get_name()); if (!sym) { fire_error_undefined_symbol(v->get_identifier()); } v->mutate()->assign_sym(sym); // for global functions, global vars and constants, `import` must exist if (!sym->try_as()) { check_import_exists_when_using_sym(v, sym); } // for `f` / `f`, resolve "MyAlias" and "T" // (for function call `f()`, this v (ast_reference `f`) is callee) if (auto v_instantiationTs = v->get_instantiationTs()) { for (int i = 0; i < v_instantiationTs->size(); ++i) { TypePtr substituted_type = finalize_type_data(v_instantiationTs->get_item(i)->substituted_type, current_function->genericTs); v_instantiationTs->get_item(i)->mutate()->assign_resolved_type(substituted_type); } } } void visit(V v) override { // for `t.tupleAt` / `obj.method`, resolve "MyAlias" and "T" // (for function call `t.tupleAt()`, this v (ast_dot_access `t.tupleAt`) is callee) if (auto v_instantiationTs = v->get_instantiationTs()) { for (int i = 0; i < v_instantiationTs->size(); ++i) { TypePtr substituted_type = finalize_type_data(v_instantiationTs->get_item(i)->substituted_type, current_function->genericTs); v_instantiationTs->get_item(i)->mutate()->assign_resolved_type(substituted_type); } } parent::visit(v->get_obj()); } void visit(V v) override { TypePtr cast_to_type = finalize_type_data(v->cast_to_type, current_function->genericTs); v->mutate()->assign_resolved_type(cast_to_type); parent::visit(v->get_expr()); } void visit(V v) override { if (v->empty()) { return; } current_scope.open_scope(v->loc); parent::visit(v); current_scope.close_scope(v->loc_end); } void visit(V v) override { current_scope.open_scope(v->loc); parent::visit(v->get_body()); parent::visit(v->get_cond()); // in 'while' condition it's ok to use variables declared inside do current_scope.close_scope(v->get_body()->loc_end); } void visit(V v) override { visit(v->get_try_body()); current_scope.open_scope(v->get_catch_body()->loc); const std::vector& catch_items = v->get_catch_expr()->get_items(); tolk_assert(catch_items.size() == 2); process_catch_variable(catch_items[1]); process_catch_variable(catch_items[0]); parent::visit(v->get_catch_body()); current_scope.close_scope(v->get_catch_body()->loc_end); } public: bool should_visit_function(const FunctionData* fun_ref) override { // this pipe is done just after parsing // visit both asm and code functions, resolve identifiers in parameter/return types everywhere // for generic functions, unresolved "T" will be replaced by TypeDataGenericT return true; } void start_visiting_function(const FunctionData* fun_ref, V v) override { current_function = fun_ref; for (int i = 0; i < v->get_num_params(); ++i) { const LocalVarData& param_var = fun_ref->parameters[i]; TypePtr declared_type = finalize_type_data(param_var.declared_type, fun_ref->genericTs); v->get_param(i)->mutate()->assign_param_ref(¶m_var); v->get_param(i)->mutate()->assign_resolved_type(declared_type); param_var.mutate()->assign_resolved_type(declared_type); } TypePtr return_type = finalize_type_data(fun_ref->declared_return_type, fun_ref->genericTs); v->mutate()->assign_resolved_type(return_type); fun_ref->mutate()->assign_resolved_type(return_type); if (fun_ref->is_code_function()) { auto v_seq = v->get_body()->as(); current_scope.open_scope(v->loc); for (int i = 0; i < v->get_num_params(); ++i) { current_scope.add_local_var(&fun_ref->parameters[i]); } parent::visit(v_seq); current_scope.close_scope(v_seq->loc_end); tolk_assert(current_scope.scopes.empty()); } current_function = nullptr; } }; NameAndScopeResolver AssignSymInsideFunctionVisitor::current_scope; const FunctionData* AssignSymInsideFunctionVisitor::current_function = nullptr; void pipeline_resolve_identifiers_and_assign_symbols() { AssignSymInsideFunctionVisitor visitor; for (const SrcFile* file : G.all_src_files) { for (AnyV v : file->ast->as()->get_toplevel_declarations()) { if (auto v_func = v->try_as()) { tolk_assert(v_func->fun_ref); visitor.start_visiting_function(v_func->fun_ref, v_func); } else if (auto v_global = v->try_as()) { TypePtr declared_type = finalize_type_data(v_global->var_ref->declared_type, nullptr); v_global->mutate()->assign_resolved_type(declared_type); v_global->var_ref->mutate()->assign_resolved_type(declared_type); } else if (auto v_const = v->try_as(); v_const && v_const->declared_type) { TypePtr declared_type = finalize_type_data(v_const->const_ref->declared_type, nullptr); v_const->mutate()->assign_resolved_type(declared_type); v_const->const_ref->mutate()->assign_resolved_type(declared_type); } } } } void pipeline_resolve_identifiers_and_assign_symbols(const FunctionData* fun_ref) { AssignSymInsideFunctionVisitor visitor; if (visitor.should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); } } } // namespace tolk