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 "expected.h"
20 : #include "rust-macro-builtins-asm.h"
21 : #include "rust-ast-fragment.h"
22 : #include "rust-ast.h"
23 : #include "rust-fmt.h"
24 : #include "rust-stmt.h"
25 : #include "rust-parse.h"
26 :
27 : namespace Rust {
28 : std::set<std::string> potentially_nonpromoted_keywords
29 : = {"in", "out", "lateout", "inout", "inlateout", "const", "sym", "label"};
30 :
31 : // Helper function strips the beginning and ending double quotes from a
32 : // string.
33 : std::string
34 46 : strip_double_quotes (const std::string &str)
35 : {
36 46 : std::string result = str;
37 :
38 46 : rust_assert (!str.empty ());
39 :
40 46 : rust_assert (str.front () == '\"');
41 46 : rust_assert (str.back () == '\"');
42 :
43 : // we have to special case empty strings which just contain a set of quotes
44 : // so, if the string is "\"\"", just return ""
45 46 : if (result.size () == 2)
46 2 : return "";
47 :
48 44 : rust_assert (result.size () >= 3);
49 :
50 44 : result.erase (0, 1);
51 44 : result.erase (result.size () - 1, 1);
52 :
53 44 : return result;
54 46 : }
55 :
56 : tl::expected<InlineAsmContext, InlineAsmParseError>
57 3 : parse_clobber_abi (InlineAsmContext inline_asm_ctx)
58 : {
59 : // clobber_abi := "clobber_abi(" <abi> *("," <abi>) [","] ")"
60 : // PARSE EVERYTHING COMMITTEDLY IN THIS FUNCTION, WE CONFIRMED VIA clobber_abi
61 : // identifier keyword
62 3 : auto &parser = inline_asm_ctx.parser;
63 3 : auto last_token_id = inline_asm_ctx.last_token_id;
64 3 : auto &inline_asm = inline_asm_ctx.inline_asm;
65 3 : auto token = parser.peek_current_token ();
66 3 : if (!parser.skip_token (LEFT_PAREN))
67 : {
68 2 : token = parser.peek_current_token ();
69 :
70 : // TODO: Error reporting shifted to the left 1 character, I'm not sure
71 : // why.
72 2 : if (token->get_id () == last_token_id)
73 : {
74 1 : rust_error_at (token->get_locus (),
75 : "expected %<(%>, found end of macro arguments");
76 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
77 : }
78 : else
79 : {
80 1 : rust_error_at (token->get_locus (), "expected %<(%>, found %qs",
81 : token->get_token_description ());
82 : }
83 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
84 : }
85 :
86 1 : if (parser.skip_token (RIGHT_PAREN))
87 : {
88 1 : rust_error_at (
89 1 : parser.peek_current_token ()->get_locus (),
90 : "at least one abi must be provided as an argument to %<clobber_abi%>");
91 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
92 : }
93 :
94 0 : std::vector<AST::TupleClobber> new_abis;
95 :
96 0 : token = parser.peek_current_token ();
97 :
98 0 : while (token->get_id () != last_token_id && token->get_id () != RIGHT_PAREN)
99 : {
100 : // Check if it is a string literal or not, codename: <ABI> in ABNF
101 0 : if (token->get_id () == STRING_LITERAL)
102 : {
103 : // TODO: Caring for span in here.
104 0 : new_abis.emplace_back (token->as_string (), token->get_locus ());
105 : }
106 : else
107 : {
108 : // TODO: We encountered something that is not string literal, which
109 : // should be illegal, please emit the correct error
110 : // https://github.com/rust-lang/rust/blob/b92758a9aef1cef7b79e2b72c3d8ba113e547f89/compiler/rustc_builtin_macros/src/asm.rs#L387
111 0 : rust_unreachable ();
112 : }
113 :
114 0 : if (parser.skip_token (RIGHT_PAREN))
115 : {
116 : break;
117 : }
118 :
119 0 : if (!parser.skip_token (COMMA))
120 : {
121 : // TODO: If the skip of comma is unsuccessful, which should be
122 : // illegal, pleaes emit the correct error.
123 0 : rust_unreachable ();
124 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
125 : }
126 :
127 0 : token = parser.peek_current_token ();
128 : }
129 :
130 : // Done processing the local clobber abis, push that to the main Args in
131 : // argument
132 :
133 0 : for (auto abi : new_abis)
134 : {
135 0 : inline_asm.clobber_abi.push_back (abi);
136 0 : }
137 :
138 0 : return inline_asm_ctx;
139 0 : }
140 :
141 : tl::optional<AST::InlineAsmRegOrRegClass>
142 32 : parse_reg (InlineAsmContext &inline_asm_ctx)
143 : {
144 32 : using RegType = AST::InlineAsmRegOrRegClass::Type;
145 32 : auto &parser = inline_asm_ctx.parser;
146 :
147 32 : if (!parser.skip_token (LEFT_PAREN))
148 : {
149 : // TODO: we expect a left parenthesis here, please return the correct
150 : // error.
151 0 : rust_unreachable ();
152 : return tl::nullopt;
153 : }
154 :
155 : // after successful left parenthesis parsing, we should return ast of
156 : // InlineAsmRegOrRegClass of reg or reg class
157 32 : auto token = parser.peek_current_token ();
158 32 : AST::InlineAsmRegOrRegClass reg_class;
159 32 : if (parser.skip_token (IDENTIFIER))
160 : {
161 : // construct a InlineAsmRegOrRegClass
162 31 : reg_class.type = RegType::RegClass;
163 31 : reg_class.reg_class.Symbol = token->as_string ();
164 : }
165 1 : else if (parser.skip_token (STRING_LITERAL))
166 : {
167 : // TODO: there is STRING_LITERAL, and BYTE_STRING_LITERAL, should we check
168 : // for both?
169 :
170 : // construct a InlineAsmRegOrRegClass
171 : // parse_format_string
172 1 : reg_class.type = RegType::Reg;
173 1 : inline_asm_ctx.is_explicit = true;
174 1 : reg_class.reg_class.Symbol = token->as_string ();
175 : }
176 : else
177 : {
178 : // TODO: This should emit error
179 : // return
180 : // Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister
181 : // {
182 : // span: p.token.span,
183 : // }));
184 0 : rust_unreachable ();
185 : }
186 32 : if (!parser.skip_token (RIGHT_PAREN))
187 : {
188 : // TODO: we expect a left parenthesis here, please return the correct
189 : // error.
190 0 : rust_unreachable ();
191 : return tl::nullopt;
192 : }
193 :
194 32 : return reg_class;
195 32 : }
196 :
197 : // From rustc
198 : tl::expected<InlineAsmContext, InlineAsmParseError>
199 56 : parse_reg_operand (InlineAsmContext inline_asm_ctx)
200 : {
201 : // let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
202 : // let (ident, _) = p.token.ident().unwrap();
203 : // p.bump();
204 : // p.expect(&token::Eq)?;
205 : // allow_templates = false;
206 : // Some(ident.name)
207 : // } else {
208 : // None
209 : // };
210 56 : auto &parser = inline_asm_ctx.parser;
211 56 : auto token = parser.peek_current_token ();
212 56 : auto iden_token = parser.peek_current_token ();
213 :
214 56 : tl::optional<std::string> name = tl::nullopt;
215 56 : if (check_identifier (parser, ""))
216 : {
217 3 : auto equal_token = parser.peek_current_token ();
218 :
219 3 : if (parser.skip_token (EQUAL))
220 : {
221 3 : name = token->as_string ();
222 : }
223 : else
224 : {
225 0 : rust_error_at (token->get_locus (),
226 : "expected operand, %s, options, or "
227 : "additional template string",
228 : "clobber_abi");
229 0 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
230 : }
231 3 : }
232 :
233 56 : int slot = inline_asm_ctx.inline_asm.operands.size ();
234 :
235 : // Here is all parse_reg_operand functions we're using in a for loop
236 56 : auto parse_funcs = {parse_reg_operand_in, parse_reg_operand_out,
237 : parse_reg_operand_lateout, parse_reg_operand_inout,
238 : parse_reg_operand_const, parse_reg_operand_sym,
239 56 : parse_reg_operand_unexpected};
240 :
241 : // Loop over and execute the parsing functions, if the parser successfullly
242 : // parses or if the parser fails to parse while it has committed to a token,
243 : // we propogate the result.
244 56 : tl::expected<InlineAsmContext, InlineAsmParseError> parsing_operand (
245 56 : inline_asm_ctx);
246 226 : for (auto &parse_func : parse_funcs)
247 : {
248 226 : auto result = parsing_operand.and_then (parse_func);
249 :
250 : // Per rust's asm.rs's structure
251 : // After we've parse successfully, we break out and do a local validation
252 : // of named, positional & explicit register operands
253 :
254 226 : if (result.has_value ())
255 : {
256 32 : inline_asm_ctx = *result;
257 32 : break;
258 : }
259 194 : else if (result.error () == COMMITTED)
260 : {
261 24 : if (parse_func == parse_reg_operand_unexpected)
262 24 : return inline_asm_ctx;
263 : else
264 0 : return result;
265 : }
266 : }
267 :
268 32 : auto &inline_asm = inline_asm_ctx.inline_asm;
269 :
270 32 : token = inline_asm_ctx.parser.peek_current_token ();
271 32 : rust_debug_loc (token->get_locus (), "Here\n");
272 32 : if (inline_asm_ctx.is_explicit)
273 : {
274 1 : if (name != tl::nullopt)
275 : {
276 1 : rust_error_at (token->get_locus (),
277 : "explicit register arguments cannot have names");
278 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
279 : }
280 0 : inline_asm.reg_args.insert (slot);
281 : }
282 31 : else if (name != tl::nullopt)
283 : {
284 2 : if (inline_asm.named_args.find (name.value ())
285 2 : != inline_asm.named_args.end ())
286 : {
287 1 : rust_error_at (token->get_locus (), "duplicate argument named %qs",
288 1 : name.value ().c_str ());
289 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
290 : }
291 1 : inline_asm.named_args[name.value ()] = slot;
292 : }
293 : else
294 : {
295 29 : if (!inline_asm.named_args.empty () || !inline_asm.reg_args.empty ())
296 : {
297 : // positional arguments cannot follow named arguments or explicit
298 : // register arguments
299 0 : rust_error_at (token->get_locus (),
300 : "positional arguments cannot follow named arguments "
301 : "or explicit register arguments");
302 0 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
303 : }
304 : }
305 :
306 30 : return inline_asm_ctx;
307 112 : }
308 :
309 : tl::expected<InlineAsmContext, InlineAsmParseError>
310 56 : parse_reg_operand_in (InlineAsmContext inline_asm_ctx)
311 : {
312 : // For the keyword IN, currently we count it as a seperate keyword called
313 : // Rust::IN search for #define RS_TOKEN_LIST in code base.
314 56 : auto &parser = inline_asm_ctx.parser;
315 56 : location_t locus = parser.peek_current_token ()->get_locus ();
316 56 : if (!inline_asm_ctx.is_global_asm () && parser.skip_token (IN))
317 : {
318 12 : auto reg = parse_reg (inline_asm_ctx);
319 :
320 12 : if (parser.skip_token (UNDERSCORE))
321 : {
322 : // We are sure to be failing a test here, based on asm.rs
323 : // https://github.com/rust-lang/rust/blob/a330e49593ee890f9197727a3a558b6e6b37f843/compiler/rustc_builtin_macros/src/asm.rs#L112
324 0 : rust_unreachable ();
325 : // return tl::unexpected<InlineAsmParseError> (COMMITTED);
326 : }
327 :
328 12 : auto expr = parser.parse_expr ();
329 12 : rust_assert (expr);
330 :
331 : // TODO: When we've succesfully parse an expr, remember to clone_expr()
332 : // instead of nullptr
333 12 : AST::InlineAsmOperand::In in (reg, std::move (expr.value ()));
334 12 : inline_asm_ctx.inline_asm.operands.emplace_back (in, locus);
335 12 : return inline_asm_ctx;
336 36 : }
337 44 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
338 : }
339 :
340 : tl::expected<InlineAsmContext, InlineAsmParseError>
341 44 : parse_reg_operand_out (InlineAsmContext inline_asm_ctx)
342 : {
343 44 : auto &parser = inline_asm_ctx.parser;
344 44 : location_t locus = parser.peek_current_token ()->get_locus ();
345 88 : if (!inline_asm_ctx.is_global_asm () && check_identifier (parser, "out"))
346 : {
347 17 : auto reg = parse_reg (inline_asm_ctx);
348 17 : auto expr = parser.parse_expr ();
349 17 : rust_assert (expr);
350 :
351 : /*auto expr_ptr =
352 : std::make_unique<AST::Expr>(AST::LiteralExpr(Literal))*/
353 :
354 : // TODO: When we've succesfully parse an expr, remember to clone_expr()
355 : // instead of nullptr
356 17 : AST::InlineAsmOperand::Out out (reg, false, std::move (expr.value ()));
357 :
358 17 : inline_asm_ctx.inline_asm.operands.emplace_back (out, locus);
359 :
360 17 : return inline_asm_ctx;
361 51 : }
362 :
363 27 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
364 : }
365 :
366 : tl::expected<InlineAsmContext, InlineAsmParseError>
367 27 : parse_reg_operand_lateout (InlineAsmContext inline_asm_ctx)
368 : {
369 27 : auto &parser = inline_asm_ctx.parser;
370 27 : auto token = parser.peek_current_token ();
371 54 : if (!inline_asm_ctx.is_global_asm () && check_identifier (parser, "lateout"))
372 : {
373 0 : rust_error_at (token->get_locus (),
374 : "The lateout feature is not implemented");
375 0 : rust_unreachable ();
376 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
377 : }
378 :
379 27 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
380 27 : }
381 :
382 : tl::expected<InlineAsmContext, InlineAsmParseError>
383 27 : parse_reg_operand_inout (InlineAsmContext inline_asm_ctx)
384 : {
385 27 : auto &parser = inline_asm_ctx.parser;
386 27 : auto token = parser.peek_current_token ();
387 27 : location_t locus = token->get_locus ();
388 :
389 54 : if (!inline_asm_ctx.is_global_asm () && check_identifier (parser, "inout"))
390 : {
391 3 : auto reg = parse_reg (inline_asm_ctx);
392 :
393 3 : if (parser.skip_token (UNDERSCORE))
394 : {
395 : // We are sure to be failing a test here, based on asm.rs
396 : // https://github.com/rust-lang/rust/blob/a330e49593ee890f9197727a3a558b6e6b37f843/compiler/rustc_builtin_macros/src/asm.rs#L112
397 0 : rust_error_at (token->get_locus (),
398 : "The lateout feature is not implemented");
399 0 : rust_unreachable ();
400 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
401 : }
402 :
403 : // TODO: Is error propogation our top priority, the ? in rust's asm.rs is
404 : // doing a lot of work.
405 3 : auto in_expr = parser.parse_expr ();
406 3 : rust_assert (in_expr);
407 :
408 3 : if (parser.skip_token (MATCH_ARROW))
409 : {
410 2 : if (!parser.skip_token (UNDERSCORE))
411 : {
412 : // auto result = parse_format_string (inline_asm_ctx);
413 :
414 2 : auto out_expr = parser.parse_expr ();
415 2 : rust_assert (out_expr);
416 :
417 2 : AST::InlineAsmOperand::SplitInOut splitinout (
418 2 : reg, false, std::move (in_expr.value ()),
419 2 : std::move (out_expr.value ()));
420 :
421 2 : inline_asm_ctx.inline_asm.operands.emplace_back (splitinout,
422 : locus);
423 :
424 2 : return inline_asm_ctx;
425 4 : }
426 :
427 0 : rust_unreachable ();
428 :
429 : // TODO: Rembmer to pass in clone_expr() instead of nullptr
430 : // https://github.com/rust-lang/rust/blob/a3167859f2fd8ff2241295469876a2b687280bdc/compiler/rustc_builtin_macros/src/asm.rs#L135
431 : // RUST VERSION: ast::InlineAsmOperand::SplitInOut { reg, in_expr:
432 : // expr, out_expr, late: false }
433 : // struct AST::InlineAsmOperand::SplitInOut split_in_out (reg,
434 : // false, nullptr,
435 : // nullptr);
436 : // inline_asm_ctx.inline_asm.operands.push_back (split_in_out);
437 :
438 : return inline_asm_ctx;
439 : }
440 : else
441 : {
442 1 : AST::InlineAsmOperand::InOut inout (reg, false,
443 1 : std::move (in_expr.value ()));
444 1 : inline_asm_ctx.inline_asm.operands.emplace_back (inout, locus);
445 : // https://github.com/rust-lang/rust/blob/a3167859f2fd8ff2241295469876a2b687280bdc/compiler/rustc_builtin_macros/src/asm.rs#L137
446 : // RUST VERSION: ast::InlineAsmOperand::InOut { reg, expr, late: false
447 : // }
448 : // struct AST::InlineAsmOperand::InOut inout (reg, false,
449 : // nullptr);
450 : // inline_asm_ctx.inline_asm.operands.push_back (inout);
451 1 : return inline_asm_ctx;
452 1 : }
453 6 : }
454 :
455 24 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
456 27 : }
457 :
458 : tl::expected<InlineAsmContext, InlineAsmParseError>
459 24 : parse_reg_operand_const (InlineAsmContext inline_asm_ctx)
460 : {
461 24 : auto &parser = inline_asm_ctx.parser;
462 48 : if (parser.peek_current_token ()->get_id () == CONST)
463 : {
464 : // TODO: Please handle const with parse_expr instead.
465 0 : auto anon_const = parse_format_string (inline_asm_ctx);
466 0 : rust_unreachable ();
467 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
468 : }
469 :
470 24 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
471 : }
472 :
473 : tl::expected<InlineAsmContext, InlineAsmParseError>
474 24 : parse_reg_operand_sym (InlineAsmContext inline_asm_ctx)
475 : {
476 24 : auto &parser = inline_asm_ctx.parser;
477 :
478 24 : if (check_identifier (parser, "sym"))
479 : {
480 : // TODO: Please handle sym, which needs ExprKind::Path in Rust's asm.rs
481 0 : rust_unreachable ();
482 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
483 : }
484 24 : return tl::unexpected<InlineAsmParseError> (NONCOMMITED);
485 : }
486 :
487 : tl::expected<InlineAsmContext, InlineAsmParseError>
488 24 : parse_reg_operand_unexpected (InlineAsmContext inline_asm_ctx)
489 : {
490 24 : auto token = inline_asm_ctx.parser.peek_current_token ();
491 : // TODO: It is weird that we can't seem to match any identifier,
492 : // something must be wrong. consult compiler code in asm.rs or rust online
493 : // compiler.
494 : // rust_unreachable ();
495 :
496 : // rust_error_at (token->get_locus (), "ERROR RIGHT HERE");
497 24 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
498 24 : }
499 :
500 : void
501 9 : check_and_set (InlineAsmContext &inline_asm_ctx, AST::InlineAsm::Option option)
502 : {
503 9 : auto &parser = inline_asm_ctx.parser;
504 9 : auto &inline_asm = inline_asm_ctx.inline_asm;
505 9 : if (inline_asm.options.count (option) != 0)
506 : {
507 : // TODO: report an error of duplication
508 4 : rust_error_at (parser.peek_current_token ()->get_locus (),
509 : "the %qs option was already provided",
510 2 : AST::InlineAsm::option_to_string (option).c_str ());
511 2 : return;
512 : }
513 : else
514 : {
515 7 : inline_asm.options.insert (option);
516 : }
517 : }
518 : tl::expected<InlineAsmContext, InlineAsmParseError>
519 7 : parse_options (InlineAsmContext &inline_asm_ctx)
520 : {
521 7 : auto &parser = inline_asm_ctx.parser;
522 7 : bool is_global_asm = inline_asm_ctx.inline_asm.is_global_asm;
523 : // Parse everything commitedly
524 7 : if (!parser.skip_token (LEFT_PAREN))
525 : {
526 1 : auto local_token = parser.peek_current_token ();
527 1 : rust_error_at (local_token->get_locus (), "expected %qs, found %qs", "(",
528 1 : local_token->as_string ().c_str ());
529 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
530 1 : }
531 :
532 6 : auto token = parser.peek_current_token ();
533 11 : while (!parser.skip_token (RIGHT_PAREN))
534 : {
535 22 : if (!is_global_asm && check_identifier (parser, "pure"))
536 : {
537 0 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::PURE);
538 : }
539 22 : else if (!is_global_asm && check_identifier (parser, "nomem"))
540 : {
541 3 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::NOMEM);
542 : }
543 16 : else if (!is_global_asm && check_identifier (parser, "readonly"))
544 : {
545 0 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::READONLY);
546 : }
547 16 : else if (!is_global_asm && check_identifier (parser, "preserves_flags"))
548 : {
549 0 : check_and_set (inline_asm_ctx,
550 : AST::InlineAsm::Option::PRESERVES_FLAGS);
551 : }
552 16 : else if (!is_global_asm && check_identifier (parser, "noreturn"))
553 : {
554 3 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::NORETURN);
555 : }
556 10 : else if (!is_global_asm && check_identifier (parser, "nostack"))
557 : {
558 1 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::NOSTACK);
559 : }
560 8 : else if (!is_global_asm && check_identifier (parser, "may_unwind"))
561 : {
562 0 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::MAY_UNWIND);
563 : }
564 4 : else if (check_identifier (parser, "att_syntax"))
565 : {
566 1 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::ATT_SYNTAX);
567 : }
568 3 : else if (check_identifier (parser, "raw"))
569 : {
570 1 : check_and_set (inline_asm_ctx, AST::InlineAsm::Option::RAW);
571 : }
572 : else
573 : {
574 2 : rust_error_at (token->get_locus (),
575 : "expected one of %qs, %qs, %qs, %qs, %qs, %qs, %qs, "
576 : "%qs, %qs, or %qs, found %qs",
577 : ")", "att_syntax", "may_unwind", "nomem", "noreturn",
578 : "nostack", "preserves_flags", "pure", "raw",
579 2 : "readonly", token->as_string ().c_str ());
580 2 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
581 : }
582 9 : if (parser.skip_token (RIGHT_PAREN))
583 : {
584 : break;
585 : }
586 :
587 : // Parse comma as optional
588 5 : if (parser.skip_token (COMMA))
589 5 : continue;
590 : else
591 : {
592 0 : rust_unreachable ();
593 : token = parser.peek_current_token ();
594 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
595 : }
596 : }
597 :
598 : // TODO: Per rust asm.rs regarding options_spans
599 : // I'm guessing this has to do with some error reporting.
600 : // let new_span = span_start.to(p.prev_token.span);
601 : // args.options_spans.push(new_span);
602 :
603 4 : return inline_asm_ctx;
604 6 : }
605 :
606 : bool
607 369 : check_identifier (Parser<MacroInvocLexer> &p, std::string ident)
608 : {
609 369 : auto token = p.peek_current_token ();
610 :
611 369 : if (token->get_id () == IDENTIFIER)
612 : {
613 162 : auto str = token->as_string ();
614 :
615 : // For non-promoted keywords, we need to also check for them.
616 :
617 162 : if (str == ident)
618 : {
619 39 : p.skip_token ();
620 39 : return true;
621 : }
622 123 : if (ident == "")
623 : {
624 22 : if (potentially_nonpromoted_keywords.find (str)
625 22 : == potentially_nonpromoted_keywords.end ())
626 : {
627 3 : p.skip_token ();
628 3 : return true;
629 : }
630 : return false;
631 : }
632 162 : }
633 :
634 : return false;
635 369 : }
636 :
637 : tl::optional<std::string>
638 72 : parse_format_string (InlineAsmContext &inline_asm_ctx)
639 : {
640 72 : auto &parser = inline_asm_ctx.parser;
641 72 : auto last_token_id = inline_asm_ctx.last_token_id;
642 72 : auto token = parser.peek_current_token ();
643 :
644 72 : if (token->get_id () != last_token_id && token->get_id () == STRING_LITERAL)
645 : {
646 : // very nice, we got a supposedly formatted string.
647 38 : parser.skip_token ();
648 38 : return token->as_string ();
649 : }
650 : else
651 : {
652 34 : return tl::nullopt;
653 : }
654 72 : }
655 :
656 : tl::optional<AST::Fragment>
657 39 : MacroBuiltin::asm_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
658 : AST::InvocKind semicolon, AST::AsmKind is_global_asm)
659 : {
660 39 : return parse_asm (invoc_locus, invoc, semicolon, is_global_asm);
661 : }
662 :
663 : tl::optional<AST::Fragment>
664 2 : MacroBuiltin::llvm_asm_handler (location_t invoc_locus,
665 : AST::MacroInvocData &invoc,
666 : AST::InvocKind semicolon,
667 : AST::AsmKind is_global_asm)
668 : {
669 2 : return parse_llvm_asm (invoc_locus, invoc, semicolon, is_global_asm);
670 : }
671 :
672 : tl::expected<InlineAsmContext, InlineAsmParseError>
673 37 : parse_asm_arg (InlineAsmContext inline_asm_ctx)
674 : {
675 37 : auto &parser = inline_asm_ctx.parser;
676 37 : auto last_token_id = inline_asm_ctx.last_token_id;
677 37 : auto token = parser.peek_current_token ();
678 37 : tl::optional<std::string> fm_string;
679 95 : while (token->get_id () != last_token_id)
680 : {
681 66 : token = parser.peek_current_token ();
682 :
683 66 : if (token->get_id () == COLON || token->get_id () == SCOPE_RESOLUTION)
684 : {
685 0 : rust_error_at (
686 : token->get_locus (),
687 : "the legacy LLVM-style %<asm!%> syntax is no longer supported");
688 8 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
689 : }
690 :
691 : // We accept a comma token here.
692 66 : if (token->get_id () != COMMA
693 66 : && inline_asm_ctx.consumed_comma_without_formatted_string)
694 : {
695 : // if it is not a comma, but we consumed it previously, this is fine
696 : // but we have to set it to false tho.
697 0 : inline_asm_ctx.consumed_comma_without_formatted_string = false;
698 : }
699 66 : else if (token->get_id () == COMMA
700 66 : && !inline_asm_ctx.consumed_comma_without_formatted_string)
701 : {
702 20 : inline_asm_ctx.consumed_comma_without_formatted_string = false;
703 20 : parser.skip_token ();
704 : }
705 46 : else if (token->get_id () == COMMA
706 46 : && inline_asm_ctx.consumed_comma_without_formatted_string)
707 : {
708 : // We consumed comma, and there happens to also be a comma
709 : // error should be: expected expression, found `,`
710 0 : rust_error_at (token->get_locus (), "expected expression, found %qs",
711 : ",");
712 0 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
713 66 : break;
714 : }
715 :
716 : // And if that token comma is also the trailing comma, we break
717 66 : token = parser.peek_current_token ();
718 66 : if (token->get_id () == COMMA && token->get_id () == last_token_id)
719 : {
720 0 : parser.skip_token ();
721 0 : break;
722 : }
723 :
724 : // Ok after the left paren is good, we better be parsing correctly
725 : // everything in here, which is operand in ABNF
726 :
727 : // Parse clobber abi, eat the identifier named "clobber_abi" if true
728 66 : if (check_identifier (parser, "clobber_abi"))
729 : {
730 3 : auto expected = parse_clobber_abi (inline_asm_ctx);
731 3 : if (expected.has_value ())
732 0 : continue;
733 3 : else if (expected.error () == COMMITTED)
734 3 : return expected;
735 :
736 : // The error type is definitely non-committed (we have checked above),
737 : // we are allowed to keep on parsing
738 : }
739 :
740 63 : if (check_identifier (parser, "options"))
741 : {
742 7 : auto expected = parse_options (inline_asm_ctx);
743 7 : if (expected.has_value ())
744 4 : continue;
745 3 : else if (expected.error () == COMMITTED)
746 3 : return expected;
747 :
748 : // The error type is definitely non-committed (we have checked above),
749 : // we are allowed to keep on parsing
750 : }
751 :
752 : // Ok after we have check that neither clobber_abi nor options works, the
753 : // only other logical choice is reg_operand
754 :
755 56 : auto expected = parse_reg_operand (inline_asm_ctx);
756 56 : if (expected.has_value ())
757 54 : continue;
758 2 : else if (expected.error () == COMMITTED)
759 2 : return expected;
760 :
761 : // Since parse_reg_operand is the last thing we've considered,
762 : // The non-committed parse error type means that we have exhausted our
763 : // search path
764 :
765 : // We then should return the error of COMMITTED, even though we have not
766 : // committed to anything So that the error bubbles up and we recover from
767 : // this error gracefully
768 0 : rust_error_at (token->get_locus (),
769 : "expected operand, %s, options, or additional "
770 : "template string",
771 : "clobber_abi");
772 0 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
773 : }
774 29 : return tl::expected<InlineAsmContext, InlineAsmParseError> (inline_asm_ctx);
775 37 : }
776 :
777 : tl::expected<InlineAsmContext, InlineAsmParseError>
778 29 : expand_inline_asm_strings (InlineAsmContext inline_asm_ctx)
779 : {
780 29 : auto &inline_asm = inline_asm_ctx.inline_asm;
781 :
782 29 : auto str_vec = inline_asm.get_template_strs ();
783 :
784 29 : decltype (str_vec) resulting_template_vec;
785 58 : for (auto &template_str : str_vec)
786 : {
787 : /*std::cout << template_str.symbol << std::endl;*/
788 :
789 29 : auto pieces = Fmt::Pieces::collect (template_str.symbol, false,
790 29 : Fmt::ffi::ParseMode::InlineAsm);
791 29 : auto &pieces_vec = pieces.get_pieces ();
792 :
793 29 : std::string transformed_template_str = "";
794 97 : for (size_t i = 0; i < pieces_vec.size (); i++)
795 : {
796 68 : auto &piece = pieces_vec[i];
797 68 : if (piece.tag == Fmt::ffi::Piece::Tag::String)
798 : {
799 80 : transformed_template_str += piece.string._0.to_string ();
800 : }
801 28 : else if (piece.tag == Fmt::ffi::Piece::Tag::NextArgument)
802 : {
803 : /* std::cout << " " << i << ": "*/
804 : /*<< piece.next_argument._0.to_string () << std::endl;*/
805 :
806 28 : auto next_argument = piece.next_argument._0;
807 28 : switch (piece.next_argument._0.position.tag)
808 : {
809 28 : case Fmt::ffi::Position::Tag::ArgumentImplicitlyIs:
810 28 : {
811 28 : auto idx = next_argument.position.argument_implicitly_is._0;
812 : /*auto trait = next_argument.format;*/
813 : /*auto arg = arguments.at (idx);*/
814 :
815 : /* // FIXME(Arthur): This API sucks*/
816 : /* rust_assert (arg.get_kind ().kind*/
817 : /*== AST::FormatArgumentKind::Kind::Normal);*/
818 : /**/
819 : /* args.push_back ({arg.get_expr ().clone_expr (),
820 : * trait});*/
821 :
822 56 : transformed_template_str += "%" + std::to_string (idx);
823 : // std::cout << "argument implicitly is: " << idx <<
824 : // std::endl; std::cout << "transformed template str is:"
825 : // << transformed_template_str << std::endl;
826 : /*std::cout << "trait: " << trait.to_string () <<
827 : * std::endl;*/
828 : /*std::cout << "arg: " << arg.to_string () << std::endl;*/
829 : }
830 28 : break;
831 0 : case Fmt::ffi::Position::Tag::ArgumentIs:
832 0 : {
833 0 : auto idx = next_argument.position.argument_is._0;
834 0 : transformed_template_str += "%" + std::to_string (idx);
835 0 : break;
836 : }
837 0 : case Fmt::ffi::Position::Tag::ArgumentNamed:
838 0 : rust_sorry_at (inline_asm.get_locus (),
839 : "unhandled argument position specifier");
840 0 : break;
841 : }
842 28 : }
843 : }
844 29 : template_str.symbol = transformed_template_str;
845 58 : }
846 :
847 29 : inline_asm.template_strs = str_vec;
848 29 : return inline_asm_ctx;
849 29 : }
850 :
851 : tl::optional<AST::Fragment>
852 39 : parse_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
853 : AST::InvocKind semicolon, AST::AsmKind is_global_asm)
854 : {
855 : // From the rule of asm.
856 : // We first parse all formatted strings. If we fail, then we return
857 : // tl::nullopt
858 :
859 : // We then parse the asm arguments. If we fail, then we return
860 : // tl::nullopt
861 :
862 : // We then validate. If we fail, then we return tl::nullopt
863 :
864 : // Done
865 39 : MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ());
866 39 : Parser<MacroInvocLexer> parser (lex);
867 39 : auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser);
868 :
869 39 : AST::InlineAsm inline_asm (invoc_locus,
870 39 : is_global_asm == AST::AsmKind::Global);
871 39 : auto inline_asm_ctx = InlineAsmContext (inline_asm, parser, last_token_id);
872 :
873 39 : auto resulting_context = parse_format_strings (inline_asm_ctx)
874 39 : .and_then (parse_asm_arg)
875 39 : .and_then (validate)
876 39 : .and_then (expand_inline_asm_strings);
877 :
878 : // TODO: I'm putting the validation here because the rust reference put
879 : // it here Per Arthur's advice we would actually do the validation in a
880 : // different stage. and visit on the InlineAsm AST instead of it's
881 : // context.
882 39 : if (resulting_context)
883 : {
884 29 : auto resulting_ctx = resulting_context.value ();
885 29 : auto node = resulting_ctx.inline_asm.clone_expr_without_block ();
886 :
887 29 : std::vector<AST::SingleASTNode> single_vec = {};
888 :
889 : // If the macro invocation has a semicolon (`asm!("...");`), then we
890 : // need to make it a statement. This way, it will be expanded
891 : // properly.
892 29 : if (semicolon == AST::InvocKind::Semicoloned)
893 27 : single_vec.emplace_back (AST::SingleASTNode (
894 54 : std::make_unique<AST::ExprStmt> (std::move (node), invoc_locus,
895 : semicolon
896 54 : == AST::InvocKind::Semicoloned)));
897 : else
898 2 : single_vec.emplace_back (AST::SingleASTNode (std::move (node)));
899 :
900 29 : AST::Fragment fragment_ast
901 : = AST::Fragment (single_vec,
902 29 : std::vector<std::unique_ptr<AST::Token>> ());
903 29 : return fragment_ast;
904 29 : }
905 : else
906 : {
907 10 : return tl::nullopt;
908 : }
909 78 : }
910 :
911 : tl::expected<InlineAsmContext, InlineAsmParseError>
912 39 : parse_format_strings (InlineAsmContext inline_asm_ctx)
913 : {
914 : // Parse the first ever formatted string, success or not, will skip 1
915 : // token
916 39 : auto &parser = inline_asm_ctx.parser;
917 39 : auto last_token_id = inline_asm_ctx.last_token_id;
918 39 : auto fm_string = parse_format_string (inline_asm_ctx);
919 :
920 39 : auto &inline_asm = inline_asm_ctx.inline_asm;
921 39 : auto token = parser.peek_current_token ();
922 39 : if (fm_string == tl::nullopt)
923 : {
924 1 : rust_error_at (parser.peek_current_token ()->get_locus (),
925 : "%s template must be a string literal", "asm");
926 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
927 : }
928 : else
929 : {
930 38 : auto template_str
931 : = AST::TupleTemplateStr (token->get_locus (),
932 76 : strip_double_quotes (fm_string.value ()));
933 38 : inline_asm.template_strs.push_back (template_str);
934 38 : }
935 :
936 : // formatted string stream
937 :
938 76 : while (parser.peek_current_token ()->get_id () != last_token_id)
939 : {
940 34 : if (!parser.skip_token (COMMA))
941 : {
942 1 : rust_error_at (parser.peek_current_token ()->get_locus (),
943 : "expected token %qs", ";");
944 1 : return tl::unexpected<InlineAsmParseError> (COMMITTED);
945 : }
946 : // Ok after the comma is good, we better be parsing correctly
947 : // everything in here, which is formatted string in ABNF
948 33 : inline_asm_ctx.consumed_comma_without_formatted_string = false;
949 :
950 33 : token = parser.peek_current_token ();
951 33 : fm_string = parse_format_string (inline_asm_ctx);
952 33 : if (fm_string == tl::nullopt)
953 : {
954 33 : inline_asm_ctx.consumed_comma_without_formatted_string = true;
955 33 : break;
956 : }
957 : else
958 : {
959 0 : auto template_str
960 : = AST::TupleTemplateStr (token->get_locus (),
961 0 : strip_double_quotes (fm_string.value ()));
962 0 : inline_asm.template_strs.push_back (template_str);
963 0 : }
964 : }
965 :
966 37 : return inline_asm_ctx;
967 39 : }
968 :
969 : // bool
970 : // is_label (const std::string &potential_label)
971 : // {
972 :
973 : // if (potential_label.empty () || potential_label.back () != ':')
974 : // return false;
975 :
976 : // // Check if all characters before the last colon are digits
977 : // for (size_t i = 0; i < potential_label.length () - 1; i++)
978 : // {
979 : // if (potential_label[i] < '0' || potential_label[i] > '9')
980 : // return false;
981 : // }
982 :
983 : // return true;
984 : // }
985 :
986 : tl::expected<InlineAsmContext, InlineAsmParseError>
987 29 : validate (InlineAsmContext inline_asm_ctx)
988 : {
989 29 : return tl::expected<InlineAsmContext, InlineAsmParseError> (inline_asm_ctx);
990 : }
991 :
992 : tl::optional<LlvmAsmContext>
993 2 : parse_llvm_templates (LlvmAsmContext ctx)
994 : {
995 2 : auto &parser = ctx.parser;
996 :
997 2 : auto token = parser.peek_current_token ();
998 :
999 2 : if (token->get_id () == ctx.last_token_id
1000 2 : || token->get_id () != STRING_LITERAL)
1001 : {
1002 0 : return tl::nullopt;
1003 : }
1004 :
1005 4 : ctx.llvm_asm.get_templates ().emplace_back (token->get_locus (),
1006 4 : strip_double_quotes (
1007 2 : token->as_string ()));
1008 2 : ctx.parser.skip_token ();
1009 :
1010 2 : token = parser.peek_current_token ();
1011 2 : if (token->get_id () != ctx.last_token_id && token->get_id () != COLON
1012 2 : && token->get_id () != SCOPE_RESOLUTION)
1013 : {
1014 : // We do not handle multiple template string, we provide minimal support
1015 : // for the black_box intrinsics.
1016 0 : rust_unreachable ();
1017 : }
1018 :
1019 2 : return ctx;
1020 2 : }
1021 :
1022 : tl::optional<LlvmAsmContext>
1023 2 : parse_llvm_arguments (LlvmAsmContext ctx)
1024 : {
1025 2 : auto &parser = ctx.parser;
1026 2 : enum State
1027 : {
1028 : Templates = 0,
1029 : Output,
1030 : Input,
1031 : Clobbers,
1032 : Options
1033 2 : } current_state
1034 : = State::Templates;
1035 :
1036 2 : while (parser.peek_current_token ()->get_id () != ctx.last_token_id
1037 40 : && parser.peek_current_token ()->get_id () != END_OF_FILE)
1038 : {
1039 16 : if (parser.peek_current_token ()->get_id () == SCOPE_RESOLUTION)
1040 : {
1041 0 : parser.skip_token (SCOPE_RESOLUTION);
1042 0 : current_state = static_cast<State> (current_state + 2);
1043 : }
1044 : else
1045 : {
1046 8 : parser.skip_token (COLON);
1047 8 : current_state = static_cast<State> (current_state + 1);
1048 : }
1049 :
1050 8 : switch (current_state)
1051 : {
1052 2 : case State::Output:
1053 2 : parse_llvm_outputs (ctx);
1054 2 : break;
1055 2 : case State::Input:
1056 2 : parse_llvm_inputs (ctx);
1057 2 : break;
1058 2 : case State::Clobbers:
1059 2 : parse_llvm_clobbers (ctx);
1060 2 : break;
1061 2 : case State::Options:
1062 2 : parse_llvm_options (ctx);
1063 2 : break;
1064 0 : case State::Templates:
1065 0 : default:
1066 0 : rust_unreachable ();
1067 : }
1068 : }
1069 :
1070 2 : return ctx;
1071 : }
1072 :
1073 : void
1074 4 : parse_llvm_operands (LlvmAsmContext &ctx, std::vector<AST::LlvmOperand> &result)
1075 : {
1076 4 : auto &parser = ctx.parser;
1077 4 : auto token = parser.peek_current_token ();
1078 6 : while (token->get_id () != COLON && token->get_id () != SCOPE_RESOLUTION
1079 8 : && token->get_id () != ctx.last_token_id)
1080 : {
1081 2 : std::string constraint;
1082 2 : if (token->get_id () == STRING_LITERAL)
1083 : {
1084 2 : constraint = strip_double_quotes (token->as_string ());
1085 : }
1086 2 : parser.skip_token (STRING_LITERAL);
1087 2 : parser.skip_token (LEFT_PAREN);
1088 :
1089 2 : token = parser.peek_current_token ();
1090 :
1091 2 : ParseRestrictions restrictions;
1092 2 : restrictions.expr_can_be_null = true;
1093 2 : auto expr = parser.parse_expr ();
1094 2 : rust_assert (expr);
1095 :
1096 2 : parser.skip_token (RIGHT_PAREN);
1097 :
1098 2 : result.emplace_back (constraint, std::move (expr.value ()));
1099 :
1100 4 : if (parser.peek_current_token ()->get_id () == COMMA)
1101 0 : parser.skip_token (COMMA);
1102 :
1103 2 : token = parser.peek_current_token ();
1104 2 : }
1105 4 : }
1106 :
1107 : void
1108 2 : parse_llvm_outputs (LlvmAsmContext &ctx)
1109 : {
1110 2 : parse_llvm_operands (ctx, ctx.llvm_asm.get_outputs ());
1111 2 : }
1112 :
1113 : void
1114 2 : parse_llvm_inputs (LlvmAsmContext &ctx)
1115 : {
1116 2 : parse_llvm_operands (ctx, ctx.llvm_asm.get_inputs ());
1117 2 : }
1118 :
1119 : void
1120 2 : parse_llvm_clobbers (LlvmAsmContext &ctx)
1121 : {
1122 2 : auto &parser = ctx.parser;
1123 2 : auto token = parser.peek_current_token ();
1124 4 : while (token->get_id () != COLON && token->get_id () != ctx.last_token_id)
1125 : {
1126 2 : if (token->get_id () == STRING_LITERAL)
1127 : {
1128 6 : ctx.llvm_asm.get_clobbers ().push_back (
1129 4 : {strip_double_quotes (token->as_string ()), token->get_locus ()});
1130 :
1131 2 : parser.skip_token (STRING_LITERAL);
1132 : }
1133 :
1134 2 : parser.maybe_skip_token (COMMA);
1135 2 : token = parser.peek_current_token ();
1136 : }
1137 2 : }
1138 :
1139 : void
1140 2 : parse_llvm_options (LlvmAsmContext &ctx)
1141 : {
1142 2 : auto &parser = ctx.parser;
1143 2 : auto token = parser.peek_current_token ();
1144 :
1145 4 : while (token->get_id () != ctx.last_token_id)
1146 : {
1147 2 : if (token->get_id () == STRING_LITERAL)
1148 : {
1149 2 : auto token_str = strip_double_quotes (token->as_string ());
1150 :
1151 2 : if (token_str == "volatile")
1152 2 : ctx.llvm_asm.set_volatile (true);
1153 0 : else if (token_str == "alignstack")
1154 0 : ctx.llvm_asm.set_align_stack (true);
1155 0 : else if (token_str == "intel")
1156 0 : ctx.llvm_asm.set_dialect (AST::LlvmInlineAsm::Dialect::Intel);
1157 : else
1158 0 : rust_error_at (token->get_locus (),
1159 : "Unknown llvm assembly option %qs",
1160 : token_str.c_str ());
1161 2 : }
1162 2 : parser.skip_token (STRING_LITERAL);
1163 :
1164 2 : token = parser.peek_current_token ();
1165 :
1166 2 : if (token->get_id () == ctx.last_token_id)
1167 2 : continue;
1168 0 : parser.skip_token (COMMA);
1169 : }
1170 :
1171 2 : parser.skip_token ();
1172 2 : }
1173 :
1174 : tl::optional<AST::Fragment>
1175 2 : parse_llvm_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
1176 : AST::InvocKind semicolon, AST::AsmKind is_global_asm)
1177 : {
1178 2 : MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ());
1179 2 : Parser<MacroInvocLexer> parser (lex);
1180 2 : auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser);
1181 :
1182 2 : AST::LlvmInlineAsm llvm_asm{invoc_locus};
1183 :
1184 2 : auto asm_ctx = LlvmAsmContext (llvm_asm, parser, last_token_id);
1185 :
1186 2 : tl::optional<LlvmAsmContext> resulting_context
1187 2 : = parse_llvm_templates (asm_ctx).and_then (parse_llvm_arguments);
1188 :
1189 2 : if (resulting_context)
1190 : {
1191 2 : auto resulting_ctx = resulting_context.value ();
1192 2 : auto node = resulting_ctx.llvm_asm.clone_expr_without_block ();
1193 :
1194 2 : std::vector<AST::SingleASTNode> single_vec = {};
1195 :
1196 : // If the macro invocation has a semicolon (`asm!("...");`), then we
1197 : // need to make it a statement. This way, it will be expanded
1198 : // properly.
1199 2 : if (semicolon == AST::InvocKind::Semicoloned)
1200 : {
1201 2 : single_vec.emplace_back (
1202 2 : std::make_unique<AST::ExprStmt> (std::move (node), invoc_locus,
1203 4 : true /* has semicolon */));
1204 : }
1205 : else
1206 0 : single_vec.emplace_back (std::move (node));
1207 :
1208 2 : AST::Fragment fragment_ast
1209 : = AST::Fragment (single_vec,
1210 2 : std::vector<std::unique_ptr<AST::Token>> ());
1211 2 : return fragment_ast;
1212 2 : }
1213 0 : return tl::nullopt;
1214 4 : }
1215 :
1216 : } // namespace Rust
|