Skip to main content

rustc_builtin_macros/
concat_bytes.rs

1use rustc_ast::tokenstream::TokenStream;
2use rustc_ast::{ExprKind, LitIntType, LitKind, StrStyle, UintTy, token};
3use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
4use rustc_session::errors::report_lit_error;
5use rustc_span::{ErrorGuaranteed, Span};
6
7use crate::diagnostics;
8use crate::util::get_exprs_from_tts;
9
10/// Emits errors for literal expressions that are invalid inside and outside of an array.
11fn invalid_type_err(
12    cx: &ExtCtxt<'_>,
13    token_lit: token::Lit,
14    span: Span,
15    is_nested: bool,
16) -> ErrorGuaranteed {
17    use diagnostics::{
18        ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob,
19    };
20    let snippet = cx.sess.source_map().span_to_snippet(span).ok();
21    let dcx = cx.dcx();
22    match LitKind::from_token_lit(token_lit) {
23        Ok(LitKind::CStr(_, style)) => {
24            // Avoid ambiguity in handling of terminal `NUL` by refusing to
25            // concatenate C string literals as bytes.
26            let sugg = if let Some(mut as_bstr) = snippet
27                && style == StrStyle::Cooked
28                && as_bstr.starts_with('c')
29                && as_bstr.ends_with('"')
30            {
31                // Suggest`c"foo"` -> `b"foo\0"` if we can
32                as_bstr.replace_range(0..1, "b");
33                as_bstr.pop();
34                as_bstr.push_str(r#"\0""#);
35                Some(ConcatBytesInvalidSuggestion::CStrLit { span, as_bstr })
36            } else {
37                // No suggestion for a missing snippet, raw strings, or if for some reason we have
38                // a span that doesn't match `c"foo"` (possible if a proc macro assigns a span
39                // that doesn't actually point to a C string).
40                None
41            };
42            // We can only provide a suggestion if we have a snip and it is not a raw string
43            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "C string", sugg, cs_note: Some(()) })
44        }
45        Ok(LitKind::Char(_)) => {
46            let sugg =
47                snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet });
48            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg, cs_note: None })
49        }
50        Ok(LitKind::Str(_, _)) => {
51            // suggestion would be invalid if we are nested
52            let sugg = if !is_nested {
53                snippet.map(|snippet| ConcatBytesInvalidSuggestion::StrLit { span, snippet })
54            } else {
55                None
56            };
57            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg, cs_note: None })
58        }
59        Ok(LitKind::Float(_, _)) => {
60            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None, cs_note: None })
61        }
62        Ok(LitKind::Bool(_)) => dcx.emit_err(ConcatBytesInvalid {
63            span,
64            lit_kind: "boolean",
65            sugg: None,
66            cs_note: None,
67        }),
68        Ok(LitKind::Int(_, _)) if !is_nested => {
69            let sugg =
70                snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span, snippet });
71            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg, cs_note: None })
72        }
73        Ok(LitKind::Int(val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8))) => {
74            if !(val.get() > u8::MAX.into()) {
    ::core::panicking::panic("assertion failed: val.get() > u8::MAX.into()")
};assert!(val.get() > u8::MAX.into()); // must be an error
75            dcx.emit_err(ConcatBytesOob { span })
76        }
77        Ok(LitKind::Int(_, _)) => dcx.emit_err(ConcatBytesNonU8 { span }),
78        Ok(LitKind::ByteStr(..) | LitKind::Byte(_)) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
79        Ok(LitKind::Err(guar)) => guar,
80        Err(err) => report_lit_error(&cx.sess.psess, err, token_lit, span),
81    }
82}
83
84/// Returns `expr` as a *single* byte literal if applicable.
85///
86/// Otherwise, returns `None`, and either pushes the `expr`'s span to `missing_literals` or
87/// updates `guar` accordingly.
88fn handle_array_element(
89    cx: &ExtCtxt<'_>,
90    guar: &mut Option<ErrorGuaranteed>,
91    missing_literals: &mut Vec<rustc_span::Span>,
92    expr: &Box<rustc_ast::Expr>,
93) -> Option<u8> {
94    let dcx = cx.dcx();
95
96    match expr.kind {
97        ExprKind::Lit(token_lit) => {
98            match LitKind::from_token_lit(token_lit) {
99                Ok(LitKind::Int(
100                    val,
101                    LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8),
102                )) if let Ok(val) = u8::try_from(val.get()) => {
103                    return Some(val);
104                }
105                Ok(LitKind::Byte(val)) => return Some(val),
106                Ok(LitKind::ByteStr(..)) => {
107                    guar.get_or_insert_with(|| {
108                        dcx.emit_err(diagnostics::ConcatBytesArray {
109                            span: expr.span,
110                            bytestr: true,
111                        })
112                    });
113                }
114                _ => {
115                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, expr.span, true));
116                }
117            };
118        }
119        ExprKind::Array(_) | ExprKind::Repeat(_, _) => {
120            guar.get_or_insert_with(|| {
121                dcx.emit_err(diagnostics::ConcatBytesArray { span: expr.span, bytestr: false })
122            });
123        }
124        ExprKind::IncludedBytes(..) => {
125            guar.get_or_insert_with(|| {
126                dcx.emit_err(diagnostics::ConcatBytesArray { span: expr.span, bytestr: false })
127            });
128        }
129        _ => missing_literals.push(expr.span),
130    }
131
132    None
133}
134
135pub(crate) fn expand_concat_bytes(
136    cx: &mut ExtCtxt<'_>,
137    sp: Span,
138    tts: TokenStream,
139) -> MacroExpanderResult<'static> {
140    let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
141        return ExpandResult::Retry(());
142    };
143    let es = match mac {
144        Ok(es) => es,
145        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
146    };
147    let mut accumulator = Vec::new();
148    let mut missing_literals = ::alloc::vec::Vec::new()vec![];
149    let mut guar = None;
150    for e in es {
151        match &e.kind {
152            ExprKind::Array(exprs) => {
153                for expr in exprs {
154                    if let Some(elem) =
155                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
156                    {
157                        accumulator.push(elem);
158                    }
159                }
160            }
161            ExprKind::Repeat(expr, count) => {
162                if let ExprKind::Lit(token_lit) = count.value.kind
163                    && let Ok(LitKind::Int(count_val, _)) = LitKind::from_token_lit(token_lit)
164                {
165                    if let Some(elem) =
166                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
167                    {
168                        for _ in 0..count_val.get() {
169                            accumulator.push(elem);
170                        }
171                    }
172                } else {
173                    guar =
174                        Some(cx.dcx().emit_err(diagnostics::ConcatBytesBadRepeat {
175                            span: count.value.span,
176                        }));
177                }
178            }
179            &ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) {
180                Ok(LitKind::Byte(val)) => {
181                    accumulator.push(val);
182                }
183                Ok(LitKind::ByteStr(ref byte_sym, _)) => {
184                    accumulator.extend_from_slice(byte_sym.as_byte_str());
185                }
186                _ => {
187                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false));
188                }
189            },
190            ExprKind::IncludedBytes(byte_sym) => {
191                accumulator.extend_from_slice(byte_sym.as_byte_str());
192            }
193            ExprKind::Err(guarantee) => {
194                guar = Some(*guarantee);
195            }
196            ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"),
197            _ => {
198                missing_literals.push(e.span);
199            }
200        }
201    }
202    ExpandResult::Ready(if !missing_literals.is_empty() {
203        let guar =
204            cx.dcx().emit_err(diagnostics::ConcatBytesMissingLiteral { spans: missing_literals });
205        MacEager::expr(DummyResult::raw_expr(sp, Some(guar)))
206    } else if let Some(guar) = guar {
207        MacEager::expr(DummyResult::raw_expr(sp, Some(guar)))
208    } else {
209        let sp = cx.with_def_site_ctxt(sp);
210        MacEager::expr(cx.expr_byte_str(sp, accumulator))
211    })
212}