Line data Source code
1 : // Copyright (C) 2020-2026 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 5 : MacroBuiltin::compile_error_handler (location_t invoc_locus,
31 : AST::MacroInvocData &invoc,
32 : AST::InvocKind semicolon)
33 : {
34 5 : auto lit_expr
35 : = parse_single_string_literal (BuiltinMacro::CompileError,
36 : invoc.get_delim_tok_tree (), invoc_locus,
37 5 : invoc.get_expander ());
38 5 : if (lit_expr == nullptr)
39 6 : return AST::Fragment::create_error ();
40 :
41 2 : rust_assert (lit_expr->is_literal ());
42 :
43 2 : std::string error_string = lit_expr->as_string ();
44 2 : rust_error_at (invoc_locus, "%s", error_string.c_str ());
45 :
46 4 : return AST::Fragment::create_error ();
47 5 : }
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 57 : MacroBuiltin::concat_handler (location_t invoc_locus,
93 : AST::MacroInvocData &invoc,
94 : AST::InvocKind semicolon)
95 : {
96 57 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
97 57 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
98 57 : Parser<MacroInvocLexer> parser (lex);
99 :
100 57 : auto str = std::string ();
101 57 : bool has_error = false;
102 :
103 57 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
104 :
105 57 : auto start = lex.get_offs ();
106 : /* NOTE: concat! could accept no argument, so we don't have any checks here */
107 57 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
108 57 : invoc.get_expander (), has_error);
109 57 : auto end = lex.get_offs ();
110 :
111 57 : auto tokens = lex.get_token_slice (start, end);
112 :
113 57 : auto pending_invocations = check_for_eager_invocations (expanded_expr);
114 57 : if (!pending_invocations.empty ())
115 6 : return make_eager_builtin_invocation (BuiltinMacro::Concat, invoc_locus,
116 3 : invoc.get_delim_tok_tree (),
117 3 : std::move (pending_invocations));
118 :
119 181 : for (auto &expr : expanded_expr)
120 : {
121 127 : if (!expr->is_literal ()
122 127 : && expr->get_expr_kind () != AST::Expr::Kind::MacroInvocation)
123 : {
124 2 : has_error = true;
125 2 : rust_error_at (expr->get_locus (), "expected a literal");
126 : // diagnostics copied from rustc
127 2 : rust_inform (expr->get_locus (),
128 : "only literals (like %<\"foo\"%>, %<42%> and "
129 : "%<3.14%>) can be passed to %<concat!()%>");
130 2 : continue;
131 : }
132 125 : auto *literal = static_cast<AST::LiteralExpr *> (expr.get ());
133 125 : if (literal->get_lit_type () == AST::Literal::BYTE
134 125 : || 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 250 : str += literal->as_string ();
142 : }
143 :
144 54 : parser.skip_token (last_token_id);
145 :
146 54 : if (has_error)
147 8 : return AST::Fragment::create_error ();
148 :
149 100 : auto node = AST::SingleASTNode (make_string (invoc_locus, str));
150 100 : auto str_tok = make_token (Token::make_string (invoc_locus, std::move (str)));
151 :
152 150 : return AST::Fragment ({node}, std::move (str_tok));
153 114 : }
154 :
155 : /* Expand builtin macro env!(), which inspects an environment variable at
156 : compile time. */
157 : tl::optional<AST::Fragment>
158 32 : MacroBuiltin::env_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
159 : AST::InvocKind semicolon)
160 : {
161 32 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
162 32 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
163 32 : Parser<MacroInvocLexer> parser (lex);
164 :
165 32 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
166 32 : std::unique_ptr<AST::LiteralExpr> error_expr = nullptr;
167 32 : std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
168 32 : bool has_error = false;
169 :
170 32 : auto start = lex.get_offs ();
171 32 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
172 32 : invoc.get_expander (), has_error);
173 32 : auto end = lex.get_offs ();
174 :
175 32 : auto tokens = lex.get_token_slice (start, end);
176 :
177 32 : if (has_error)
178 4 : return AST::Fragment::create_error ();
179 :
180 30 : auto pending = check_for_eager_invocations (expanded_expr);
181 30 : if (!pending.empty ())
182 6 : return make_eager_builtin_invocation (BuiltinMacro::Env, invoc_locus,
183 : invoc_token_tree,
184 3 : std::move (pending));
185 :
186 27 : if (expanded_expr.size () < 1 || expanded_expr.size () > 2)
187 : {
188 2 : rust_error_at (invoc_locus, "env! takes 1 or 2 arguments");
189 4 : return AST::Fragment::create_error ();
190 : }
191 25 : if (expanded_expr.size () > 0)
192 : {
193 50 : if (!(lit_expr
194 25 : = try_extract_string_literal_from_fragment (invoc_locus,
195 50 : expanded_expr[0])))
196 : {
197 6 : return AST::Fragment::create_error ();
198 : }
199 : }
200 22 : if (expanded_expr.size () > 1)
201 : {
202 5 : if (!(error_expr
203 5 : = try_extract_string_literal_from_fragment (invoc_locus,
204 10 : expanded_expr[1])))
205 : {
206 2 : return AST::Fragment::create_error ();
207 : }
208 : }
209 :
210 21 : parser.skip_token (last_token_id);
211 :
212 21 : auto env_value = getenv (lit_expr->as_string ().c_str ());
213 :
214 21 : if (env_value == nullptr)
215 : {
216 7 : if (error_expr == nullptr)
217 3 : rust_error_at (invoc_locus, "environment variable %qs not defined",
218 6 : lit_expr->as_string ().c_str ());
219 : else
220 4 : rust_error_at (invoc_locus, "%s", error_expr->as_string ().c_str ());
221 14 : 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 64 : }
230 :
231 : /* Expand builtin macro option_env!(), which inspects an environment variable at
232 : compile time. */
233 : tl::optional<AST::Fragment>
234 25 : MacroBuiltin::option_env_handler (location_t invoc_locus,
235 : AST::MacroInvocData &invoc,
236 : AST::InvocKind semicolon)
237 : {
238 25 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
239 25 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
240 25 : Parser<MacroInvocLexer> parser (lex);
241 :
242 25 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
243 25 : std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
244 25 : bool has_error = false;
245 :
246 25 : auto start = lex.get_offs ();
247 25 : auto expanded_expr = try_expand_many_expr (parser, last_token_id,
248 25 : invoc.get_expander (), has_error);
249 25 : auto end = lex.get_offs ();
250 :
251 25 : auto tokens = lex.get_token_slice (start, end);
252 :
253 25 : if (has_error)
254 0 : return AST::Fragment::create_error ();
255 :
256 25 : auto pending = check_for_eager_invocations (expanded_expr);
257 25 : 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 18 : if (expanded_expr.size () != 1)
263 : {
264 1 : rust_error_at (invoc_locus, "%<option_env!%> takes 1 argument");
265 2 : return AST::Fragment::create_error ();
266 : }
267 :
268 17 : if (expanded_expr.size () > 0)
269 34 : if (!(lit_expr
270 17 : = try_extract_string_literal_from_fragment (invoc_locus,
271 34 : expanded_expr[0])))
272 2 : return AST::Fragment::create_error ();
273 :
274 16 : parser.skip_token (last_token_id);
275 :
276 16 : auto env_value = getenv (lit_expr->as_string ().c_str ());
277 16 : AST::Builder b (invoc_locus);
278 :
279 16 : if (env_value == nullptr)
280 : {
281 1 : auto none_expr = std::unique_ptr<AST::Expr> (
282 : new AST::PathInExpression (LangItem::Kind::OPTION_NONE, {},
283 1 : invoc_locus));
284 :
285 1 : auto node = AST::SingleASTNode (std::move (none_expr));
286 1 : std::vector<AST::SingleASTNode> nodes;
287 1 : nodes.push_back (node);
288 :
289 2 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
290 1 : }
291 15 : std::vector<std::unique_ptr<AST::Expr>> args;
292 15 : args.push_back (b.literal_string (env_value));
293 :
294 15 : std::unique_ptr<AST::Expr> some_expr
295 15 : = b.call (std::unique_ptr<AST::Expr> (
296 : new AST::PathInExpression (LangItem::Kind::OPTION_SOME, {},
297 30 : invoc_locus)),
298 15 : std::move (args));
299 :
300 15 : auto node = AST::SingleASTNode (std::move (some_expr));
301 :
302 15 : std::vector<AST::SingleASTNode> nodes;
303 15 : nodes.push_back (node);
304 :
305 30 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
306 50 : }
307 :
308 : tl::optional<AST::Fragment>
309 47 : MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
310 : AST::InvocKind semicolon)
311 : {
312 : // only parse if not already parsed
313 47 : if (!invoc.is_parsed ())
314 : {
315 47 : std::unique_ptr<AST::AttrInputMetaItemContainer> converted_input (
316 47 : invoc.get_delim_tok_tree ().parse_to_meta_item ());
317 :
318 47 : 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 47 : std::vector<std::unique_ptr<AST::MetaItemInner>> meta_items (
326 47 : std::move (converted_input->get_items ()));
327 47 : invoc.set_meta_item_output (std::move (meta_items));
328 47 : }
329 47 : }
330 :
331 : /* TODO: assuming that cfg! macros can only have one meta item inner, like cfg
332 : * attributes */
333 47 : if (invoc.get_meta_items ().size () != 1)
334 0 : return AST::Fragment::create_error ();
335 :
336 47 : bool result = invoc.get_meta_items ()[0]->check_cfg_predicate (
337 47 : Session::get_instance ());
338 47 : auto literal_exp = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
339 68 : new AST::LiteralExpr (result ? "true" : "false", AST::Literal::BOOL,
340 141 : PrimitiveCoreType::CORETYPE_BOOL, {}, invoc_locus)));
341 47 : auto tok = make_token (
342 68 : Token::make (result ? TRUE_LITERAL : FALSE_LITERAL, invoc_locus));
343 :
344 141 : return AST::Fragment ({literal_exp}, std::move (tok));
345 47 : }
346 :
347 : tl::optional<AST::Fragment>
348 8 : MacroBuiltin::stringify_handler (location_t invoc_locus,
349 : AST::MacroInvocData &invoc,
350 : AST::InvocKind semicolon)
351 : {
352 8 : std::string content;
353 8 : auto invoc_token_tree = invoc.get_delim_tok_tree ();
354 8 : 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 51 : 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 43 : auto token_id = (*token)->get_id ();
364 43 : if (token_id != RIGHT_PAREN && token_id != EXCLAM
365 43 : && token != tokens.cbegin () + 1)
366 : {
367 19 : content.push_back (' ');
368 : }
369 86 : content += (*token)->as_string ();
370 : }
371 :
372 16 : auto node = AST::SingleASTNode (make_string (invoc_locus, content));
373 8 : auto token
374 16 : = make_token (Token::make_string (invoc_locus, std::move (content)));
375 24 : return AST::Fragment ({node}, std::move (token));
376 8 : }
377 :
378 : } // namespace Rust
|