/* This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "rust-parse.h" #include "rust-linemap.h" #include "rust-diagnostics.h" #include "rust-token.h" #include "rust-attribute-values.h" namespace Rust { std::string extract_module_path (const AST::AttrVec &inner_attrs, const AST::AttrVec &outer_attrs, const std::string &name) { AST::Attribute path_attr = AST::Attribute::create_empty (); for (const auto &attr : inner_attrs) { if (attr.get_path ().as_string () == Values::Attributes::PATH) { path_attr = attr; break; } } // Here, we found a path attribute, but it has no associated string. This is // invalid if (!path_attr.is_empty () && !path_attr.has_attr_input ()) { rust_error_at ( path_attr.get_locus (), // Split the format string so that -Wformat-diag does not complain... "path attributes must contain a filename: '%s'", "#![path = \"file\"]"); return name; } for (const auto &attr : outer_attrs) { if (attr.get_path ().as_string () == Values::Attributes::PATH) { path_attr = attr; break; } } // We didn't find a path attribute. This is not an error, there simply isn't // one present if (path_attr.is_empty ()) return name; // Here, we found a path attribute, but it has no associated string. This is // invalid if (!path_attr.has_attr_input ()) { rust_error_at ( path_attr.get_locus (), // Split the format string so that -Wformat-diag does not complain... "path attributes must contain a filename: '%s'", "#[path = \"file\"]"); return name; } auto path_value = path_attr.get_attr_input ().as_string (); // At this point, the 'path' is of the following format: '= ""' // We need to remove the equal sign and only keep the actual filename. // In order to do this, we can simply go through the string until we find // a character that is not an equal sign or whitespace auto filename_begin = path_value.find_first_not_of ("=\t "); auto path = path_value.substr (filename_begin); // On windows, the path might mix '/' and '\' separators. Replace the // UNIX-like separators by MSDOS separators to make sure the path will resolve // properly. // // Source: rustc compiler // (https://github.com/rust-lang/rust/blob/9863bf51a52b8e61bcad312f81b5193d53099f9f/compiler/rustc_expand/src/module.rs#L174) #if defined(HAVE_DOS_BASED_FILE_SYSTEM) path.replace ('/', '\\'); #endif /* HAVE_DOS_BASED_FILE_SYSTEM */ return path; } template static bool contains (std::vector &vec, T elm) { return std::find (vec.begin (), vec.end (), elm) != vec.end (); } /** * Avoid UB by calling .front() and .back() on empty containers... */ template static const T * get_back_ptr (const std::vector> &values) { if (values.empty ()) return nullptr; return values.back ().get (); } template static const T * get_front_ptr (const std::vector> &values) { if (values.empty ()) return nullptr; return values.front ().get (); } static bool peculiar_fragment_match_compatible_fragment ( const AST::MacroFragSpec &last_spec, const AST::MacroFragSpec &spec, location_t match_locus) { static std::unordered_map> fragment_follow_set = {{AST::MacroFragSpec::PATH, {AST::MacroFragSpec::BLOCK}}, {AST::MacroFragSpec::TY, {AST::MacroFragSpec::BLOCK}}, {AST::MacroFragSpec::VIS, {AST::MacroFragSpec::IDENT, AST::MacroFragSpec::TY, AST::MacroFragSpec::PATH}}}; auto is_valid = contains (fragment_follow_set[last_spec.get_kind ()], spec.get_kind ()); if (!is_valid) rust_error_at ( match_locus, "fragment specifier %<%s%> is not allowed after %<%s%> fragments", spec.as_string ().c_str (), last_spec.as_string ().c_str ()); return is_valid; } static bool peculiar_fragment_match_compatible (const AST::MacroMatchFragment &last_match, const AST::MacroMatch &match) { static std::unordered_map> follow_set = {{AST::MacroFragSpec::EXPR, {MATCH_ARROW, COMMA, SEMICOLON}}, {AST::MacroFragSpec::STMT, {MATCH_ARROW, COMMA, SEMICOLON}}, {AST::MacroFragSpec::PAT, {MATCH_ARROW, COMMA, EQUAL, PIPE, IF, IN}}, {AST::MacroFragSpec::PATH, {MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE, RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}}, {AST::MacroFragSpec::TY, {MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE, RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}}, {AST::MacroFragSpec::VIS, {COMMA, IDENTIFIER, LEFT_PAREN, LEFT_SQUARE, EXCLAM, ASTERISK, AMP, LOGICAL_AND, QUESTION_MARK, LIFETIME, LEFT_ANGLE, LEFT_SHIFT, UNDERSCORE, ABSTRACT, AS, ASYNC, AUTO, BECOME, BOX, BREAK, CONST, CONTINUE, CRATE, DO, DYN, ELSE, ENUM_KW, EXTERN_KW, FALSE_LITERAL, FINAL_KW, FN_KW, FOR, IF, IMPL, IN, LET, LOOP, MACRO, MATCH_KW, MOD, MOVE, MUT, OVERRIDE_KW, PUB, REF, RETURN_KW, SELF_ALIAS, SELF, STATIC_KW, STRUCT_KW, SUPER, TRAIT, TRUE_LITERAL, TRY, TYPE, TYPEOF, UNSAFE, UNSIZED, USE, VIRTUAL, WHERE, WHILE, YIELD}}}; location_t error_locus = match.get_match_locus (); std::string kind_str = "fragment"; auto &allowed_toks = follow_set[last_match.get_frag_spec ().get_kind ()]; // There are two behaviors to handle here: If the follow-up match is a token, // we want to check if it is allowed. // If it is a fragment, repetition or matcher then we know that it will be // an error. // For repetitions and matchers we want to extract a proper location to report // the error. switch (match.get_macro_match_type ()) { case AST::MacroMatch::Tok: { auto tok = static_cast (&match); if (contains (allowed_toks, tok->get_id ())) return true; kind_str = "token `" + std::string (get_token_description (tok->get_id ())) + "`"; error_locus = tok->get_match_locus (); break; } break; case AST::MacroMatch::Repetition: { auto repetition = static_cast (&match); auto &matches = repetition->get_matches (); auto first_frag = get_front_ptr (matches); if (first_frag) return peculiar_fragment_match_compatible (last_match, *first_frag); break; } case AST::MacroMatch::Matcher: { auto matcher = static_cast (&match); auto first_token = matcher->get_delim_type (); TokenId delim_id; switch (first_token) { case AST::PARENS: delim_id = LEFT_PAREN; break; case AST::SQUARE: delim_id = LEFT_SQUARE; break; case AST::CURLY: delim_id = LEFT_CURLY; break; default: rust_unreachable (); break; } if (contains (allowed_toks, delim_id)) return true; kind_str = "token `" + std::string (get_token_description (delim_id)) + "` at start of matcher"; error_locus = matcher->get_match_locus (); break; } case AST::MacroMatch::Fragment: { auto last_spec = last_match.get_frag_spec (); auto fragment = static_cast (&match); if (last_spec.has_follow_set_fragment_restrictions ()) return peculiar_fragment_match_compatible_fragment ( last_spec, fragment->get_frag_spec (), match.get_match_locus ()); } break; } rust_error_at (error_locus, "%s is not allowed after %<%s%> fragment", kind_str.c_str (), last_match.get_frag_spec ().as_string ().c_str ()); auto allowed_toks_str = "`" + std::string (get_token_description (allowed_toks[0])) + "`"; for (size_t i = 1; i < allowed_toks.size (); i++) allowed_toks_str += ", `" + std::string (get_token_description (allowed_toks[i])) + "`"; rust_inform (error_locus, "allowed tokens are %s", allowed_toks_str.c_str ()); return false; } bool is_match_compatible (const AST::MacroMatch &last_match, const AST::MacroMatch &match) { const AST::MacroMatch *new_last = nullptr; // We want to "extract" the concerning matches. In cases such as matchers and // repetitions, we actually store multiple matchers, but are only concerned // about the follow-set ambiguities of certain elements. // There are some cases where we can short-circuit the algorithm: There will // never be restrictions on token literals, or on certain fragments which do // not have a set of follow-restrictions. switch (last_match.get_macro_match_type ()) { // This is our main stop condition: When we are finally looking at the // last match (or its actual last component), and it is a fragment, it // may contain some follow up restrictions. case AST::MacroMatch::Fragment: { auto fragment = static_cast (&last_match); if (fragment->get_frag_spec ().has_follow_set_restrictions ()) return peculiar_fragment_match_compatible (*fragment, match); else return true; } case AST::MacroMatch::Repetition: { // A repetition on the left hand side means we want to make sure the // last match of the repetition is compatible with the new match auto repetition = static_cast (&last_match); new_last = get_back_ptr (repetition->get_matches ()); // If there are no matches in the matcher, then it can be followed by // anything if (!new_last) return true; break; } case AST::MacroMatch::Matcher: case AST::MacroMatch::Tok: return true; } rust_assert (new_last); // We check recursively until we find a terminating condition // FIXME: Does expansion depth/limit matter here? return is_match_compatible (*new_last, match); } } // namespace Rust