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-fmt.h"
20 : : #include "rust-ast-builder.h"
21 : : #include "rust-macro-builtins.h"
22 : : #include "rust-macro-builtins-helpers.h"
23 : : #include "rust-session-manager.h"
24 : :
25 : : namespace Rust {
26 : :
27 : : /* Expand builtin macro compile_error!("error"), which forces a compile error
28 : : during the compile time. */
29 : : tl::optional<AST::Fragment>
30 : 10 : MacroBuiltin::compile_error_handler (location_t invoc_locus,
31 : : AST::MacroInvocData &invoc,
32 : : AST::InvocKind semicolon)
33 : : {
34 : 10 : auto lit_expr
35 : : = parse_single_string_literal (BuiltinMacro::CompileError,
36 : : invoc.get_delim_tok_tree (), invoc_locus,
37 : 10 : invoc.get_expander ());
38 : 10 : if (lit_expr == nullptr)
39 : 12 : return AST::Fragment::create_error ();
40 : :
41 : 4 : rust_assert (lit_expr->is_literal ());
42 : :
43 : 4 : std::string error_string = lit_expr->as_string ();
44 : 4 : rust_error_at (invoc_locus, "%s", error_string.c_str ());
45 : :
46 : 8 : return AST::Fragment::create_error ();
47 : 10 : }
48 : :
49 : : /* Expand builtin macro concat!(), which joins all the literal parameters
50 : : into a string with no delimiter. */
51 : :
52 : : // This is a weird one. We want to do something where, if something cannot be
53 : : // expanded yet (i.e. macro invocation?) we return the whole MacroInvocation
54 : : // node again but expanded as much as possible.
55 : : // Is that possible? How do we do that?
56 : : //
57 : : // Let's take a few examples:
58 : : //
59 : : // 1. concat!(1, 2, true);
60 : : // 2. concat!(a!(), 2, true);
61 : : // 3. concat!(concat!(1, false), 2, true);
62 : : // 4. concat!(concat!(1, a!()), 2, true);
63 : : //
64 : : // 1. We simply want to return the new fragment: "12true"
65 : : // 2. We want to return `concat!(a_expanded, 2, true)` as a fragment
66 : : // 3. We want to return `concat!(1, false, 2, true)`
67 : : // 4. We want to return `concat!(concat!(1, a_expanded), 2, true);
68 : : //
69 : : // How do we do that?
70 : : //
71 : : // For each (un)expanded fragment: we check if it is expanded fully
72 : : //
73 : : // 1. What is expanded fully?
74 : : // 2. How to check?
75 : : //
76 : : // If it is expanded fully and not a literal, then we error out.
77 : : // Otherwise we simply emplace it back and keep going.
78 : : //
79 : : // In the second case, we must mark that this concat invocation still has some
80 : : // expansion to do: This allows us to return a `MacroInvocation { ... }` as an
81 : : // AST fragment, instead of a completed string.
82 : : //
83 : : // This means that we must change all the `try_expand_many_*` APIs and so on to
84 : : // return some sort of index or way to signify that we might want to reuse some
85 : : // bits and pieces of the original token tree.
86 : : //
87 : : // Now, before that: How do we resolve the names used in a builtin macro
88 : : // invocation?
89 : : // Do we split the two passes of parsing the token tree and then expanding it?
90 : : // Can we do that easily?
91 : : tl::optional<AST::Fragment>
92 : 70 : MacroBuiltin::concat_handler (location_t invoc_locus,
93 : : AST::MacroInvocData &invoc,
94 : : AST::InvocKind semicolon)
95 : : {
96 : 70 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
97 : 70 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
98 : 70 : Parser<MacroInvocLexer> parser (lex);
99 : :
100 : 70 : auto str = std::string ();
101 : 70 : bool has_error = false;
102 : :
103 : 70 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
104 : :
105 : 70 : auto start = lex.get_offs ();
106 : : /* NOTE: concat! could accept no argument, so we don't have any checks here */
107 : 70 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
108 : 70 : invoc.get_expander (), has_error);
109 : 70 : auto end = lex.get_offs ();
110 : :
111 : 70 : auto tokens = lex.get_token_slice (start, end);
112 : :
113 : 70 : auto pending_invocations = check_for_eager_invocations (expanded_expr);
114 : 70 : if (!pending_invocations.empty ())
115 : 12 : return make_eager_builtin_invocation (BuiltinMacro::Concat, invoc_locus,
116 : 6 : invoc.get_delim_tok_tree (),
117 : 6 : std::move (pending_invocations));
118 : :
119 : 216 : for (auto &expr : expanded_expr)
120 : : {
121 : 152 : if (!expr->is_literal ()
122 : 152 : && expr->get_expr_kind () != AST::Expr::Kind::MacroInvocation)
123 : : {
124 : 4 : has_error = true;
125 : 4 : rust_error_at (expr->get_locus (), "expected a literal");
126 : : // diagnostics copied from rustc
127 : 4 : rust_inform (expr->get_locus (),
128 : : "only literals (like %<\"foo\"%>, %<42%> and "
129 : : "%<3.14%>) can be passed to %<concat!()%>");
130 : 4 : continue;
131 : : }
132 : 148 : auto *literal = static_cast<AST::LiteralExpr *> (expr.get ());
133 : 148 : if (literal->get_lit_type () == AST::Literal::BYTE
134 : 148 : || literal->get_lit_type () == AST::Literal::BYTE_STRING)
135 : : {
136 : 0 : has_error = true;
137 : 0 : rust_error_at (expr->get_locus (),
138 : : "cannot concatenate a byte string literal");
139 : 0 : continue;
140 : : }
141 : 296 : str += literal->as_string ();
142 : : }
143 : :
144 : 64 : parser.skip_token (last_token_id);
145 : :
146 : 64 : if (has_error)
147 : 16 : return AST::Fragment::create_error ();
148 : :
149 : 112 : auto node = AST::SingleASTNode (make_string (invoc_locus, str));
150 : 56 : auto str_tok = make_token (Token::make_string (invoc_locus, std::move (str)));
151 : :
152 : 168 : return AST::Fragment ({node}, std::move (str_tok));
153 : 140 : }
154 : :
155 : : /* Expand builtin macro env!(), which inspects an environment variable at
156 : : compile time. */
157 : : tl::optional<AST::Fragment>
158 : 50 : MacroBuiltin::env_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
159 : : AST::InvocKind semicolon)
160 : : {
161 : 50 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
162 : 50 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
163 : 50 : Parser<MacroInvocLexer> parser (lex);
164 : :
165 : 50 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
166 : 50 : std::unique_ptr<AST::LiteralExpr> error_expr = nullptr;
167 : 50 : std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
168 : 50 : bool has_error = false;
169 : :
170 : 50 : auto start = lex.get_offs ();
171 : 50 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
172 : 50 : invoc.get_expander (), has_error);
173 : 50 : auto end = lex.get_offs ();
174 : :
175 : 50 : auto tokens = lex.get_token_slice (start, end);
176 : :
177 : 50 : if (has_error)
178 : 8 : return AST::Fragment::create_error ();
179 : :
180 : 46 : auto pending = check_for_eager_invocations (expanded_expr);
181 : 46 : if (!pending.empty ())
182 : 12 : return make_eager_builtin_invocation (BuiltinMacro::Env, invoc_locus,
183 : : invoc_token_tree,
184 : 6 : std::move (pending));
185 : :
186 : 40 : if (expanded_expr.size () < 1 || expanded_expr.size () > 2)
187 : : {
188 : 4 : rust_error_at (invoc_locus, "env! takes 1 or 2 arguments");
189 : 8 : return AST::Fragment::create_error ();
190 : : }
191 : 36 : if (expanded_expr.size () > 0)
192 : : {
193 : 72 : if (!(lit_expr
194 : 36 : = try_extract_string_literal_from_fragment (invoc_locus,
195 : 72 : expanded_expr[0])))
196 : : {
197 : 12 : return AST::Fragment::create_error ();
198 : : }
199 : : }
200 : 30 : if (expanded_expr.size () > 1)
201 : : {
202 : 10 : if (!(error_expr
203 : 10 : = try_extract_string_literal_from_fragment (invoc_locus,
204 : 20 : expanded_expr[1])))
205 : : {
206 : 4 : return AST::Fragment::create_error ();
207 : : }
208 : : }
209 : :
210 : 28 : parser.skip_token (last_token_id);
211 : :
212 : 28 : auto env_value = getenv (lit_expr->as_string ().c_str ());
213 : :
214 : 28 : if (env_value == nullptr)
215 : : {
216 : 14 : if (error_expr == nullptr)
217 : 6 : rust_error_at (invoc_locus, "environment variable %qs not defined",
218 : 12 : lit_expr->as_string ().c_str ());
219 : : else
220 : 8 : rust_error_at (invoc_locus, "%s", error_expr->as_string ().c_str ());
221 : 28 : return AST::Fragment::create_error ();
222 : : }
223 : :
224 : 14 : auto node = AST::SingleASTNode (make_string (invoc_locus, env_value));
225 : 14 : auto tok
226 : 28 : = make_token (Token::make_string (invoc_locus, std::move (env_value)));
227 : :
228 : 42 : return AST::Fragment ({node}, std::move (tok));
229 : 100 : }
230 : :
231 : : /* Expand builtin macro option_env!(), which inspects an environment variable at
232 : : compile time. */
233 : : tl::optional<AST::Fragment>
234 : 29 : MacroBuiltin::option_env_handler (location_t invoc_locus,
235 : : AST::MacroInvocData &invoc,
236 : : AST::InvocKind semicolon)
237 : : {
238 : 29 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
239 : 29 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
240 : 29 : Parser<MacroInvocLexer> parser (lex);
241 : :
242 : 29 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
243 : 29 : std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
244 : 29 : bool has_error = false;
245 : :
246 : 29 : auto start = lex.get_offs ();
247 : 29 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
248 : 29 : invoc.get_expander (), has_error);
249 : 29 : auto end = lex.get_offs ();
250 : :
251 : 29 : auto tokens = lex.get_token_slice (start, end);
252 : :
253 : 29 : if (has_error)
254 : 0 : return AST::Fragment::create_error ();
255 : :
256 : 29 : auto pending = check_for_eager_invocations (expanded_expr);
257 : 29 : if (!pending.empty ())
258 : 14 : return make_eager_builtin_invocation (BuiltinMacro::OptionEnv, invoc_locus,
259 : : invoc_token_tree,
260 : 7 : std::move (pending));
261 : :
262 : 22 : if (expanded_expr.size () != 1)
263 : : {
264 : 2 : rust_error_at (invoc_locus, "%<option_env!%> takes 1 argument");
265 : 4 : return AST::Fragment::create_error ();
266 : : }
267 : :
268 : 20 : if (expanded_expr.size () > 0)
269 : 40 : if (!(lit_expr
270 : 20 : = try_extract_string_literal_from_fragment (invoc_locus,
271 : 40 : expanded_expr[0])))
272 : 4 : return AST::Fragment::create_error ();
273 : :
274 : 18 : parser.skip_token (last_token_id);
275 : :
276 : 18 : auto env_value = getenv (lit_expr->as_string ().c_str ());
277 : 18 : AST::Builder b (invoc_locus);
278 : :
279 : 18 : if (env_value == nullptr)
280 : : {
281 : 2 : auto none_expr = std::unique_ptr<AST::Expr> (
282 : : new AST::PathInExpression (LangItem::Kind::OPTION_NONE, {},
283 : 2 : invoc_locus));
284 : :
285 : 2 : auto node = AST::SingleASTNode (std::move (none_expr));
286 : 2 : std::vector<AST::SingleASTNode> nodes;
287 : 2 : nodes.push_back (node);
288 : :
289 : 4 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
290 : 2 : }
291 : 16 : std::vector<std::unique_ptr<AST::Expr>> args;
292 : 16 : args.push_back (b.literal_string (env_value));
293 : :
294 : 16 : std::unique_ptr<AST::Expr> some_expr
295 : 16 : = b.call (std::unique_ptr<AST::Expr> (
296 : : new AST::PathInExpression (LangItem::Kind::OPTION_SOME, {},
297 : 32 : invoc_locus)),
298 : 16 : std::move (args));
299 : :
300 : 16 : auto node = AST::SingleASTNode (std::move (some_expr));
301 : :
302 : 16 : std::vector<AST::SingleASTNode> nodes;
303 : 16 : nodes.push_back (node);
304 : :
305 : 32 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
306 : 58 : }
307 : :
308 : : tl::optional<AST::Fragment>
309 : 52 : MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
310 : : AST::InvocKind semicolon)
311 : : {
312 : : // only parse if not already parsed
313 : 52 : if (!invoc.is_parsed ())
314 : : {
315 : 52 : std::unique_ptr<AST::AttrInputMetaItemContainer> converted_input (
316 : 52 : invoc.get_delim_tok_tree ().parse_to_meta_item ());
317 : :
318 : 52 : if (converted_input == nullptr)
319 : : {
320 : 0 : rust_debug ("DEBUG: failed to parse macro to meta item");
321 : : // TODO: do something now? is this an actual error?
322 : : }
323 : : else
324 : : {
325 : 52 : std::vector<std::unique_ptr<AST::MetaItemInner>> meta_items (
326 : 52 : std::move (converted_input->get_items ()));
327 : 52 : invoc.set_meta_item_output (std::move (meta_items));
328 : 52 : }
329 : 52 : }
330 : :
331 : : /* TODO: assuming that cfg! macros can only have one meta item inner, like cfg
332 : : * attributes */
333 : 52 : if (invoc.get_meta_items ().size () != 1)
334 : 0 : return AST::Fragment::create_error ();
335 : :
336 : 52 : bool result = invoc.get_meta_items ()[0]->check_cfg_predicate (
337 : 52 : Session::get_instance ());
338 : 52 : auto literal_exp = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
339 : 73 : new AST::LiteralExpr (result ? "true" : "false", AST::Literal::BOOL,
340 : 156 : PrimitiveCoreType::CORETYPE_BOOL, {}, invoc_locus)));
341 : 52 : auto tok = make_token (
342 : 73 : Token::make (result ? TRUE_LITERAL : FALSE_LITERAL, invoc_locus));
343 : :
344 : 156 : return AST::Fragment ({literal_exp}, std::move (tok));
345 : 52 : }
346 : :
347 : : tl::optional<AST::Fragment>
348 : 9 : MacroBuiltin::stringify_handler (location_t invoc_locus,
349 : : AST::MacroInvocData &invoc,
350 : : AST::InvocKind semicolon)
351 : : {
352 : 9 : std::string content;
353 : 9 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
354 : 9 : auto tokens = invoc_token_tree.to_token_stream ();
355 : :
356 : : // Tokens stream includes the first and last delimiter
357 : : // which we need to skip.
358 : 67 : for (auto token = tokens.cbegin () + 1; token < tokens.cend () - 1; token++)
359 : : {
360 : : // Rust stringify format has no garantees but the reference compiler
361 : : // removes spaces before some tokens depending on the lexer's behavior,
362 : : // let's mimick some of those behaviors.
363 : 58 : auto token_id = (*token)->get_id ();
364 : 58 : if (token_id != RIGHT_PAREN && token_id != EXCLAM
365 : 58 : && token != tokens.cbegin () + 1)
366 : : {
367 : 31 : content.push_back (' ');
368 : : }
369 : 116 : content += (*token)->as_string ();
370 : : }
371 : :
372 : 18 : auto node = AST::SingleASTNode (make_string (invoc_locus, content));
373 : 9 : auto token
374 : 9 : = make_token (Token::make_string (invoc_locus, std::move (content)));
375 : 27 : return AST::Fragment ({node}, std::move (token));
376 : 9 : }
377 : :
378 : : } // namespace Rust
|