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-ast-fragment.h"
20 : #include "rust-common.h"
21 : #include "rust-macro-builtins.h"
22 : #include "rust-macro-builtins-helpers.h"
23 : #include "rust-session-manager.h"
24 : #include "optional.h"
25 : namespace Rust {
26 : /* Expand builtin macro include_bytes!("filename"), which includes the contents
27 : of the given file as reference to a byte array. Yields an expression of type
28 : &'static [u8; N]. */
29 :
30 : tl::optional<AST::Fragment>
31 45 : MacroBuiltin::include_bytes_handler (location_t invoc_locus,
32 : AST::MacroInvocData &invoc,
33 : AST::InvocKind semicolon)
34 : {
35 : /* Get target filename from the macro invocation, which is treated as a path
36 : relative to the include!-ing file (currently being compiled). */
37 45 : auto lit_expr
38 : = parse_single_string_literal (BuiltinMacro::IncludeBytes,
39 : invoc.get_delim_tok_tree (), invoc_locus,
40 45 : invoc.get_expander ());
41 45 : if (lit_expr == nullptr)
42 6 : return AST::Fragment::create_error ();
43 :
44 42 : if (!lit_expr->is_literal ())
45 : {
46 17 : auto token_tree = invoc.get_delim_tok_tree ();
47 34 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
48 68 : token_tree.to_token_stream ());
49 17 : }
50 :
51 25 : std::string target_filename
52 25 : = source_relative_path (lit_expr->as_string (), invoc_locus);
53 :
54 25 : auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
55 :
56 25 : if (!maybe_bytes.has_value ())
57 4 : return AST::Fragment::create_error ();
58 :
59 23 : std::vector<uint8_t> bytes = maybe_bytes.value ();
60 :
61 : /* Is there a more efficient way to do this? */
62 23 : std::vector<std::unique_ptr<AST::Expr>> elts;
63 :
64 : // We create the tokens for a borrow expression of a byte array, so
65 : // & [ <byte0>, <byte1>, ... ]
66 23 : std::vector<std::unique_ptr<AST::Token>> toks;
67 23 : toks.emplace_back (make_token (Token::make (AMP, invoc_locus)));
68 23 : toks.emplace_back (make_token (Token::make (LEFT_SQUARE, invoc_locus)));
69 :
70 1755 : for (uint8_t b : bytes)
71 : {
72 1732 : elts.emplace_back (
73 3464 : new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
74 : PrimitiveCoreType::CORETYPE_U8,
75 3464 : {} /* outer_attrs */, invoc_locus));
76 1732 : toks.emplace_back (make_token (Token::make_byte_char (invoc_locus, b)));
77 3464 : toks.emplace_back (make_token (Token::make (COMMA, invoc_locus)));
78 : }
79 :
80 23 : toks.emplace_back (make_token (Token::make (RIGHT_SQUARE, invoc_locus)));
81 :
82 23 : auto elems = std::unique_ptr<AST::ArrayElems> (
83 23 : new AST::ArrayElemsValues (std::move (elts), invoc_locus));
84 :
85 23 : auto array = std::unique_ptr<AST::Expr> (
86 23 : new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
87 :
88 23 : auto borrow = std::unique_ptr<AST::Expr> (
89 : new AST::BorrowExpr (std::move (array), Mutability::Imm,
90 : /* raw borrow */ false,
91 23 : /* double borrow */ false, {}, invoc_locus));
92 :
93 23 : auto node = AST::SingleASTNode (std::move (borrow));
94 :
95 69 : return AST::Fragment ({node}, std::move (toks));
96 68 : }
97 :
98 : /* Expand builtin macro include_str!("filename"), which includes the contents
99 : of the given file as a string. The file must be UTF-8 encoded. Yields an
100 : expression of type &'static str. */
101 :
102 : tl::optional<AST::Fragment>
103 48 : MacroBuiltin::include_str_handler (location_t invoc_locus,
104 : AST::MacroInvocData &invoc,
105 : AST::InvocKind semicolon)
106 : {
107 : /* Get target filename from the macro invocation, which is treated as a path
108 : relative to the include!-ing file (currently being compiled). */
109 48 : auto lit_expr
110 : = parse_single_string_literal (BuiltinMacro::IncludeStr,
111 : invoc.get_delim_tok_tree (), invoc_locus,
112 48 : invoc.get_expander ());
113 48 : if (lit_expr == nullptr)
114 6 : return AST::Fragment::create_error ();
115 :
116 45 : if (!lit_expr->is_literal ())
117 : {
118 18 : auto token_tree = invoc.get_delim_tok_tree ();
119 36 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
120 72 : token_tree.to_token_stream ());
121 18 : }
122 :
123 27 : std::string target_filename
124 27 : = source_relative_path (lit_expr->as_string (), invoc_locus);
125 :
126 27 : auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
127 :
128 27 : if (!maybe_bytes.has_value ())
129 4 : return AST::Fragment::create_error ();
130 :
131 25 : std::vector<uint8_t> bytes = maybe_bytes.value ();
132 :
133 : /* FIXME: reuse lexer */
134 25 : int expect_single = 0;
135 1911 : for (uint8_t b : bytes)
136 : {
137 1887 : if (expect_single)
138 : {
139 0 : if ((b & 0xC0) != 0x80)
140 : /* character was truncated, exit with expect_single != 0 */
141 : break;
142 0 : expect_single--;
143 : }
144 1887 : else if (b & 0x80)
145 : {
146 1 : if (b >= 0xF8)
147 : {
148 : /* more than 4 leading 1s */
149 : expect_single = 1;
150 : break;
151 : }
152 0 : else if (b >= 0xF0)
153 : {
154 : /* 4 leading 1s */
155 : expect_single = 3;
156 : }
157 0 : else if (b >= 0xE0)
158 : {
159 : /* 3 leading 1s */
160 : expect_single = 2;
161 : }
162 0 : else if (b >= 0xC0)
163 : {
164 : /* 2 leading 1s */
165 : expect_single = 1;
166 : }
167 : else
168 : {
169 : /* only 1 leading 1 */
170 : expect_single = 1;
171 : break;
172 : }
173 : }
174 : }
175 :
176 25 : std::string str;
177 25 : if (expect_single)
178 1 : rust_error_at (invoc_locus, "%s was not a valid utf-8 file",
179 : target_filename.c_str ());
180 : else
181 24 : str = std::string ((const char *) bytes.data (), bytes.size ());
182 :
183 50 : auto node = AST::SingleASTNode (make_string (invoc_locus, str));
184 50 : auto str_tok = make_token (Token::make_string (invoc_locus, std::move (str)));
185 :
186 75 : return AST::Fragment ({node}, std::move (str_tok));
187 73 : }
188 :
189 : /* Expand builtin macro include!(), which includes a source file at the current
190 : scope compile time. */
191 :
192 : tl::optional<AST::Fragment>
193 7 : MacroBuiltin::include_handler (location_t invoc_locus,
194 : AST::MacroInvocData &invoc,
195 : AST::InvocKind semicolon)
196 : {
197 7 : bool is_semicoloned = semicolon == AST::InvocKind::Semicoloned;
198 : /* Get target filename from the macro invocation, which is treated as a path
199 : relative to the include!-ing file (currently being compiled). */
200 7 : std::unique_ptr<AST::Expr> lit_expr
201 : = parse_single_string_literal (BuiltinMacro::Include,
202 : invoc.get_delim_tok_tree (), invoc_locus,
203 7 : invoc.get_expander (), is_semicoloned);
204 7 : if (lit_expr == nullptr)
205 0 : return AST::Fragment::create_error ();
206 :
207 7 : if (!lit_expr->is_literal ())
208 : {
209 : // We have to expand an inner macro eagerly
210 2 : auto token_tree = invoc.get_delim_tok_tree ();
211 :
212 : // parse_single_string_literal returned an AST::MacroInvocation, which
213 : // can either be an AST::Item or AST::Expr. Depending on the context the
214 : // original macro was invoked in, we will set AST::Item or AST::Expr
215 : // appropriately.
216 2 : if (is_semicoloned)
217 : {
218 1 : std::unique_ptr<AST::Item> lit_item = std::unique_ptr<AST::Item> (
219 1 : static_cast<AST::MacroInvocation *> (lit_expr.release ()));
220 2 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_item))},
221 4 : token_tree.to_token_stream ());
222 1 : }
223 : else
224 2 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
225 4 : token_tree.to_token_stream ());
226 2 : }
227 :
228 5 : std::string filename
229 5 : = source_relative_path (lit_expr->as_string (), invoc_locus);
230 5 : auto target_filename
231 5 : = Rust::Session::get_instance ().include_extra_file (std::move (filename));
232 :
233 5 : RAIIFile target_file (target_filename);
234 5 : Linemap *linemap = Session::get_instance ().linemap;
235 :
236 5 : if (!target_file.ok ())
237 : {
238 0 : rust_error_at (lit_expr->get_locus (),
239 : "cannot open included file %qs: %m", target_filename);
240 0 : return AST::Fragment::create_error ();
241 : }
242 :
243 5 : rust_debug ("Attempting to parse included file %s", target_filename);
244 :
245 5 : Lexer lex (target_filename, std::move (target_file), linemap);
246 5 : Parser<Lexer> parser (lex);
247 5 : std::unique_ptr<AST::Expr> parsed_expr = nullptr;
248 5 : std::vector<std::unique_ptr<AST::Item>> parsed_items{};
249 :
250 5 : if (is_semicoloned)
251 6 : parsed_items = parser.parse_items ().value_or (
252 6 : std::vector<std::unique_ptr<AST::Item>>{});
253 : else
254 4 : parsed_expr = parser.parse_expr ().value ();
255 :
256 5 : bool has_error = !parser.get_errors ().empty ();
257 :
258 5 : for (const auto &error : parser.get_errors ())
259 0 : error.emit ();
260 :
261 5 : if (has_error)
262 : {
263 : // inform the user that the errors above are from a included file
264 0 : rust_inform (invoc_locus, "included from here");
265 0 : return AST::Fragment::create_error ();
266 : }
267 :
268 5 : std::vector<AST::SingleASTNode> nodes{};
269 5 : if (is_semicoloned)
270 5 : for (auto &item : parsed_items)
271 : {
272 2 : AST::SingleASTNode node (std::move (item));
273 2 : nodes.push_back (node);
274 2 : }
275 : else
276 : {
277 2 : AST::SingleASTNode node (std::move (parsed_expr));
278 2 : nodes.push_back (node);
279 2 : }
280 : // FIXME: This returns an empty vector of tokens and works fine, but is that
281 : // the expected behavior? `include` macros are a bit harder to reason about
282 : // since they include tokens. Furthermore, our lexer has no easy way to return
283 : // a slice of tokens like the MacroInvocLexer. So it gets even harder to
284 : // extract tokens from here. For now, let's keep it that way and see if it
285 : // eventually breaks, but I don't expect it to cause many issues since the
286 : // list of tokens is only used when a macro invocation mixes eager
287 : // macro invocations and already expanded tokens. Think
288 : // `concat!(a!(), 15, b!())`. We need to be able to expand a!(), expand b!(),
289 : // and then insert the `15` token in between. In the case of `include!()`, we
290 : // only have one argument. So it's either going to be a macro invocation or a
291 : // string literal.
292 10 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
293 17 : }
294 : } // namespace Rust
|