use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ExprKind, LitIntType, LitKind, UintTy, token}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_session::errors::report_lit_error; use rustc_span::{ErrorGuaranteed, Span}; use crate::errors; use crate::util::get_exprs_from_tts; /// Emits errors for literal expressions that are invalid inside and outside of an array. fn invalid_type_err( cx: &ExtCtxt<'_>, token_lit: token::Lit, span: Span, is_nested: bool, ) -> ErrorGuaranteed { use errors::{ ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob, }; let snippet = cx.sess.source_map().span_to_snippet(span).ok(); let dcx = cx.dcx(); match LitKind::from_token_lit(token_lit) { Ok(LitKind::CStr(_, _)) => { // Avoid ambiguity in handling of terminal `NUL` by refusing to // concatenate C string literals as bytes. dcx.emit_err(errors::ConcatCStrLit { span }) } Ok(LitKind::Char(_)) => { let sugg = snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet }); dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg }) } Ok(LitKind::Str(_, _)) => { // suggestion would be invalid if we are nested let sugg = if !is_nested { snippet.map(|snippet| ConcatBytesInvalidSuggestion::StrLit { span, snippet }) } else { None }; dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg }) } Ok(LitKind::Float(_, _)) => { dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None }) } Ok(LitKind::Bool(_)) => { dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None }) } Ok(LitKind::Int(_, _)) if !is_nested => { let sugg = snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span, snippet }); dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg }) } Ok(LitKind::Int(val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8))) => { assert!(val.get() > u8::MAX.into()); // must be an error dcx.emit_err(ConcatBytesOob { span }) } Ok(LitKind::Int(_, _)) => dcx.emit_err(ConcatBytesNonU8 { span }), Ok(LitKind::ByteStr(..) | LitKind::Byte(_)) => unreachable!(), Ok(LitKind::Err(guar)) => guar, Err(err) => report_lit_error(&cx.sess.psess, err, token_lit, span), } } /// Returns `expr` as a *single* byte literal if applicable. /// /// Otherwise, returns `None`, and either pushes the `expr`'s span to `missing_literals` or /// updates `guar` accordingly. fn handle_array_element( cx: &ExtCtxt<'_>, guar: &mut Option, missing_literals: &mut Vec, expr: &P, ) -> Option { let dcx = cx.dcx(); match expr.kind { ExprKind::Lit(token_lit) => { match LitKind::from_token_lit(token_lit) { Ok(LitKind::Int( val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8), )) if let Ok(val) = u8::try_from(val.get()) => { return Some(val); } Ok(LitKind::Byte(val)) => return Some(val), Ok(LitKind::ByteStr(..)) => { guar.get_or_insert_with(|| { dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true }) }); } _ => { guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, expr.span, true)); } }; } ExprKind::Array(_) | ExprKind::Repeat(_, _) => { guar.get_or_insert_with(|| { dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }) }); } ExprKind::IncludedBytes(..) => { guar.get_or_insert_with(|| { dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false }) }); } _ => missing_literals.push(expr.span), } None } pub(crate) fn expand_concat_bytes( cx: &mut ExtCtxt<'_>, sp: Span, tts: TokenStream, ) -> MacroExpanderResult<'static> { let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else { return ExpandResult::Retry(()); }; let es = match mac { Ok(es) => es, Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), }; let mut accumulator = Vec::new(); let mut missing_literals = vec![]; let mut guar = None; for e in es { match &e.kind { ExprKind::Array(exprs) => { for expr in exprs { if let Some(elem) = handle_array_element(cx, &mut guar, &mut missing_literals, expr) { accumulator.push(elem); } } } ExprKind::Repeat(expr, count) => { if let ExprKind::Lit(token_lit) = count.value.kind && let Ok(LitKind::Int(count_val, _)) = LitKind::from_token_lit(token_lit) { if let Some(elem) = handle_array_element(cx, &mut guar, &mut missing_literals, expr) { for _ in 0..count_val.get() { accumulator.push(elem); } } } else { guar = Some( cx.dcx().emit_err(errors::ConcatBytesBadRepeat { span: count.value.span }), ); } } &ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) { Ok(LitKind::Byte(val)) => { accumulator.push(val); } Ok(LitKind::ByteStr(ref bytes, _)) => { accumulator.extend_from_slice(bytes); } _ => { guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false)); } }, ExprKind::IncludedBytes(bytes) => { accumulator.extend_from_slice(bytes); } ExprKind::Err(guarantee) => { guar = Some(*guarantee); } ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"), _ => { missing_literals.push(e.span); } } } ExpandResult::Ready(if !missing_literals.is_empty() { let guar = cx.dcx().emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals }); MacEager::expr(DummyResult::raw_expr(sp, Some(guar))) } else if let Some(guar) = guar { MacEager::expr(DummyResult::raw_expr(sp, Some(guar))) } else { let sp = cx.with_def_site_ctxt(sp); MacEager::expr(cx.expr_byte_str(sp, accumulator)) }) }