| use rustc_ast::ptr::P; |
| use rustc_ast::tokenstream::TokenStream; |
| use rustc_ast::{ExprKind, LitIntType, LitKind, StrStyle, 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(_, style)) => { |
| // Avoid ambiguity in handling of terminal `NUL` by refusing to |
| // concatenate C string literals as bytes. |
| let sugg = if let Some(mut as_bstr) = snippet |
| && style == StrStyle::Cooked |
| && as_bstr.starts_with('c') |
| && as_bstr.ends_with('"') |
| { |
| // Suggest`c"foo"` -> `b"foo\0"` if we can |
| as_bstr.replace_range(0..1, "b"); |
| as_bstr.pop(); |
| as_bstr.push_str(r#"\0""#); |
| Some(ConcatBytesInvalidSuggestion::CStrLit { span, as_bstr }) |
| } else { |
| // No suggestion for a missing snippet, raw strings, or if for some reason we have |
| // a span that doesn't match `c"foo"` (possible if a proc macro assigns a span |
| // that doesn't actually point to a C string). |
| None |
| }; |
| // We can only provide a suggestion if we have a snip and it is not a raw string |
| dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "C string", sugg, cs_note: Some(()) }) |
| } |
| Ok(LitKind::Char(_)) => { |
| let sugg = |
| snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet }); |
| dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg, cs_note: None }) |
| } |
| 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, cs_note: None }) |
| } |
| Ok(LitKind::Float(_, _)) => { |
| dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None, cs_note: None }) |
| } |
| Ok(LitKind::Bool(_)) => dcx.emit_err(ConcatBytesInvalid { |
| span, |
| lit_kind: "boolean", |
| sugg: None, |
| cs_note: 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, cs_note: None }) |
| } |
| 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<ErrorGuaranteed>, |
| missing_literals: &mut Vec<rustc_span::Span>, |
| expr: &P<rustc_ast::Expr>, |
| ) -> Option<u8> { |
| 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 byte_sym, _)) => { |
| accumulator.extend_from_slice(byte_sym.as_byte_str()); |
| } |
| _ => { |
| guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false)); |
| } |
| }, |
| ExprKind::IncludedBytes(byte_sym) => { |
| accumulator.extend_from_slice(byte_sym.as_byte_str()); |
| } |
| 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)) |
| }) |
| } |