LCOV - code coverage report
Current view: top level - gcc/rust/expand - rust-macro-builtins-utility.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 96.6 % 178 172
Test Date: 2026-02-28 14:20:25 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            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
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.