Branch data Line data Source code
1 : : // Copyright (C) 2020-2025 Free Software Foundation, Inc.
2 : :
3 : : // This file is part of GCC.
4 : :
5 : : // GCC is free software; you can redistribute it and/or modify it under
6 : : // the terms of the GNU General Public License as published by the Free
7 : : // Software Foundation; either version 3, or (at your option) any later
8 : : // version.
9 : :
10 : : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 : : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : : // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 : : // for more details.
14 : :
15 : : // You should have received a copy of the GNU General Public License
16 : : // along with GCC; see the file COPYING3. If not see
17 : : // <http://www.gnu.org/licenses/>.
18 : :
19 : : #include "rust-macro-substitute-ctx.h"
20 : : #include "input.h"
21 : : #include "rust-hir-map.h"
22 : : #include "rust-token.h"
23 : :
24 : : namespace Rust {
25 : :
26 : : bool
27 : 2 : SubstituteCtx::substitute_dollar_crate (
28 : : std::vector<std::unique_ptr<AST::Token>> &expanded)
29 : : {
30 : 2 : auto &mappings = Analysis::Mappings::get ();
31 : :
32 : 2 : auto def_crate = mappings.lookup_macro_def_crate (definition.get_node_id ());
33 : 2 : auto current_crate = mappings.get_current_crate ();
34 : :
35 : 2 : rust_assert (def_crate);
36 : :
37 : : // If we're expanding a macro defined in the current crate which uses $crate,
38 : : // we can just replace the metavar with the `crate` path segment. Otherwise,
39 : : // use the fully qualified extern-crate lookup path `::<crate_name>`
40 : 2 : if (*def_crate == current_crate)
41 : : {
42 : 4 : expanded.push_back (std::make_unique<AST::Token> (
43 : 4 : Rust::Token::make_identifier (UNKNOWN_LOCATION, "crate")));
44 : : }
45 : : else
46 : : {
47 : 0 : auto name = mappings.get_crate_name (*def_crate);
48 : :
49 : 0 : rust_assert (name);
50 : :
51 : 0 : expanded.push_back (std::make_unique<AST::Token> (
52 : 0 : Rust::Token::make (SCOPE_RESOLUTION, UNKNOWN_LOCATION)));
53 : 0 : expanded.push_back (std::make_unique<AST::Token> (
54 : 0 : Rust::Token::make_identifier (UNKNOWN_LOCATION, std::string (*name))));
55 : : }
56 : :
57 : 2 : return true;
58 : : }
59 : :
60 : : bool
61 : 12282 : SubstituteCtx::substitute_metavar (
62 : : std::unique_ptr<AST::Token> &metavar,
63 : : std::vector<std::unique_ptr<AST::Token>> &expanded)
64 : : {
65 : 12282 : auto metavar_name = metavar->get_str ();
66 : :
67 : 12282 : auto it = fragments.find (metavar_name);
68 : 12282 : if (it == fragments.end ())
69 : : {
70 : : // fail to substitute, unless we are dealing with a special-case metavar
71 : : // like $crate
72 : :
73 : 4 : if (metavar->get_id () == CRATE)
74 : 2 : return substitute_dollar_crate (expanded);
75 : :
76 : 2 : expanded.push_back (metavar->clone_token ());
77 : :
78 : 2 : return false;
79 : : }
80 : : else
81 : : {
82 : : // If we are expanding a metavar which has a lof of matches, we are
83 : : // currently expanding a repetition metavar - not a simple metavar. We
84 : : // need to error out and inform the user.
85 : : // Associated test case for an example: compile/macro-issue1224.rs
86 : 12278 : if (!it->second->is_single_fragment ())
87 : : {
88 : 2 : rust_error_at (metavar->get_locus (),
89 : : "metavariable is still repeating at this depth");
90 : 2 : rust_inform (
91 : : metavar->get_locus (),
92 : : "you probably forgot the repetition operator: %<%s%s%s%>", "$(",
93 : 2 : metavar->as_string ().c_str (), ")*");
94 : 2 : return true;
95 : : }
96 : :
97 : : // We only care about the vector when expanding repetitions.
98 : : // Just access the first element of the vector.
99 : 12276 : auto &frag = it->second->get_single_fragment ();
100 : 27498 : for (size_t offs = frag.token_offset_begin; offs < frag.token_offset_end;
101 : : offs++)
102 : : {
103 : 15222 : auto &tok = input.at (offs);
104 : 15222 : expanded.push_back (tok->clone_token ());
105 : : }
106 : : }
107 : :
108 : : return true;
109 : 12282 : }
110 : :
111 : : bool
112 : 2740 : SubstituteCtx::check_repetition_amount (size_t pattern_start,
113 : : size_t pattern_end,
114 : : size_t &expected_repetition_amount)
115 : : {
116 : 2740 : bool first_fragment_found = false;
117 : 2740 : bool is_valid = true;
118 : :
119 : 29007 : for (size_t i = pattern_start; i < pattern_end; i++)
120 : : {
121 : 26267 : if (macro.at (i)->get_id () == DOLLAR_SIGN)
122 : : {
123 : 3276 : auto &frag_token = macro.at (i + 1);
124 : 3276 : if (token_id_is_keyword (frag_token->get_id ())
125 : 3276 : || frag_token->get_id () == IDENTIFIER)
126 : : {
127 : 3188 : auto it = fragments.find (frag_token->get_str ());
128 : 3188 : if (it == fragments.end ())
129 : : {
130 : : // If the repetition is not anything we know (ie no declared
131 : : // metavars, or metavars which aren't present in the
132 : : // fragment), we can just error out. No need to paste the
133 : : // tokens as if nothing had happened.
134 : 0 : rust_error_at (frag_token->get_locus (),
135 : : "metavar %s used in repetition does not exist",
136 : 0 : frag_token->get_str ().c_str ());
137 : :
138 : 0 : is_valid = false;
139 : : }
140 : :
141 : 3188 : auto &fragment = *it->second;
142 : :
143 : 3188 : if (!fragment.is_single_fragment ())
144 : : {
145 : 3184 : auto &fragment_rep
146 : : = static_cast<MatchedFragmentContainerRepetition &> (
147 : : fragment);
148 : 3184 : size_t repeat_amount = fragment_rep.get_match_amount ();
149 : 3184 : if (!first_fragment_found)
150 : : {
151 : 2740 : first_fragment_found = true;
152 : 2740 : expected_repetition_amount = repeat_amount;
153 : : }
154 : : else
155 : : {
156 : 444 : if (repeat_amount != expected_repetition_amount)
157 : : {
158 : 4 : rust_error_at (
159 : : frag_token->get_locus (),
160 : : "different amount of matches used in merged "
161 : : "repetitions: expected %lu, got %lu",
162 : : (unsigned long) expected_repetition_amount,
163 : : (unsigned long) repeat_amount);
164 : 4 : is_valid = false;
165 : : }
166 : : }
167 : : }
168 : : }
169 : : }
170 : : }
171 : :
172 : 2740 : return is_valid && first_fragment_found;
173 : : }
174 : :
175 : : std::vector<std::unique_ptr<AST::Token>>
176 : 2740 : SubstituteCtx::substitute_repetition (
177 : : size_t pattern_start, size_t pattern_end,
178 : : std::unique_ptr<AST::Token> separator_token)
179 : : {
180 : 2740 : rust_assert (pattern_end < macro.size ());
181 : :
182 : 2740 : size_t repeat_amount = 0;
183 : 2740 : if (!check_repetition_amount (pattern_start, pattern_end, repeat_amount))
184 : 4 : return {};
185 : :
186 : 2736 : rust_debug ("repetition amount to use: %lu", (unsigned long) repeat_amount);
187 : 2736 : std::vector<std::unique_ptr<AST::Token>> expanded;
188 : 2736 : std::vector<std::unique_ptr<AST::Token>> new_macro;
189 : :
190 : : // We want to generate a "new macro" to substitute with. This new macro
191 : : // should contain only the tokens inside the pattern
192 : 28955 : for (size_t tok_idx = pattern_start; tok_idx < pattern_end; tok_idx++)
193 : 26219 : new_macro.emplace_back (macro.at (tok_idx)->clone_token ());
194 : :
195 : : // Then, we want to create a subset of the matches so that
196 : : // `substitute_tokens()` can only see one fragment per metavar. Let's say we
197 : : // have the following user input: (1 145 'h')
198 : : // on the following match arm: ($($lit:literal)*)
199 : : // which causes the following matches: { "lit": [1, 145, 'h'] }
200 : : //
201 : : // The pattern (new_macro) is `$lit:literal`
202 : : // The first time we expand it, we want $lit to have the following token: 1
203 : : // The second time, 145
204 : : // The third and final time, 'h'
205 : : //
206 : : // In order to do so we must create "sub maps", which only contain parts of
207 : : // the original matches
208 : : // sub-maps: [ { "lit": 1 }, { "lit": 145 }, { "lit": 'h' } ]
209 : : //
210 : : // and give them to `substitute_tokens` one by one.
211 : :
212 : 8115 : for (size_t i = 0; i < repeat_amount; i++)
213 : : {
214 : 5379 : std::map<std::string, MatchedFragmentContainer *> sub_map;
215 : 14050 : for (auto &kv_match : fragments)
216 : : {
217 : 8671 : if (kv_match.second->is_single_fragment ())
218 : 2845 : sub_map.emplace (kv_match.first, kv_match.second);
219 : : // Hack: A repeating meta variable might not be present in the new
220 : : // macro. Don't include this match if the fragment doesn't have enough
221 : : // items, as check_repetition_amount should prevent repetition amount
222 : : // mismatches anyway.
223 : 5826 : else if (kv_match.second->get_fragments ().size () > i)
224 : 5804 : sub_map.emplace (kv_match.first,
225 : 5804 : kv_match.second->get_fragments ().at (i).get ());
226 : : }
227 : :
228 : 5379 : auto substitute_context
229 : 5379 : = SubstituteCtx (input, new_macro, sub_map, definition);
230 : 5379 : auto new_tokens = substitute_context.substitute_tokens ();
231 : :
232 : : // Skip the first repetition, but add the separator to the expanded
233 : : // tokens if it is present
234 : 5379 : if (i != 0 && separator_token)
235 : 173 : expanded.emplace_back (separator_token->clone_token ());
236 : :
237 : 106149 : for (auto &new_token : new_tokens)
238 : 100770 : expanded.emplace_back (new_token->clone_token ());
239 : 5379 : }
240 : :
241 : : // FIXME: We also need to make sure that all subsequent fragments
242 : : // contain the same amount of repetitions as the first one
243 : :
244 : 2736 : return expanded;
245 : 2736 : }
246 : :
247 : : static bool
248 : 2740 : is_rep_op (std::unique_ptr<AST::Token> &tok)
249 : : {
250 : 2740 : auto id = tok->get_id ();
251 : 2740 : return id == QUESTION_MARK || id == ASTERISK || id == PLUS;
252 : : }
253 : :
254 : : std::pair<std::vector<std::unique_ptr<AST::Token>>, size_t>
255 : 15024 : SubstituteCtx::substitute_token (size_t token_idx)
256 : : {
257 : 15024 : auto &token = macro.at (token_idx);
258 : :
259 : 15024 : switch (token->get_id ())
260 : : {
261 : 6 : default:
262 : 6 : if (token_id_is_keyword (token->get_id ()))
263 : : {
264 : 12282 : case IDENTIFIER:
265 : 12284 : std::vector<std::unique_ptr<AST::Token>> expanded;
266 : :
267 : 12282 : rust_debug ("expanding metavar: %s", token->get_str ().c_str ());
268 : :
269 : 12282 : if (substitute_metavar (token, expanded))
270 : 12280 : return {std::move (expanded), 2};
271 : : }
272 : :
273 : : // don't substitute, dollar sign is alone/metavar is unknown
274 : 4 : return {std::vector<std::unique_ptr<AST::Token>> (), 0};
275 : :
276 : 2740 : case LEFT_PAREN: {
277 : : // We need to parse up until the closing delimiter and expand this
278 : : // fragment->n times.
279 : 2740 : rust_debug ("expanding repetition");
280 : :
281 : : // We're in a context where macro repetitions have already been
282 : : // parsed and validated: This means that
283 : : // 1/ There will be no delimiters as that is an error
284 : : // 2/ There are no fragment specifiers anymore, which prevents us
285 : : // from reusing parser functions.
286 : : //
287 : : // Repetition patterns are also special in that they cannot contain
288 : : // "rogue" delimiters: For example, this is invalid, as they are
289 : : // parsed as MacroMatches and must contain a correct amount of
290 : : // delimiters.
291 : : // `$($e:expr ) )`
292 : : // ^ rogue closing parenthesis
293 : : //
294 : : // With all of that in mind, we can simply skip ahead from one
295 : : // parenthesis to the other to find the pattern to expand. Of course,
296 : : // pairs of delimiters, including parentheses, are allowed.
297 : : // `$($e:expr ( ) )`
298 : : // Parentheses are the sole delimiter for which we need a special
299 : : // behavior since they delimit the repetition pattern
300 : :
301 : 2740 : size_t pattern_start = token_idx + 1;
302 : 2740 : size_t pattern_end = pattern_start;
303 : 2740 : auto parentheses_stack = 0;
304 : 29007 : for (size_t idx = pattern_start; idx < macro.size (); idx++)
305 : : {
306 : 29007 : if (macro.at (idx)->get_id () == LEFT_PAREN)
307 : : {
308 : 1499 : parentheses_stack++;
309 : : }
310 : 27508 : else if (macro.at (idx)->get_id () == RIGHT_PAREN)
311 : : {
312 : 4239 : if (parentheses_stack == 0)
313 : : {
314 : : pattern_end = idx;
315 : : break;
316 : : }
317 : 1499 : parentheses_stack--;
318 : : }
319 : : }
320 : :
321 : : // Unreachable case, but let's make sure we don't ever run into it
322 : 2740 : rust_assert (pattern_end != pattern_start);
323 : :
324 : 2740 : std::unique_ptr<AST::Token> separator_token = nullptr;
325 : 2740 : if (pattern_end + 1 <= macro.size ())
326 : : {
327 : 2740 : auto &post_pattern_token = macro.at (pattern_end + 1);
328 : 2740 : if (!is_rep_op (post_pattern_token))
329 : 227 : separator_token = post_pattern_token->clone_token ();
330 : : }
331 : :
332 : : // Amount of tokens to skip
333 : 2740 : auto to_skip = 0;
334 : : // Parentheses
335 : 2740 : to_skip += 2;
336 : : // Repetition operator
337 : 2740 : to_skip += 1;
338 : : // Separator
339 : 2740 : if (separator_token)
340 : 227 : to_skip += 1;
341 : :
342 : 2740 : return {substitute_repetition (pattern_start, pattern_end,
343 : : std::move (separator_token)),
344 : 2740 : pattern_end - pattern_start + to_skip + 1};
345 : 2740 : }
346 : : }
347 : :
348 : : rust_unreachable ();
349 : : }
350 : :
351 : : std::vector<std::unique_ptr<AST::Token>>
352 : 8847 : SubstituteCtx::substitute_tokens ()
353 : : {
354 : 8847 : std::vector<std::unique_ptr<AST::Token>> replaced_tokens;
355 : 8847 : rust_debug ("expanding tokens");
356 : :
357 : 150866 : for (size_t i = 0; i < macro.size ();)
358 : : {
359 : 142019 : auto &tok = macro.at (i);
360 : 142019 : if (tok->get_id () == DOLLAR_SIGN)
361 : : {
362 : : // Aaaaah, if only we had C++17 :)
363 : : // auto [expanded, tok_to_skip] = ...
364 : 15024 : auto p = substitute_token (i + 1);
365 : 15024 : auto expanded = std::move (p.first);
366 : 15024 : auto tok_to_skip = p.second;
367 : :
368 : 15024 : if (!tok_to_skip)
369 : : {
370 : 4 : replaced_tokens.emplace_back (tok->clone_token ());
371 : 4 : tok_to_skip++;
372 : : }
373 : :
374 : 15024 : i += tok_to_skip;
375 : :
376 : 131191 : for (auto &token : expanded)
377 : 116167 : replaced_tokens.emplace_back (token->clone_token ());
378 : 15024 : }
379 : : else
380 : : {
381 : 126995 : replaced_tokens.emplace_back (tok->clone_token ());
382 : 126995 : i++;
383 : : }
384 : : }
385 : :
386 : 8847 : return replaced_tokens;
387 : : }
388 : :
389 : : } // namespace Rust
|