gccrs: early-name-resolver: Add simple macro name resolution
[official-gcc.git] / gcc / rust / expand / rust-macro-builtins.cc
bloba230ad9f2d830e00e2d5e01f09c9b74377325c40
1 // Copyright (C) 2020-2023 Free Software Foundation, Inc.
3 // This file is part of GCC.
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.
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.
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/>.
19 #include "rust-macro-builtins.h"
20 #include "rust-ast.h"
21 #include "rust-diagnostics.h"
22 #include "rust-expr.h"
23 #include "rust-session-manager.h"
24 #include "rust-macro-invoc-lexer.h"
25 #include "rust-lex.h"
26 #include "rust-parse.h"
27 #include "rust-early-name-resolver.h"
28 #include "rust-attribute-visitor.h"
30 namespace Rust {
31 namespace {
32 std::unique_ptr<AST::Expr>
33 make_string (Location locus, std::string value)
35 return std::unique_ptr<AST::Expr> (
36 new AST::LiteralExpr (value, AST::Literal::STRING,
37 PrimitiveCoreType::CORETYPE_STR, {}, locus));
40 /* Match the end token of a macro given the start delimiter of the macro */
42 static inline TokenId
43 macro_end_token (AST::DelimTokenTree &invoc_token_tree,
44 Parser<MacroInvocLexer> &parser)
46 auto last_token_id = TokenId::RIGHT_CURLY;
47 switch (invoc_token_tree.get_delim_type ())
49 case AST::DelimType::PARENS:
50 last_token_id = TokenId::RIGHT_PAREN;
51 rust_assert (parser.skip_token (LEFT_PAREN));
52 break;
54 case AST::DelimType::CURLY:
55 rust_assert (parser.skip_token (LEFT_CURLY));
56 break;
58 case AST::DelimType::SQUARE:
59 last_token_id = TokenId::RIGHT_SQUARE;
60 rust_assert (parser.skip_token (LEFT_SQUARE));
61 break;
64 return last_token_id;
67 /* Expand and extract an expression from the macro */
69 static inline AST::ASTFragment
70 try_expand_macro_expression (AST::Expr *expr, MacroExpander *expander)
72 rust_assert (expander);
74 auto attr_visitor = Rust::AttrVisitor (*expander);
75 auto early_name_resolver = Resolver::EarlyNameResolver ();
76 expr->accept_vis (early_name_resolver);
77 expr->accept_vis (attr_visitor);
78 return expander->take_expanded_fragment (attr_visitor);
81 /* Expand and then extract a string literal from the macro */
83 static std::unique_ptr<AST::LiteralExpr>
84 try_extract_string_literal_from_fragment (const Location &parent_locus,
85 std::unique_ptr<AST::Expr> &node)
87 auto maybe_lit = static_cast<AST::LiteralExpr *> (node.get ());
88 if (!node || !node->is_literal ()
89 || maybe_lit->get_lit_type () != AST::Literal::STRING)
91 rust_error_at (parent_locus, "argument must be a string literal");
92 if (node)
93 rust_inform (node->get_locus (), "expanded from here");
94 return nullptr;
96 return std::unique_ptr<AST::LiteralExpr> (
97 static_cast<AST::LiteralExpr *> (node->clone_expr ().release ()));
100 static std::unique_ptr<AST::LiteralExpr>
101 try_expand_single_string_literal (AST::Expr *input_expr,
102 const Location &invoc_locus,
103 MacroExpander *expander)
105 auto nodes = try_expand_macro_expression (input_expr, expander);
106 if (nodes.is_error () || nodes.is_expression_fragment ())
108 rust_error_at (input_expr->get_locus (),
109 "argument must be a string literal");
110 return nullptr;
112 auto expr = nodes.take_expression_fragment ();
113 return try_extract_string_literal_from_fragment (input_expr->get_locus (),
114 expr);
117 static std::vector<std::unique_ptr<AST::Expr>>
118 try_expand_many_expr (Parser<MacroInvocLexer> &parser,
119 const Location &invoc_locus, const TokenId last_token_id,
120 MacroExpander *expander, bool &has_error)
122 auto restrictions = Rust::ParseRestrictions ();
123 // stop parsing when encountered a braces/brackets
124 restrictions.expr_can_be_null = true;
125 // we can't use std::optional, so...
126 auto result = std::vector<std::unique_ptr<AST::Expr>> ();
127 auto empty_expr = std::vector<std::unique_ptr<AST::Expr>> ();
129 auto first_token = parser.peek_current_token ()->get_id ();
130 if (first_token == COMMA)
132 rust_error_at (parser.peek_current_token ()->get_locus (),
133 "expected expression, found %<,%>");
134 has_error = true;
135 return empty_expr;
138 while (parser.peek_current_token ()->get_id () != last_token_id
139 && parser.peek_current_token ()->get_id () != END_OF_FILE)
141 auto expr = parser.parse_expr (AST::AttrVec (), restrictions);
142 // something must be so wrong that the expression could not be parsed
143 rust_assert (expr);
144 auto nodes = try_expand_macro_expression (expr.get (), expander);
145 if (nodes.is_error ())
147 // not macro
148 result.push_back (std::move (expr));
150 else if (!nodes.is_expression_fragment ())
152 rust_error_at (expr->get_locus (), "expected expression");
153 has_error = true;
154 return empty_expr;
156 else
158 result.push_back (nodes.take_expression_fragment ());
161 auto next_token = parser.peek_current_token ();
162 if (!parser.skip_token (COMMA) && next_token->get_id () != last_token_id)
164 rust_error_at (next_token->get_locus (), "expected token: %<,%>");
165 // TODO: is this recoverable? to avoid crashing the parser in the next
166 // fragment we have to exit early here
167 has_error = true;
168 return empty_expr;
172 return result;
175 /* Parse a single string literal from the given delimited token tree,
176 and return the LiteralExpr for it. Allow for an optional trailing comma,
177 but otherwise enforce that these are the only tokens. */
179 std::unique_ptr<AST::LiteralExpr>
180 parse_single_string_literal (AST::DelimTokenTree &invoc_token_tree,
181 Location invoc_locus, MacroExpander *expander)
183 MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
184 Parser<MacroInvocLexer> parser (lex);
186 auto last_token_id = macro_end_token (invoc_token_tree, parser);
188 std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
190 if (parser.peek_current_token ()->get_id () == STRING_LITERAL)
192 lit_expr = parser.parse_literal_expr ();
193 parser.maybe_skip_token (COMMA);
194 if (parser.peek_current_token ()->get_id () != last_token_id)
196 lit_expr = nullptr;
197 rust_error_at (invoc_locus, "macro takes 1 argument");
200 else if (parser.peek_current_token ()->get_id () == last_token_id)
201 rust_error_at (invoc_locus, "macro takes 1 argument");
202 else
204 // when the expression does not seem to be a string literal, we then try
205 // to parse/expand it as macro to see if it expands to a string literal
206 auto expr = parser.parse_expr ();
207 lit_expr
208 = try_expand_single_string_literal (expr.get (), invoc_locus, expander);
211 parser.skip_token (last_token_id);
213 return lit_expr;
216 /* Treat PATH as a path relative to the source file currently being
217 compiled, and return the absolute path for it. */
219 std::string
220 source_relative_path (std::string path, Location locus)
222 std::string compile_fname
223 = Session::get_instance ().linemap->location_file (locus);
225 auto dir_separator_pos = compile_fname.rfind (file_separator);
227 /* If there is no file_separator in the path, use current dir ('.'). */
228 std::string dirname;
229 if (dir_separator_pos == std::string::npos)
230 dirname = std::string (".") + file_separator;
231 else
232 dirname = compile_fname.substr (0, dir_separator_pos) + file_separator;
234 return dirname + path;
237 /* Read the full contents of the file FILENAME and return them in a vector.
238 FIXME: platform specific. */
240 std::vector<uint8_t>
241 load_file_bytes (const char *filename)
243 RAIIFile file_wrap (filename);
244 if (file_wrap.get_raw () == nullptr)
246 rust_error_at (Location (), "cannot open filename %s: %m", filename);
247 return std::vector<uint8_t> ();
250 FILE *f = file_wrap.get_raw ();
251 fseek (f, 0L, SEEK_END);
252 long fsize = ftell (f);
253 fseek (f, 0L, SEEK_SET);
255 std::vector<uint8_t> buf (fsize);
257 if (fread (&buf[0], fsize, 1, f) != 1)
259 rust_error_at (Location (), "error reading file %s: %m", filename);
260 return std::vector<uint8_t> ();
263 return buf;
265 } // namespace
267 AST::ASTFragment
268 MacroBuiltin::assert (Location invoc_locus, AST::MacroInvocData &invoc)
270 rust_debug ("assert!() called");
272 return AST::ASTFragment::create_error ();
275 AST::ASTFragment
276 MacroBuiltin::file (Location invoc_locus, AST::MacroInvocData &invoc)
278 auto current_file
279 = Session::get_instance ().linemap->location_file (invoc_locus);
280 auto file_str = AST::SingleASTNode (make_string (invoc_locus, current_file));
282 return AST::ASTFragment ({file_str});
285 AST::ASTFragment
286 MacroBuiltin::column (Location invoc_locus, AST::MacroInvocData &invoc)
288 auto current_column
289 = Session::get_instance ().linemap->location_to_column (invoc_locus);
291 auto column_no = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
292 new AST::LiteralExpr (std::to_string (current_column), AST::Literal::INT,
293 PrimitiveCoreType::CORETYPE_U32, {}, invoc_locus)));
295 return AST::ASTFragment ({column_no});
298 /* Expand builtin macro include_bytes!("filename"), which includes the contents
299 of the given file as reference to a byte array. Yields an expression of type
300 &'static [u8; N]. */
302 AST::ASTFragment
303 MacroBuiltin::include_bytes (Location invoc_locus, AST::MacroInvocData &invoc)
305 /* Get target filename from the macro invocation, which is treated as a path
306 relative to the include!-ing file (currently being compiled). */
307 auto lit_expr
308 = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus,
309 invoc.get_expander ());
310 if (lit_expr == nullptr)
311 return AST::ASTFragment::create_error ();
313 std::string target_filename
314 = source_relative_path (lit_expr->as_string (), invoc_locus);
316 std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
318 /* Is there a more efficient way to do this? */
319 std::vector<std::unique_ptr<AST::Expr>> elts;
320 for (uint8_t b : bytes)
322 elts.emplace_back (
323 new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
324 PrimitiveCoreType::CORETYPE_U8,
325 {} /* outer_attrs */, invoc_locus));
328 auto elems = std::unique_ptr<AST::ArrayElems> (
329 new AST::ArrayElemsValues (std::move (elts), invoc_locus));
331 auto array = std::unique_ptr<AST::Expr> (
332 new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
334 auto borrow = std::unique_ptr<AST::Expr> (
335 new AST::BorrowExpr (std::move (array), false, false, {}, invoc_locus));
337 auto node = AST::SingleASTNode (std::move (borrow));
338 return AST::ASTFragment ({node});
341 /* Expand builtin macro include_str!("filename"), which includes the contents
342 of the given file as a string. The file must be UTF-8 encoded. Yields an
343 expression of type &'static str. */
345 AST::ASTFragment
346 MacroBuiltin::include_str (Location invoc_locus, AST::MacroInvocData &invoc)
348 /* Get target filename from the macro invocation, which is treated as a path
349 relative to the include!-ing file (currently being compiled). */
350 auto lit_expr
351 = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus,
352 invoc.get_expander ());
353 if (lit_expr == nullptr)
354 return AST::ASTFragment::create_error ();
356 std::string target_filename
357 = source_relative_path (lit_expr->as_string (), invoc_locus);
359 std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
361 /* FIXME: Enforce that the file contents are valid UTF-8. */
362 std::string str ((const char *) &bytes[0], bytes.size ());
364 auto node = AST::SingleASTNode (make_string (invoc_locus, str));
365 return AST::ASTFragment ({node});
368 /* Expand builtin macro compile_error!("error"), which forces a compile error
369 during the compile time. */
370 AST::ASTFragment
371 MacroBuiltin::compile_error (Location invoc_locus, AST::MacroInvocData &invoc)
373 auto lit_expr
374 = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus,
375 invoc.get_expander ());
376 if (lit_expr == nullptr)
377 return AST::ASTFragment::create_error ();
379 std::string error_string = lit_expr->as_string ();
380 rust_error_at (invoc_locus, "%s", error_string.c_str ());
382 return AST::ASTFragment::create_error ();
385 /* Expand builtin macro concat!(), which joins all the literal parameters
386 into a string with no delimiter. */
388 AST::ASTFragment
389 MacroBuiltin::concat (Location invoc_locus, AST::MacroInvocData &invoc)
391 auto invoc_token_tree = invoc.get_delim_tok_tree ();
392 MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
393 Parser<MacroInvocLexer> parser (lex);
395 auto str = std::string ();
396 bool has_error = false;
398 auto last_token_id = macro_end_token (invoc_token_tree, parser);
400 /* NOTE: concat! could accept no argument, so we don't have any checks here */
401 auto expanded_expr = try_expand_many_expr (parser, invoc_locus, last_token_id,
402 invoc.get_expander (), has_error);
403 for (auto &expr : expanded_expr)
405 if (!expr->is_literal ())
407 has_error = true;
408 rust_error_at (expr->get_locus (), "expected a literal");
409 // diagnostics copied from rustc
410 rust_inform (expr->get_locus (),
411 "only literals (like %<\"foo\"%>, %<42%> and "
412 "%<3.14%>) can be passed to %<concat!()%>");
413 continue;
415 auto *literal = static_cast<AST::LiteralExpr *> (expr.get ());
416 if (literal->get_lit_type () == AST::Literal::BYTE
417 || literal->get_lit_type () == AST::Literal::BYTE_STRING)
419 has_error = true;
420 rust_error_at (expr->get_locus (),
421 "cannot concatenate a byte string literal");
422 continue;
424 str += literal->as_string ();
427 parser.skip_token (last_token_id);
429 if (has_error)
430 return AST::ASTFragment::create_error ();
432 auto node = AST::SingleASTNode (make_string (invoc_locus, str));
433 return AST::ASTFragment ({node});
436 /* Expand builtin macro env!(), which inspects an environment variable at
437 compile time. */
439 AST::ASTFragment
440 MacroBuiltin::env (Location invoc_locus, AST::MacroInvocData &invoc)
442 auto invoc_token_tree = invoc.get_delim_tok_tree ();
443 MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
444 Parser<MacroInvocLexer> parser (lex);
446 auto last_token_id = macro_end_token (invoc_token_tree, parser);
447 std::unique_ptr<AST::LiteralExpr> error_expr = nullptr;
448 std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
449 bool has_error = false;
451 auto expanded_expr = try_expand_many_expr (parser, invoc_locus, last_token_id,
452 invoc.get_expander (), has_error);
453 if (has_error)
454 return AST::ASTFragment::create_error ();
455 if (expanded_expr.size () < 1 || expanded_expr.size () > 2)
457 rust_error_at (invoc_locus, "env! takes 1 or 2 arguments");
458 return AST::ASTFragment::create_error ();
460 if (expanded_expr.size () > 0)
462 if (!(lit_expr
463 = try_extract_string_literal_from_fragment (invoc_locus,
464 expanded_expr[0])))
466 return AST::ASTFragment::create_error ();
469 if (expanded_expr.size () > 1)
471 if (!(error_expr
472 = try_extract_string_literal_from_fragment (invoc_locus,
473 expanded_expr[1])))
475 return AST::ASTFragment::create_error ();
479 parser.skip_token (last_token_id);
481 auto env_value = getenv (lit_expr->as_string ().c_str ());
483 if (env_value == nullptr)
485 if (error_expr == nullptr)
486 rust_error_at (invoc_locus, "environment variable %qs not defined",
487 lit_expr->as_string ().c_str ());
488 else
489 rust_error_at (invoc_locus, "%s", error_expr->as_string ().c_str ());
490 return AST::ASTFragment::create_error ();
493 auto node = AST::SingleASTNode (make_string (invoc_locus, env_value));
494 return AST::ASTFragment ({node});
497 AST::ASTFragment
498 MacroBuiltin::cfg (Location invoc_locus, AST::MacroInvocData &invoc)
500 // only parse if not already parsed
501 if (!invoc.is_parsed ())
503 std::unique_ptr<AST::AttrInputMetaItemContainer> converted_input (
504 invoc.get_delim_tok_tree ().parse_to_meta_item ());
506 if (converted_input == nullptr)
508 rust_debug ("DEBUG: failed to parse macro to meta item");
509 // TODO: do something now? is this an actual error?
511 else
513 std::vector<std::unique_ptr<AST::MetaItemInner>> meta_items (
514 std::move (converted_input->get_items ()));
515 invoc.set_meta_item_output (std::move (meta_items));
519 /* TODO: assuming that cfg! macros can only have one meta item inner, like cfg
520 * attributes */
521 if (invoc.get_meta_items ().size () != 1)
522 return AST::ASTFragment::create_error ();
524 bool result = invoc.get_meta_items ()[0]->check_cfg_predicate (
525 Session::get_instance ());
526 auto literal_exp = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
527 new AST::LiteralExpr (result ? "true" : "false", AST::Literal::BOOL,
528 PrimitiveCoreType::CORETYPE_BOOL, {}, invoc_locus)));
530 return AST::ASTFragment ({literal_exp});
533 /* Expand builtin macro include!(), which includes a source file at the current
534 scope compile time. */
536 AST::ASTFragment
537 MacroBuiltin::include (Location invoc_locus, AST::MacroInvocData &invoc)
539 /* Get target filename from the macro invocation, which is treated as a path
540 relative to the include!-ing file (currently being compiled). */
541 auto lit_expr
542 = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus,
543 invoc.get_expander ());
544 if (lit_expr == nullptr)
545 return AST::ASTFragment::create_error ();
547 std::string filename
548 = source_relative_path (lit_expr->as_string (), invoc_locus);
549 auto target_filename
550 = Rust::Session::get_instance ().include_extra_file (std::move (filename));
552 RAIIFile target_file (target_filename);
553 Linemap *linemap = Session::get_instance ().linemap;
555 if (!target_file.ok ())
557 rust_error_at (lit_expr->get_locus (),
558 "cannot open included file %qs: %m", target_filename);
559 return AST::ASTFragment::create_error ();
562 rust_debug ("Attempting to parse included file %s", target_filename);
564 Lexer lex (target_filename, std::move (target_file), linemap);
565 Parser<Lexer> parser (lex);
567 auto parsed_items = parser.parse_items ();
568 bool has_error = !parser.get_errors ().empty ();
570 for (const auto &error : parser.get_errors ())
571 error.emit_error ();
573 if (has_error)
575 // inform the user that the errors above are from a included file
576 rust_inform (invoc_locus, "included from here");
577 return AST::ASTFragment::create_error ();
580 std::vector<AST::SingleASTNode> nodes{};
581 for (auto &item : parsed_items)
583 AST::SingleASTNode node (std::move (item));
584 nodes.push_back (node);
587 return AST::ASTFragment (nodes);
590 AST::ASTFragment
591 MacroBuiltin::line (Location invoc_locus, AST::MacroInvocData &invoc)
593 auto current_line
594 = Session::get_instance ().linemap->location_to_line (invoc_locus);
596 auto line_no = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
597 new AST::LiteralExpr (std::to_string (current_line), AST::Literal::INT,
598 PrimitiveCoreType::CORETYPE_U32, {}, invoc_locus)));
600 return AST::ASTFragment ({line_no});
603 } // namespace Rust