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 : 11334 : SubstituteCtx::substitute_metavar (
62 : : std::unique_ptr<AST::Token> &metavar,
63 : : std::vector<std::unique_ptr<AST::Token>> &expanded)
64 : : {
65 : 11334 : auto metavar_name = metavar->get_str ();
66 : :
67 : 11334 : auto it = fragments.find (metavar_name);
68 : 11334 : if (it == fragments.end ())
69 : : {
70 : : // fail to substitute, unless we are dealing with a special-case metavar
71 : : // like $crate
72 : :
73 : 3 : if (metavar->get_id () == CRATE)
74 : 2 : return substitute_dollar_crate (expanded);
75 : :
76 : 1 : expanded.push_back (metavar->clone_token ());
77 : :
78 : 1 : 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 : 11331 : if (!it->second->is_single_fragment ())
87 : : {
88 : 1 : rust_error_at (metavar->get_locus (),
89 : : "metavariable is still repeating at this depth");
90 : 1 : rust_inform (
91 : : metavar->get_locus (),
92 : : "you probably forgot the repetition operator: %<%s%s%s%>", "$(",
93 : 1 : metavar->as_string ().c_str (), ")*");
94 : 1 : return true;
95 : : }
96 : :
97 : : // We only care about the vector when expanding repetitions.
98 : : // Just access the first element of the vector.
99 : 11330 : auto &frag = it->second->get_single_fragment ();
100 : 29260 : for (size_t offs = frag.token_offset_begin; offs < frag.token_offset_end;
101 : : offs++)
102 : : {
103 : 17930 : auto &tok = input.at (offs);
104 : 17930 : expanded.push_back (tok->clone_token ());
105 : : }
106 : : }
107 : :
108 : : return true;
109 : 11334 : }
110 : :
111 : : bool
112 : 1631 : SubstituteCtx::check_repetition_amount (size_t pattern_start,
113 : : size_t pattern_end,
114 : : size_t &expected_repetition_amount)
115 : : {
116 : 1631 : bool first_fragment_found = false;
117 : 1631 : bool is_valid = true;
118 : :
119 : 22644 : for (size_t i = pattern_start; i < pattern_end; i++)
120 : : {
121 : 21013 : if (macro.at (i)->get_id () == DOLLAR_SIGN)
122 : : {
123 : 2049 : auto &frag_token = macro.at (i + 1);
124 : 2049 : if (token_id_is_keyword (frag_token->get_id ())
125 : 2049 : || frag_token->get_id () == IDENTIFIER)
126 : : {
127 : 2005 : auto it = fragments.find (frag_token->get_str ());
128 : 2005 : 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 : 2005 : auto &fragment = *it->second;
142 : :
143 : 2005 : if (!fragment.is_single_fragment ())
144 : : {
145 : 2003 : auto &fragment_rep
146 : : = static_cast<MatchedFragmentContainerRepetition &> (
147 : : fragment);
148 : 2003 : size_t repeat_amount = fragment_rep.get_match_amount ();
149 : 2003 : if (!first_fragment_found)
150 : : {
151 : 1631 : first_fragment_found = true;
152 : 1631 : expected_repetition_amount = repeat_amount;
153 : : }
154 : : else
155 : : {
156 : 372 : if (repeat_amount != expected_repetition_amount)
157 : : {
158 : 2 : 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 : 2 : is_valid = false;
165 : : }
166 : : }
167 : : }
168 : : }
169 : : }
170 : : }
171 : :
172 : 1631 : return is_valid && first_fragment_found;
173 : : }
174 : :
175 : : std::vector<std::unique_ptr<AST::Token>>
176 : 1631 : SubstituteCtx::substitute_repetition (
177 : : size_t pattern_start, size_t pattern_end,
178 : : std::unique_ptr<AST::Token> separator_token)
179 : : {
180 : 1631 : rust_assert (pattern_end < macro.size ());
181 : :
182 : 1631 : size_t repeat_amount = 0;
183 : 1631 : if (!check_repetition_amount (pattern_start, pattern_end, repeat_amount))
184 : 2 : return {};
185 : :
186 : 1629 : rust_debug ("repetition amount to use: %lu", (unsigned long) repeat_amount);
187 : 1629 : std::vector<std::unique_ptr<AST::Token>> expanded;
188 : 1629 : 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 : 22618 : for (size_t tok_idx = pattern_start; tok_idx < pattern_end; tok_idx++)
193 : 20989 : 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 : 5719 : for (size_t i = 0; i < repeat_amount; i++)
213 : : {
214 : 4090 : std::map<std::string, MatchedFragmentContainer *> sub_map;
215 : 10187 : for (auto &kv_match : fragments)
216 : : {
217 : 6097 : if (kv_match.second->is_single_fragment ())
218 : 1660 : 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 : 4437 : else if (kv_match.second->get_fragments ().size () > i)
224 : 4419 : sub_map.emplace (kv_match.first,
225 : 4419 : kv_match.second->get_fragments ().at (i).get ());
226 : : }
227 : :
228 : 4090 : auto substitute_context
229 : 4090 : = SubstituteCtx (input, new_macro, sub_map, definition);
230 : 4090 : 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 : 4090 : if (i != 0 && separator_token)
235 : 155 : expanded.emplace_back (separator_token->clone_token ());
236 : :
237 : 96826 : for (auto &new_token : new_tokens)
238 : 92736 : expanded.emplace_back (new_token->clone_token ());
239 : 4090 : }
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 : 1629 : return expanded;
245 : 1629 : }
246 : :
247 : : static bool
248 : 1631 : is_rep_op (std::unique_ptr<AST::Token> &tok)
249 : : {
250 : 1631 : auto id = tok->get_id ();
251 : 1631 : return id == QUESTION_MARK || id == ASTERISK || id == PLUS;
252 : : }
253 : :
254 : : std::pair<std::vector<std::unique_ptr<AST::Token>>, size_t>
255 : 12966 : SubstituteCtx::substitute_token (size_t token_idx)
256 : : {
257 : 12966 : auto &token = macro.at (token_idx);
258 : :
259 : 12966 : switch (token->get_id ())
260 : : {
261 : 4 : default:
262 : 4 : if (token_id_is_keyword (token->get_id ()))
263 : : {
264 : 11334 : case IDENTIFIER:
265 : 11335 : std::vector<std::unique_ptr<AST::Token>> expanded;
266 : :
267 : 11334 : rust_debug ("expanding metavar: %s", token->get_str ().c_str ());
268 : :
269 : 11334 : if (substitute_metavar (token, expanded))
270 : 11333 : return {std::move (expanded), 2};
271 : : }
272 : :
273 : : // don't substitute, dollar sign is alone/metavar is unknown
274 : 2 : return {std::vector<std::unique_ptr<AST::Token>> (), 0};
275 : :
276 : 1631 : case LEFT_PAREN:
277 : 1631 : {
278 : : // We need to parse up until the closing delimiter and expand this
279 : : // fragment->n times.
280 : 1631 : rust_debug ("expanding repetition");
281 : :
282 : : // We're in a context where macro repetitions have already been
283 : : // parsed and validated: This means that
284 : : // 1/ There will be no delimiters as that is an error
285 : : // 2/ There are no fragment specifiers anymore, which prevents us
286 : : // from reusing parser functions.
287 : : //
288 : : // Repetition patterns are also special in that they cannot contain
289 : : // "rogue" delimiters: For example, this is invalid, as they are
290 : : // parsed as MacroMatches and must contain a correct amount of
291 : : // delimiters.
292 : : // `$($e:expr ) )`
293 : : // ^ rogue closing parenthesis
294 : : //
295 : : // With all of that in mind, we can simply skip ahead from one
296 : : // parenthesis to the other to find the pattern to expand. Of course,
297 : : // pairs of delimiters, including parentheses, are allowed.
298 : : // `$($e:expr ( ) )`
299 : : // Parentheses are the sole delimiter for which we need a special
300 : : // behavior since they delimit the repetition pattern
301 : :
302 : 1631 : size_t pattern_start = token_idx + 1;
303 : 1631 : size_t pattern_end = pattern_start;
304 : 1631 : auto parentheses_stack = 0;
305 : 22644 : for (size_t idx = pattern_start; idx < macro.size (); idx++)
306 : : {
307 : 22644 : if (macro.at (idx)->get_id () == LEFT_PAREN)
308 : : {
309 : 1319 : parentheses_stack++;
310 : : }
311 : 21325 : else if (macro.at (idx)->get_id () == RIGHT_PAREN)
312 : : {
313 : 2950 : if (parentheses_stack == 0)
314 : : {
315 : : pattern_end = idx;
316 : : break;
317 : : }
318 : 1319 : parentheses_stack--;
319 : : }
320 : : }
321 : :
322 : : // Unreachable case, but let's make sure we don't ever run into it
323 : 1631 : rust_assert (pattern_end != pattern_start);
324 : :
325 : 1631 : std::unique_ptr<AST::Token> separator_token = nullptr;
326 : 1631 : if (pattern_end + 1 <= macro.size ())
327 : : {
328 : 1631 : auto &post_pattern_token = macro.at (pattern_end + 1);
329 : 1631 : if (!is_rep_op (post_pattern_token))
330 : 202 : separator_token = post_pattern_token->clone_token ();
331 : : }
332 : :
333 : : // Amount of tokens to skip
334 : 1631 : auto to_skip = 0;
335 : : // Parentheses
336 : 1631 : to_skip += 2;
337 : : // Repetition operator
338 : 1631 : to_skip += 1;
339 : : // Separator
340 : 1631 : if (separator_token)
341 : 202 : to_skip += 1;
342 : :
343 : 1631 : return {substitute_repetition (pattern_start, pattern_end,
344 : : std::move (separator_token)),
345 : 1631 : pattern_end - pattern_start + to_skip + 1};
346 : 1631 : }
347 : : }
348 : :
349 : : rust_unreachable ();
350 : : }
351 : :
352 : : std::vector<std::unique_ptr<AST::Token>>
353 : 6481 : SubstituteCtx::substitute_tokens ()
354 : : {
355 : 6481 : std::vector<std::unique_ptr<AST::Token>> replaced_tokens;
356 : 6481 : rust_debug ("expanding tokens");
357 : :
358 : 133764 : for (size_t i = 0; i < macro.size ();)
359 : : {
360 : 127283 : auto &tok = macro.at (i);
361 : 127283 : if (tok->get_id () == DOLLAR_SIGN)
362 : : {
363 : : // Aaaaah, if only we had C++17 :)
364 : : // auto [expanded, tok_to_skip] = ...
365 : 12966 : auto p = substitute_token (i + 1);
366 : 12966 : auto expanded = std::move (p.first);
367 : 12966 : auto tok_to_skip = p.second;
368 : :
369 : 12966 : if (!tok_to_skip)
370 : : {
371 : 2 : replaced_tokens.emplace_back (tok->clone_token ());
372 : 2 : tok_to_skip++;
373 : : }
374 : :
375 : 12966 : i += tok_to_skip;
376 : :
377 : 123789 : for (auto &token : expanded)
378 : 110823 : replaced_tokens.emplace_back (token->clone_token ());
379 : 12966 : }
380 : : else
381 : : {
382 : 114317 : replaced_tokens.emplace_back (tok->clone_token ());
383 : 114317 : i++;
384 : : }
385 : : }
386 : :
387 : 6481 : return replaced_tokens;
388 : : }
389 : :
390 : : } // namespace Rust
|