rustc_builtin_macros/
concat_bytes.rs

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