From 74bf0b95ae79c0ae446ca9174068830d1a1f3164 Mon Sep 17 00:00:00 2001 From: Sam De Roeck Date: Thu, 13 Feb 2025 01:11:52 -0800 Subject: [PATCH] AST support for include aliasing Summary: Support include aliases in the thrift AST. Include aliasing takes the form of: ```thrift include "a/b/c/my_program.thrift" as my_alias struct Foo { 1: my_alias.Bar my_field; } ``` Where each program will only allow resolution of scope aliases defined locally within the program. Reviewed By: iahs Differential Revision: D68747918 fbshipit-source-id: 5b8d021f5e6d1d5a3b8c9ca754a51ccd778d9172 --- .../src/thrift/compiler/ast/t_include.h | 14 +- .../src/thrift/compiler/parse/parse_ast.cc | 171 +++++++++++------- .../src/thrift/compiler/parse/parser.cc | 2 +- .../thrift/src/thrift/compiler/parse/parser.h | 5 +- .../thrift/src/thrift/compiler/parse/token.cc | 3 +- .../thrift/src/thrift/compiler/parse/token.h | 1 + 6 files changed, 125 insertions(+), 71 deletions(-) diff --git a/third-party/thrift/src/thrift/compiler/ast/t_include.h b/third-party/thrift/src/thrift/compiler/ast/t_include.h index cf8880297c766c..78b0645e3f2b94 100644 --- a/third-party/thrift/src/thrift/compiler/ast/t_include.h +++ b/third-party/thrift/src/thrift/compiler/ast/t_include.h @@ -28,12 +28,19 @@ class t_program; */ class t_include : public t_node { public: - t_include(t_program* program, std::string raw_path) - : program_(program), raw_path_(std::move(raw_path)) {} + t_include( + t_program* program, + std::string raw_path, + std::optional alias) + : program_(program), + raw_path_(std::move(raw_path)), + alias_(std::move(alias)) {} t_program* get_program() const { return program_; } - fmt::string_view raw_path() const { return raw_path_; } + std::string_view raw_path() const { return raw_path_; } + + std::optional alias() const { return alias_; } void set_str_range(source_range rng) { range_ = rng; } source_range str_range() const { return range_; } @@ -41,6 +48,7 @@ class t_include : public t_node { private: t_program* program_; std::string raw_path_; + std::optional alias_; source_range range_; }; diff --git a/third-party/thrift/src/thrift/compiler/parse/parse_ast.cc b/third-party/thrift/src/thrift/compiler/parse/parse_ast.cc index 237b8da761a6fc..654dd896deb7fc 100644 --- a/third-party/thrift/src/thrift/compiler/parse/parse_ast.cc +++ b/third-party/thrift/src/thrift/compiler/parse/parse_ast.cc @@ -199,7 +199,10 @@ class parsing_terminator : public std::runtime_error { } using include_handler = std::function; + source_range range, + const std::string& include_name, + const std::optional& alias, + const t_program& p)>; // A semantic analyzer and AST builder for a single Thrift program. class ast_builder : public parser_actions { @@ -505,14 +508,20 @@ class ast_builder : public parser_actions { void on_include( source_range range, std::string_view str, + const std::optional& alias, source_range str_range) override { std::string include_name = fmt::to_string(str); - auto included_program = on_include_(range, include_name, program_); + std::optional alias_str_opt = alias.has_value() + ? std::make_optional(std::string{alias.value()}) + : std::nullopt; + auto included_program = + on_include_(range, include_name, alias_str_opt, program_); auto last_slash = include_name.find_last_of("/\\"); if (last_slash != std::string::npos) { included_program->set_include_prefix(include_name.substr(0, last_slash)); } - auto include = std::make_unique(included_program, include_name); + auto include = std::make_unique( + included_program, std::move(include_name), std::move(alias_str_opt)); include->set_src_range(range); include->set_str_range(str_range); program_.add_include(std::move(include)); @@ -1014,73 +1023,105 @@ std::unique_ptr parse_ast( auto circular_deps = std::set{path}; - include_handler on_include = [&](source_range range, - const std::string& include_path, - const t_program& parent) { - auto path_or_error = sm.find_include_file( - include_path, parent.path(), params.incl_searchpath); - if (path_or_error.index() == 1) { - diags.report( - range.begin, - params.allow_missing_includes ? diagnostic_level::warning - : diagnostic_level::error, - "{}", - std::get<1>(path_or_error)); - if (!params.allow_missing_includes) { - end_parsing(); - } - } + include_handler on_include = + [&](source_range range, + const std::string& include_path, + const std::optional& include_alias, + const t_program& parent) { + auto path_or_error = sm.find_include_file( + include_path, parent.path(), params.incl_searchpath); + if (path_or_error.index() == 1) { + diags.report( + range.begin, + params.allow_missing_includes ? diagnostic_level::warning + : diagnostic_level::error, + "{}", + std::get<1>(path_or_error)); + if (!params.allow_missing_includes) { + end_parsing(); + } + } - // Skip already parsed files. - t_program* program = nullptr; - const std::string* resolved_path = &include_path; - const std::string* full_path = resolved_path; - if (path_or_error.index() == 0) { - full_path = &std::get<0>(path_or_error); - program = programs->find_program_by_full_path(*full_path); - if (program) { - // We've already seen this program but know it by another path. - resolved_path = &program->path(); - } - } - if (program) { - if (program == programs->get_root_program()) { - // If we're including the root program we must have a dependency cycle. - assert(circular_deps.count(*full_path)); - } else { - return program; - } - } + // Resolve the include path to a full path. + t_program* program = nullptr; + const std::string* resolved_path = &include_path; + const std::string* full_path = resolved_path; + if (path_or_error.index() == 0) { + full_path = &std::get<0>(path_or_error); + program = programs->find_program_by_full_path(*full_path); + if (program) { + // We've already seen this program but know it by another path. + resolved_path = &program->path(); + } + } - // Fail on circular dependencies. - if (!circular_deps.insert(*full_path).second) { - diags.error( - range.begin, - "Circular dependency found: file `{}` is already parsed.", - *resolved_path); - end_parsing(); - } + // Fail on duplicate include aliases + if (include_alias.has_value()) { + const auto& knownIncludes = parent.includes(); + for (const t_include* inc : knownIncludes) { + if (!inc->alias()) { + continue; + } + + if (inc->alias() == include_alias.value()) { + diags.error( + range.begin, + "'{}' is already an alias for '{}'", + *include_alias, + inc->get_program()->full_path()); + } + + if (*full_path == inc->get_program()->full_path()) { + diags.error( + range.begin, + "Include '{}' has multiple aliases: '{}' vs '{}'", + *full_path, + inc->alias().value(), + *include_alias); + } + } + } - // Create a new program for a Thrift file in an include statement and - // set its include_prefix by parsing the directory which it is - // included from. - auto included_program = - std::make_unique(*resolved_path, *full_path, &parent); - program = included_program.get(); - programs->add_program(std::move(included_program)); + // Skip already parsed files. + if (program) { + if (program == programs->get_root_program()) { + // If we're including the root program we must have a dependency + // cycle. + assert(circular_deps.count(*full_path)); + } else { + return program; + } + } - try { - ast_builder(diags, *program, params, on_include) - .parse_file(sm, range.begin); - } catch (...) { - if (!params.allow_missing_includes) { - throw; - } - } + // Fail on circular dependencies. + if (!circular_deps.insert(*full_path).second) { + diags.error( + range.begin, + "Circular dependency found: file `{}` is already parsed.", + *resolved_path); + end_parsing(); + } - circular_deps.erase(*full_path); - return program; - }; + // Create a new program for a Thrift file in an include statement and + // set its include_prefix by parsing the directory which it is + // included from. + auto included_program = + std::make_unique(*resolved_path, *full_path, &parent); + program = included_program.get(); + programs->add_program(std::move(included_program)); + + try { + ast_builder(diags, *program, params, on_include) + .parse_file(sm, range.begin); + } catch (...) { + if (!params.allow_missing_includes) { + throw; + } + } + + circular_deps.erase(*full_path); + return program; + }; t_program& root_program = *programs->root_program(); try { diff --git a/third-party/thrift/src/thrift/compiler/parse/parser.cc b/third-party/thrift/src/thrift/compiler/parse/parser.cc index dce73f92d5bc87..d3c645cd272646 100644 --- a/third-party/thrift/src/thrift/compiler/parse/parser.cc +++ b/third-party/thrift/src/thrift/compiler/parse/parser.cc @@ -175,7 +175,7 @@ class parser { consume_token(); switch (kind) { case tok::kw_include: - actions_.on_include(range, str, str_range); + actions_.on_include(range, str, std::nullopt, str_range); break; case tok::kw_cpp_include: actions_.on_cpp_include(range, str); diff --git a/third-party/thrift/src/thrift/compiler/parse/parser.h b/third-party/thrift/src/thrift/compiler/parse/parser.h index d98d1a46a129ee..6d16f80c7cdb26 100644 --- a/third-party/thrift/src/thrift/compiler/parse/parser.h +++ b/third-party/thrift/src/thrift/compiler/parse/parser.h @@ -89,7 +89,10 @@ class parser_actions { std::string_view name) = 0; virtual void on_include( - source_range range, std::string_view str, source_range str_range) = 0; + source_range range, + std::string_view str, + const std::optional& alias, + source_range str_range) = 0; virtual void on_cpp_include(source_range range, std::string_view str) = 0; virtual void on_hs_include(source_range range, std::string_view str) = 0; diff --git a/third-party/thrift/src/thrift/compiler/parse/token.cc b/third-party/thrift/src/thrift/compiler/parse/token.cc index 126407e53144eb..dbaf3181a8e0bb 100644 --- a/third-party/thrift/src/thrift/compiler/parse/token.cc +++ b/third-party/thrift/src/thrift/compiler/parse/token.cc @@ -114,7 +114,8 @@ constexpr token_kind_info info[] = { THRIFT_KEYWORD(struct), THRIFT_KEYWORD(throws), THRIFT_KEYWORD(typedef), - THRIFT_KEYWORD(union)}; + THRIFT_KEYWORD(union), + THRIFT_KEYWORD(as)}; constexpr int num_token_kinds = sizeof(info) / sizeof(*info); diff --git a/third-party/thrift/src/thrift/compiler/parse/token.h b/third-party/thrift/src/thrift/compiler/parse/token.h index daa3cca0dbde33..9bd9c5716a5a8e 100644 --- a/third-party/thrift/src/thrift/compiler/parse/token.h +++ b/third-party/thrift/src/thrift/compiler/parse/token.h @@ -107,6 +107,7 @@ enum class tok { kw_throws, kw_typedef, kw_union, + kw_as, }; namespace detail {