Use token::Lit in ast::ExprKind::Lit.

Instead of `ast::Lit`.

Literal lowering now happens at two different times. Expression literals
are lowered when HIR is crated. Attribute literals are lowered during
parsing.

This commit changes the language very slightly. Some programs that used
to not compile now will compile. This is because some invalid literals
that are removed by `cfg` or attribute macros will no longer trigger
errors. See this comment for more details:
https://github.com/rust-lang/rust/pull/102944#issuecomment-1277476773
This commit is contained in:
Nicholas Nethercote 2022-10-10 13:40:56 +11:00
parent 01760265cb
commit 358a603f11
45 changed files with 701 additions and 581 deletions

View File

@ -1332,7 +1332,7 @@ pub enum ExprKind {
/// A unary operation (e.g., `!x`, `*x`).
Unary(UnOp, P<Expr>),
/// A literal (e.g., `1`, `"foo"`).
Lit(Lit),
Lit(token::Lit),
/// A cast (e.g., `foo as f64`).
Cast(P<Expr>, P<Ty>),
/// A type ascription (e.g., `42: usize`).
@ -1698,16 +1698,12 @@ pub struct StrLit {
}
impl StrLit {
pub fn as_lit(&self) -> Lit {
pub fn as_token_lit(&self) -> token::Lit {
let token_kind = match self.style {
StrStyle::Cooked => token::Str,
StrStyle::Raw(n) => token::StrRaw(n),
};
Lit {
token_lit: token::Lit::new(token_kind, self.symbol, self.suffix),
span: self.span,
kind: LitKind::Str(self.symbol_unescaped, self.style),
}
token::Lit::new(token_kind, self.symbol, self.suffix)
}
}
@ -1733,9 +1729,10 @@ pub enum LitFloatType {
Unsuffixed,
}
/// Literal kind.
///
/// E.g., `"foo"`, `42`, `12.34`, or `bool`.
/// Note that the entire literal (including the suffix) is considered when
/// deciding the `LitKind`. This means that float literals like `1f32` are
/// classified by this type as `Float`. This is different to `token::LitKind`
/// which does *not* consider the suffix.
#[derive(Clone, Encodable, Decodable, Debug, Hash, Eq, PartialEq, HashStable_Generic)]
pub enum LitKind {
/// A string literal (`"foo"`). The symbol is unescaped, and so may differ
@ -1749,10 +1746,11 @@ pub enum LitKind {
Char(char),
/// An integer literal (`1`).
Int(u128, LitIntType),
/// A float literal (`1f64` or `1E10f64`). Stored as a symbol rather than
/// `f64` so that `LitKind` can impl `Eq` and `Hash`.
/// A float literal (`1.0`, `1f64` or `1E10f64`). The pre-suffix part is
/// stored as a symbol rather than `f64` so that `LitKind` can impl `Eq`
/// and `Hash`.
Float(Symbol, LitFloatType),
/// A boolean literal.
/// A boolean literal (`true`, `false`).
Bool(bool),
/// Placeholder for a literal that wasn't well-formed in some way.
Err,

View File

@ -533,7 +533,7 @@ impl MetaItemKind {
MetaItemKind::NameValue(lit) => {
let expr = P(ast::Expr {
id: ast::DUMMY_NODE_ID,
kind: ast::ExprKind::Lit(lit.clone()),
kind: ast::ExprKind::Lit(lit.token_lit.clone()),
span: lit.span,
attrs: ast::AttrVec::new(),
tokens: None,
@ -605,7 +605,7 @@ impl MetaItemKind {
MetaItemKind::name_value_from_tokens(&mut inner_tokens.into_trees())
}
Some(TokenTree::Token(token, _)) => {
Lit::from_token(&token).ok().map(MetaItemKind::NameValue)
Lit::from_token(&token).map(MetaItemKind::NameValue)
}
_ => None,
}
@ -618,8 +618,10 @@ impl MetaItemKind {
MetaItemKind::list_from_tokens(tokens.clone())
}
MacArgs::Delimited(..) => None,
MacArgs::Eq(_, MacArgsEq::Ast(expr)) => match &expr.kind {
ast::ExprKind::Lit(lit) => Some(MetaItemKind::NameValue(lit.clone())),
MacArgs::Eq(_, MacArgsEq::Ast(expr)) => match expr.kind {
ast::ExprKind::Lit(token_lit) => Some(MetaItemKind::NameValue(
Lit::from_token_lit(token_lit, expr.span).expect("token_lit in from_mac_args"),
)),
_ => None,
},
MacArgs::Eq(_, MacArgsEq::Hir(lit)) => Some(MetaItemKind::NameValue(lit.clone())),
@ -668,7 +670,7 @@ impl NestedMetaItem {
{
match tokens.peek() {
Some(TokenTree::Token(token, _))
if let Ok(lit) = Lit::from_token(token) =>
if let Some(lit) = Lit::from_token(token) =>
{
tokens.next();
return Some(NestedMetaItem::Literal(lit));

View File

@ -59,13 +59,17 @@ pub enum Delimiter {
Invisible,
}
// Note that the suffix is *not* considered when deciding the `LitKind` in this
// type. This means that float literals like `1f32` are classified by this type
// as `Int`. Only upon conversion to `ast::LitKind` will such a literal be
// given the `Float` kind.
#[derive(Clone, Copy, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum LitKind {
Bool, // AST only, must never appear in a `Token`
Byte,
Char,
Integer,
Float,
Integer, // e.g. `1`, `1u8`, `1f32`
Float, // e.g. `1.`, `1.0`, `1e3f32`
Str,
StrRaw(u8), // raw string delimited by `n` hash symbols
ByteStr,
@ -81,6 +85,42 @@ pub struct Lit {
pub suffix: Option<Symbol>,
}
impl Lit {
pub fn new(kind: LitKind, symbol: Symbol, suffix: Option<Symbol>) -> Lit {
Lit { kind, symbol, suffix }
}
/// Returns `true` if this is semantically a float literal. This includes
/// ones like `1f32` that have an `Integer` kind but a float suffix.
pub fn is_semantic_float(&self) -> bool {
match self.kind {
LitKind::Float => true,
LitKind::Integer => match self.suffix {
Some(sym) => sym == sym::f32 || sym == sym::f64,
None => false,
},
_ => false,
}
}
/// Keep this in sync with `Token::can_begin_literal_or_bool` excluding unary negation.
pub fn from_token(token: &Token) -> Option<Lit> {
match token.uninterpolate().kind {
Ident(name, false) if name.is_bool_lit() => {
Some(Lit::new(Bool, name, None))
}
Literal(token_lit) => Some(token_lit),
Interpolated(ref nt)
if let NtExpr(expr) | NtLiteral(expr) = &**nt
&& let ast::ExprKind::Lit(token_lit) = expr.kind =>
{
Some(token_lit.clone())
}
_ => None,
}
}
}
impl fmt::Display for Lit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Lit { kind, symbol, suffix } = *self;
@ -139,12 +179,6 @@ impl LitKind {
}
}
impl Lit {
pub fn new(kind: LitKind, symbol: Symbol, suffix: Option<Symbol>) -> Lit {
Lit { kind, symbol, suffix }
}
}
pub fn ident_can_begin_expr(name: Symbol, span: Span, is_raw: bool) -> bool {
let ident_token = Token::new(Ident(name, is_raw), span);

View File

@ -8,8 +8,8 @@ use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;
use std::ascii;
#[derive(Debug)]
pub enum LitError {
NotLiteral,
LexerError,
InvalidSuffix,
InvalidIntSuffix,
@ -202,27 +202,10 @@ impl Lit {
Ok(Lit { token_lit, kind: LitKind::from_token_lit(token_lit)?, span })
}
/// Converts arbitrary token into an AST literal.
///
/// Keep this in sync with `Token::can_begin_literal_or_bool` excluding unary negation.
pub fn from_token(token: &Token) -> Result<Lit, LitError> {
let lit = match token.uninterpolate().kind {
token::Ident(name, false) if name.is_bool_lit() => {
token::Lit::new(token::Bool, name, None)
}
token::Literal(lit) => lit,
token::Interpolated(ref nt) => {
if let token::NtExpr(expr) | token::NtLiteral(expr) = &**nt
&& let ast::ExprKind::Lit(lit) = &expr.kind
{
return Ok(lit.clone());
}
return Err(LitError::NotLiteral);
}
_ => return Err(LitError::NotLiteral),
};
Lit::from_token_lit(lit, token.span)
/// Converts an arbitrary token into an AST literal.
pub fn from_token(token: &Token) -> Option<Lit> {
token::Lit::from_token(token)
.and_then(|token_lit| Lit::from_token_lit(token_lit, token.span).ok())
}
/// Attempts to recover an AST literal from semantic literal.

View File

@ -14,6 +14,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::definitions::DefPathData;
use rustc_session::errors::report_lit_error;
use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned};
use rustc_span::symbol::{sym, Ident};
use rustc_span::DUMMY_SP;
@ -84,8 +85,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
let ohs = self.lower_expr(ohs);
hir::ExprKind::Unary(op, ohs)
}
ExprKind::Lit(ref l) => {
hir::ExprKind::Lit(respan(self.lower_span(l.span), l.kind.clone()))
ExprKind::Lit(token_lit) => {
let lit_kind = match LitKind::from_token_lit(token_lit) {
Ok(lit_kind) => lit_kind,
Err(err) => {
report_lit_error(&self.tcx.sess.parse_sess, err, token_lit, e.span);
LitKind::Err
}
};
hir::ExprKind::Lit(respan(self.lower_span(e.span), lit_kind))
}
ExprKind::IncludedBytes(ref bytes) => hir::ExprKind::Lit(respan(
self.lower_span(e.span),

View File

@ -959,8 +959,15 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
MacArgs::Eq(eq_span, MacArgsEq::Ast(ref expr)) => {
// In valid code the value always ends up as a single literal. Otherwise, a dummy
// literal suffices because the error is handled elsewhere.
let lit = if let ExprKind::Lit(lit) = &expr.kind {
lit.clone()
let lit = if let ExprKind::Lit(token_lit) = expr.kind {
match Lit::from_token_lit(token_lit, expr.span) {
Ok(lit) => lit,
Err(_err) => Lit {
token_lit: token::Lit::new(token::LitKind::Err, kw::Empty, None),
kind: LitKind::Err,
span: DUMMY_SP,
},
}
} else {
Lit {
token_lit: token::Lit::new(token::LitKind::Err, kw::Empty, None),

View File

@ -373,8 +373,12 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
}
fn print_literal(&mut self, lit: &ast::Lit) {
self.maybe_print_comment(lit.span.lo());
self.word(lit.token_lit.to_string())
self.print_token_literal(lit.token_lit, lit.span)
}
fn print_token_literal(&mut self, token_lit: token::Lit, span: Span) {
self.maybe_print_comment(span.lo());
self.word(token_lit.to_string())
}
fn print_string(&mut self, st: &str, style: ast::StrStyle) {
@ -1735,7 +1739,7 @@ impl<'a> State<'a> {
}
ast::Extern::Explicit(abi, _) => {
self.word_nbsp("extern");
self.print_literal(&abi.as_lit());
self.print_token_literal(abi.as_token_lit(), abi.span);
self.nbsp();
}
}

View File

@ -319,8 +319,8 @@ impl<'a> State<'a> {
ast::ExprKind::AddrOf(k, m, ref expr) => {
self.print_expr_addr_of(k, m, expr);
}
ast::ExprKind::Lit(ref lit) => {
self.print_literal(lit);
ast::ExprKind::Lit(token_lit) => {
self.print_token_literal(token_lit, expr.span);
}
ast::ExprKind::IncludedBytes(ref bytes) => {
let lit = ast::Lit::from_included_bytes(bytes, expr.span);

View File

@ -207,7 +207,7 @@ impl<'a> State<'a> {
s.word("extern");
}));
if let Some(abi) = nmod.abi {
self.print_literal(&abi.as_lit());
self.print_token_literal(abi.as_token_lit(), abi.span);
self.nbsp();
}
self.bopen();

View File

@ -172,7 +172,11 @@ pub fn parse_asm_args<'a>(
// If it can't possibly expand to a string, provide diagnostics here to include other
// things it could have been.
match template.kind {
ast::ExprKind::Lit(ast::Lit { kind: ast::LitKind::Str(..), .. }) => {}
ast::ExprKind::Lit(token_lit)
if matches!(
token_lit.kind,
token::LitKind::Str | token::LitKind::StrRaw(_)
) => {}
ast::ExprKind::MacCall(..) => {}
_ => {
let errstr = if is_global_asm {

View File

@ -1,6 +1,7 @@
use rustc_ast as ast;
use rustc_ast::tokenstream::TokenStream;
use rustc_expand::base::{self, DummyResult};
use rustc_session::errors::report_lit_error;
use rustc_span::symbol::Symbol;
use std::string::String;
@ -18,28 +19,28 @@ pub fn expand_concat(
let mut has_errors = false;
for e in es {
match e.kind {
ast::ExprKind::Lit(ref lit) => match lit.kind {
ast::LitKind::Str(ref s, _) | ast::LitKind::Float(ref s, _) => {
ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
Ok(ast::LitKind::Str(ref s, _) | ast::LitKind::Float(ref s, _)) => {
accumulator.push_str(s.as_str());
}
ast::LitKind::Char(c) => {
Ok(ast::LitKind::Char(c)) => {
accumulator.push(c);
}
ast::LitKind::Int(
i,
ast::LitIntType::Unsigned(_)
| ast::LitIntType::Signed(_)
| ast::LitIntType::Unsuffixed,
) => {
Ok(ast::LitKind::Int(i, _)) => {
accumulator.push_str(&i.to_string());
}
ast::LitKind::Bool(b) => {
Ok(ast::LitKind::Bool(b)) => {
accumulator.push_str(&b.to_string());
}
ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..) => {
Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
cx.span_err(e.span, "cannot concatenate a byte string literal");
has_errors = true;
}
ast::LitKind::Err => {
Ok(ast::LitKind::Err) => {
has_errors = true;
}
Err(err) => {
report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span);
has_errors = true;
}
},

View File

@ -2,18 +2,21 @@ use rustc_ast as ast;
use rustc_ast::{ptr::P, tokenstream::TokenStream};
use rustc_errors::Applicability;
use rustc_expand::base::{self, DummyResult};
use rustc_span::Span;
/// Emits errors for literal expressions that are invalid inside and outside of an array.
fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_nested: bool) {
let ast::ExprKind::Lit(lit) = &expr.kind else {
unreachable!();
};
match lit.kind {
ast::LitKind::Char(_) => {
let mut err = cx.struct_span_err(expr.span, "cannot concatenate character literals");
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
fn invalid_type_err(
cx: &mut base::ExtCtxt<'_>,
token_lit: ast::token::Lit,
span: Span,
is_nested: bool,
) {
match ast::LitKind::from_token_lit(token_lit) {
Ok(ast::LitKind::Char(_)) => {
let mut err = cx.struct_span_err(span, "cannot concatenate character literals");
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) {
err.span_suggestion(
expr.span,
span,
"try using a byte character",
format!("b{}", snippet),
Applicability::MachineApplicable,
@ -21,13 +24,13 @@ fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_ne
.emit();
}
}
ast::LitKind::Str(_, _) => {
let mut err = cx.struct_span_err(expr.span, "cannot concatenate string literals");
Ok(ast::LitKind::Str(_, _)) => {
let mut err = cx.struct_span_err(span, "cannot concatenate string literals");
// suggestion would be invalid if we are nested
if !is_nested {
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) {
err.span_suggestion(
expr.span,
span,
"try using a byte string",
format!("b{}", snippet),
Applicability::MachineApplicable,
@ -36,18 +39,18 @@ fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_ne
}
err.emit();
}
ast::LitKind::Float(_, _) => {
cx.span_err(expr.span, "cannot concatenate float literals");
Ok(ast::LitKind::Float(_, _)) => {
cx.span_err(span, "cannot concatenate float literals");
}
ast::LitKind::Bool(_) => {
cx.span_err(expr.span, "cannot concatenate boolean literals");
Ok(ast::LitKind::Bool(_)) => {
cx.span_err(span, "cannot concatenate boolean literals");
}
ast::LitKind::Err => {}
ast::LitKind::Int(_, _) if !is_nested => {
let mut err = cx.struct_span_err(expr.span, "cannot concatenate numeric literals");
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
Ok(ast::LitKind::Err) => {}
Ok(ast::LitKind::Int(_, _)) if !is_nested => {
let mut err = cx.struct_span_err(span, "cannot concatenate numeric literals");
if let Ok(snippet) = cx.sess.source_map().span_to_snippet(span) {
err.span_suggestion(
expr.span,
span,
"try wrapping the number in an array",
format!("[{}]", snippet),
Applicability::MachineApplicable,
@ -55,15 +58,15 @@ fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_ne
}
err.emit();
}
ast::LitKind::Int(
Ok(ast::LitKind::Int(
val,
ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
) => {
)) => {
assert!(val > u8::MAX.into()); // must be an error
cx.span_err(expr.span, "numeric literal is out of bounds");
cx.span_err(span, "numeric literal is out of bounds");
}
ast::LitKind::Int(_, _) => {
cx.span_err(expr.span, "numeric literal is not a `u8`");
Ok(ast::LitKind::Int(_, _)) => {
cx.span_err(span, "numeric literal is not a `u8`");
}
_ => unreachable!(),
}
@ -83,14 +86,14 @@ fn handle_array_element(
*has_errors = true;
None
}
ast::ExprKind::Lit(ref lit) => match lit.kind {
ast::LitKind::Int(
ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
Ok(ast::LitKind::Int(
val,
ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
) if val <= u8::MAX.into() => Some(val as u8),
)) if val <= u8::MAX.into() => Some(val as u8),
ast::LitKind::Byte(val) => Some(val),
ast::LitKind::ByteStr(_) => {
Ok(ast::LitKind::Byte(val)) => Some(val),
Ok(ast::LitKind::ByteStr(_)) => {
if !*has_errors {
cx.struct_span_err(expr.span, "cannot concatenate doubly nested array")
.note("byte strings are treated as arrays of bytes")
@ -102,7 +105,7 @@ fn handle_array_element(
}
_ => {
if !*has_errors {
invalid_type_err(cx, expr, true);
invalid_type_err(cx, token_lit, expr.span, true);
}
*has_errors = true;
None
@ -148,9 +151,9 @@ pub fn expand_concat_bytes(
}
}
ast::ExprKind::Repeat(ref expr, ref count) => {
if let ast::ExprKind::Lit(ast::Lit {
kind: ast::LitKind::Int(count_val, _), ..
}) = count.value.kind
if let ast::ExprKind::Lit(token_lit) = count.value.kind
&& let Ok(ast::LitKind::Int(count_val, _)) =
ast::LitKind::from_token_lit(token_lit)
{
if let Some(elem) =
handle_array_element(cx, &mut has_errors, &mut missing_literals, expr)
@ -163,16 +166,16 @@ pub fn expand_concat_bytes(
cx.span_err(count.value.span, "repeat count is not a positive number");
}
}
ast::ExprKind::Lit(ref lit) => match lit.kind {
ast::LitKind::Byte(val) => {
ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
Ok(ast::LitKind::Byte(val)) => {
accumulator.push(val);
}
ast::LitKind::ByteStr(ref bytes) => {
Ok(ast::LitKind::ByteStr(ref bytes)) => {
accumulator.extend_from_slice(&bytes);
}
_ => {
if !has_errors {
invalid_type_err(cx, &e, false);
invalid_type_err(cx, token_lit, e.span, false);
}
has_errors = true;
}

View File

@ -1226,10 +1226,10 @@ pub fn expr_to_spanned_string<'a>(
let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
Err(match expr.kind {
ast::ExprKind::Lit(ref l) => match l.kind {
ast::LitKind::Str(s, style) => return Ok((s, style, expr.span)),
ast::LitKind::ByteStr(_) => {
let mut err = cx.struct_span_err(l.span, err_msg);
ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
Ok(ast::LitKind::Str(s, style)) => return Ok((s, style, expr.span)),
Ok(ast::LitKind::ByteStr(_)) => {
let mut err = cx.struct_span_err(expr.span, err_msg);
let span = expr.span.shrink_to_lo();
err.span_suggestion(
span.with_hi(span.lo() + BytePos(1)),
@ -1239,8 +1239,9 @@ pub fn expr_to_spanned_string<'a>(
);
Some((err, true))
}
ast::LitKind::Err => None,
_ => Some((cx.struct_span_err(l.span, err_msg), false)),
Ok(ast::LitKind::Err) => None,
Err(_) => None,
_ => Some((cx.struct_span_err(expr.span, err_msg), false)),
},
ast::ExprKind::Err => None,
_ => Some((cx.struct_span_err(expr.span, err_msg), false)),

View File

@ -334,8 +334,8 @@ impl<'a> ExtCtxt<'a> {
}
fn expr_lit(&self, span: Span, lit_kind: ast::LitKind) -> P<ast::Expr> {
let lit = ast::Lit::from_lit_kind(lit_kind, span);
self.expr(span, ast::ExprKind::Lit(lit))
let token_lit = lit_kind.to_token_lit();
self.expr(span, ast::ExprKind::Lit(token_lit))
}
pub fn expr_usize(&self, span: Span, i: usize) -> P<ast::Expr> {

View File

@ -516,14 +516,14 @@ impl server::TokenStream for Rustc<'_, '_> {
// We don't use `TokenStream::from_ast` as the tokenstream currently cannot
// be recovered in the general case.
match &expr.kind {
ast::ExprKind::Lit(l) if l.token_lit.kind == token::Bool => {
ast::ExprKind::Lit(token_lit) if token_lit.kind == token::Bool => {
Ok(tokenstream::TokenStream::token_alone(
token::Ident(l.token_lit.symbol, false),
l.span,
token::Ident(token_lit.symbol, false),
expr.span,
))
}
ast::ExprKind::Lit(l) => {
Ok(tokenstream::TokenStream::token_alone(token::Literal(l.token_lit), l.span))
ast::ExprKind::Lit(token_lit) => {
Ok(tokenstream::TokenStream::token_alone(token::Literal(*token_lit), expr.span))
}
ast::ExprKind::IncludedBytes(bytes) => {
let lit = ast::Lit::from_included_bytes(bytes, expr.span);
@ -533,16 +533,13 @@ impl server::TokenStream for Rustc<'_, '_> {
))
}
ast::ExprKind::Unary(ast::UnOp::Neg, e) => match &e.kind {
ast::ExprKind::Lit(l) => match l.token_lit {
ast::ExprKind::Lit(token_lit) => match token_lit {
token::Lit { kind: token::Integer | token::Float, .. } => {
Ok(Self::TokenStream::from_iter([
// FIXME: The span of the `-` token is lost when
// parsing, so we cannot faithfully recover it here.
tokenstream::TokenTree::token_alone(token::BinOp(token::Minus), e.span),
tokenstream::TokenTree::token_alone(
token::Literal(l.token_lit),
l.span,
),
tokenstream::TokenTree::token_alone(token::Literal(*token_lit), e.span),
]))
}
_ => Err(()),

View File

@ -167,11 +167,15 @@ pub enum DocStyle {
Inner,
}
// Note that the suffix is *not* considered when deciding the `LiteralKind` in
// this type. This means that float literals like `1f32` are classified by this
// type as `Int`. (Compare against `rustc_ast::token::LitKind` and
// `rustc_ast::ast::LitKind.)
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LiteralKind {
/// "12_u8", "0o100", "0b120i99"
/// "12_u8", "0o100", "0b120i99", "1f32".
Int { base: Base, empty_int: bool },
/// "12.34f32", "0b100.100"
/// "12.34f32", "1e3", but not "1f32`.
Float { base: Base, empty_exponent: bool },
/// "'a'", "'\\'", "'''", "';"
Char { terminated: bool },

View File

@ -98,9 +98,10 @@ fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
impl EarlyLintPass for WhileTrue {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if let ast::ExprKind::While(cond, _, label) = &e.kind
&& let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind
&& let ast::LitKind::Bool(true) = lit.kind
&& !lit.span.from_expansion()
&& let cond = pierce_parens(cond)
&& let ast::ExprKind::Lit(token_lit) = cond.kind
&& let token::Lit { kind: token::Bool, symbol: kw::True, .. } = token_lit
&& !cond.span.from_expansion()
{
let condition_span = e.span.with_hi(cond.span.hi());
cx.struct_span_lint(

View File

@ -123,23 +123,22 @@ impl EarlyLintPass for HiddenUnicodeCodepoints {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
// byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString`
let (text, span, padding) = match &expr.kind {
ast::ExprKind::Lit(ast::Lit { token_lit, kind, span }) => {
match &expr.kind {
ast::ExprKind::Lit(token_lit) => {
let text = token_lit.symbol;
if !contains_text_flow_control_chars(text.as_str()) {
return;
}
let padding = match kind {
let padding = match token_lit.kind {
// account for `"` or `'`
ast::LitKind::Str(_, ast::StrStyle::Cooked) | ast::LitKind::Char(_) => 1,
ast::token::LitKind::Str | ast::token::LitKind::Char => 1,
// account for `r###"`
ast::LitKind::Str(_, ast::StrStyle::Raw(val)) => *val as u32 + 2,
ast::token::LitKind::StrRaw(n) => n as u32 + 2,
_ => return,
};
(text, span, padding)
self.lint_text_direction_codepoint(cx, text, expr.span, padding, true, "literal");
}
_ => return,
_ => {}
};
self.lint_text_direction_codepoint(cx, text, *span, padding, true, "literal");
}
}

View File

@ -304,61 +304,6 @@ pub(crate) struct FloatLiteralRequiresIntegerPart {
pub correct: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_int_literal_width)]
#[help]
pub(crate) struct InvalidIntLiteralWidth {
#[primary_span]
pub span: Span,
pub width: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_num_literal_base_prefix)]
#[note]
pub(crate) struct InvalidNumLiteralBasePrefix {
#[primary_span]
#[suggestion(applicability = "maybe-incorrect", code = "{fixed}")]
pub span: Span,
pub fixed: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_num_literal_suffix)]
#[help]
pub(crate) struct InvalidNumLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
pub suffix: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_float_literal_width)]
#[help]
pub(crate) struct InvalidFloatLiteralWidth {
#[primary_span]
pub span: Span,
pub width: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_float_literal_suffix)]
#[help]
pub(crate) struct InvalidFloatLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
pub suffix: String,
}
#[derive(Diagnostic)]
#[diag(parser_int_literal_too_large)]
pub(crate) struct IntLiteralTooLarge {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_missing_semicolon_before_array)]
pub(crate) struct MissingSemicolonBeforeArray {
@ -740,41 +685,6 @@ pub(crate) struct InvalidInterpolatedExpression {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_hexadecimal_float_literal_not_supported)]
pub(crate) struct HexadecimalFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_octal_float_literal_not_supported)]
pub(crate) struct OctalFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_binary_float_literal_not_supported)]
pub(crate) struct BinaryFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_literal_suffix)]
pub(crate) struct InvalidLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
// FIXME(#100717)
pub kind: String,
pub suffix: Symbol,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_literal_suffix_on_tuple_index)]
pub(crate) struct InvalidLiteralSuffixOnTupleIndex {

View File

@ -661,6 +661,7 @@ impl<'a> StringReader<'a> {
prefix_len: u32,
postfix_len: u32,
) -> (token::LitKind, Symbol) {
let mut has_fatal_err = false;
let content_start = start + BytePos(prefix_len);
let content_end = end - BytePos(postfix_len);
let lit_content = self.str_from_to(content_start, content_end);
@ -672,6 +673,9 @@ impl<'a> StringReader<'a> {
let lo = content_start + BytePos(start);
let hi = lo + BytePos(end - start);
let span = self.mk_sp(lo, hi);
if err.is_fatal() {
has_fatal_err = true;
}
emit_unescape_error(
&self.sess.span_diagnostic,
lit_content,
@ -683,7 +687,14 @@ impl<'a> StringReader<'a> {
);
}
});
// We normally exclude the quotes for the symbol, but for errors we
// include it because it results in clearer error messages.
if !has_fatal_err {
(kind, Symbol::intern(lit_content))
} else {
(token::Err, self.symbol_from_to(start, end))
}
}
}

View File

@ -316,8 +316,8 @@ impl<'a> Parser<'a> {
}
pub(crate) fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
let lit = self.parse_lit()?;
debug!("checking if {:?} is unusuffixed", lit);
let lit = self.parse_ast_lit()?;
debug!("checking if {:?} is unsuffixed", lit);
if !lit.kind.is_unsuffixed() {
self.sess.emit_err(SuffixedLiteralInAttribute { span: lit.span });

View File

@ -7,35 +7,30 @@ use super::{
};
use crate::errors::{
ArrayBracketsInsteadOfSpaces, ArrayBracketsInsteadOfSpacesSugg, AsyncMoveOrderIncorrect,
BinaryFloatLiteralNotSupported, BracesForStructLiteral, CatchAfterTry, CommaAfterBaseStruct,
ComparisonInterpretedAsGeneric, ComparisonOrShiftInterpretedAsGenericSugg,
DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedEqForLetExpr,
ExpectedExpressionFoundLet, FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart,
FoundExprWouldBeStmt, HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition,
IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub, IntLiteralTooLarge,
BracesForStructLiteral, CatchAfterTry, CommaAfterBaseStruct, ComparisonInterpretedAsGeneric,
ComparisonOrShiftInterpretedAsGenericSugg, DoCatchSyntaxRemoved, DotDotDot, EqFieldInit,
ExpectedElseBlock, ExpectedEqForLetExpr, ExpectedExpressionFoundLet,
FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart, FoundExprWouldBeStmt,
IfExpressionMissingCondition, IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub,
InvalidBlockMacroSegment, InvalidComparisonOperator, InvalidComparisonOperatorSub,
InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth, InvalidIntLiteralWidth,
InvalidInterpolatedExpression, InvalidLiteralSuffix, InvalidLiteralSuffixOnTupleIndex,
InvalidLogicalOperator, InvalidLogicalOperatorSub, InvalidNumLiteralBasePrefix,
InvalidNumLiteralSuffix, LabeledLoopInBreak, LeadingPlusNotSupported, LeftArrowOperator,
InvalidInterpolatedExpression, InvalidLiteralSuffixOnTupleIndex, InvalidLogicalOperator,
InvalidLogicalOperatorSub, LabeledLoopInBreak, LeadingPlusNotSupported, LeftArrowOperator,
LifetimeInBorrowExpression, MacroInvocationWithQualifiedPath, MalformedLoopLabel,
MatchArmBodyWithoutBraces, MatchArmBodyWithoutBracesSugg, MissingCommaAfterMatchArm,
MissingDotDot, MissingInInForLoop, MissingInInForLoopSub, MissingSemicolonBeforeArray,
NoFieldsForFnCall, NotAsNegationOperator, NotAsNegationOperatorSub,
OctalFloatLiteralNotSupported, OuterAttributeNotAllowedOnIfElse, ParenthesesWithStructFields,
OuterAttributeNotAllowedOnIfElse, ParenthesesWithStructFields,
RequireColonAfterLabeledExpression, ShiftInterpretedAsGeneric, StructLiteralNotAllowedHere,
StructLiteralNotAllowedHereSugg, TildeAsUnaryOperator, UnexpectedTokenAfterLabel,
UnexpectedTokenAfterLabelSugg, WrapExpressionInParentheses,
};
use crate::maybe_recover_from_interpolated_ty_qpath;
use core::mem;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::Spacing;
use rustc_ast::util::case::Case;
use rustc_ast::util::classify;
use rustc_ast::util::literal::LitError;
use rustc_ast::util::parser::{prec_let_scrutinee_needs_par, AssocOp, Fixity};
use rustc_ast::visit::Visitor;
use rustc_ast::{self as ast, AttrStyle, AttrVec, CaptureBy, ExprField, Lit, UnOp, DUMMY_NODE_ID};
@ -47,7 +42,7 @@ use rustc_errors::{
Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult,
StashKey,
};
use rustc_session::errors::ExprParenthesesNeeded;
use rustc_session::errors::{report_lit_error, ExprParenthesesNeeded};
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_span::source_map::{self, Span, Spanned};
@ -1415,9 +1410,9 @@ impl<'a> Parser<'a> {
fn parse_lit_expr(&mut self) -> PResult<'a, P<Expr>> {
let lo = self.token.span;
match self.parse_opt_lit() {
Some(literal) => {
let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Lit(literal));
match self.parse_opt_token_lit() {
Some((token_lit, _)) => {
let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Lit(token_lit));
self.maybe_recover_from_bad_qpath(expr)
}
None => self.try_macro_suggestion(),
@ -1548,7 +1543,7 @@ impl<'a> Parser<'a> {
})
});
consume_colon = false;
Ok(self.mk_expr(lo, ExprKind::Lit(lit)))
Ok(self.mk_expr(lo, ExprKind::Lit(lit.token_lit)))
} else if !ate_colon
&& (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt))
{
@ -1625,9 +1620,9 @@ impl<'a> Parser<'a> {
/// Emit an error when a char is parsed as a lifetime because of a missing quote
pub(super) fn recover_unclosed_char(
&mut self,
&self,
lifetime: Ident,
err: impl FnOnce(&mut Self) -> DiagnosticBuilder<'a, ErrorGuaranteed>,
err: impl FnOnce(&Self) -> DiagnosticBuilder<'a, ErrorGuaranteed>,
) -> ast::Lit {
if let Some(mut diag) =
self.sess.span_diagnostic.steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar)
@ -1649,9 +1644,10 @@ impl<'a> Parser<'a> {
)
.emit();
}
let name = lifetime.without_first_quote().name;
ast::Lit {
token_lit: token::Lit::new(token::LitKind::Char, lifetime.name, None),
kind: ast::LitKind::Char(lifetime.name.as_str().chars().next().unwrap_or('_')),
token_lit: token::Lit::new(token::LitKind::Char, name, None),
kind: ast::LitKind::Char(name.as_str().chars().next().unwrap_or('_')),
span: lifetime.span,
}
}
@ -1765,7 +1761,7 @@ impl<'a> Parser<'a> {
/// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind,
/// and returns `None` if the next token is not literal at all.
pub fn parse_str_lit(&mut self) -> Result<ast::StrLit, Option<Lit>> {
match self.parse_opt_lit() {
match self.parse_opt_ast_lit() {
Some(lit) => match lit.kind {
ast::LitKind::Str(symbol_unescaped, style) => Ok(ast::StrLit {
style,
@ -1780,8 +1776,7 @@ impl<'a> Parser<'a> {
}
}
pub(super) fn parse_lit(&mut self) -> PResult<'a, Lit> {
self.parse_opt_lit().ok_or(()).or_else(|()| {
fn handle_missing_lit(&mut self) -> PResult<'a, Lit> {
if let token::Interpolated(inner) = &self.token.kind {
let expr = match inner.as_ref() {
token::NtExpr(expr) => Some(expr),
@ -1798,7 +1793,7 @@ impl<'a> Parser<'a> {
}
}
let token = self.token.clone();
let err = |self_: &mut Self| {
let err = |self_: &Self| {
let msg = format!("unexpected token: {}", super::token_descr(&token));
self_.struct_span_err(token.span, &msg)
};
@ -1809,12 +1804,19 @@ impl<'a> Parser<'a> {
} else {
Err(err(self))
}
})
}
/// Matches `lit = true | false | token_lit`.
/// Returns `None` if the next token is not a literal.
pub(super) fn parse_opt_lit(&mut self) -> Option<Lit> {
pub(super) fn parse_token_lit(&mut self) -> PResult<'a, (token::Lit, Span)> {
self.parse_opt_token_lit()
.ok_or(())
.or_else(|()| self.handle_missing_lit().map(|lit| (lit.token_lit, lit.span)))
}
pub(super) fn parse_ast_lit(&mut self) -> PResult<'a, Lit> {
self.parse_opt_ast_lit().ok_or(()).or_else(|()| self.handle_missing_lit())
}
fn recover_after_dot(&mut self) -> Option<Token> {
let mut recovered = None;
if self.token == token::Dot {
// Attempt to recover `.4` as `0.4`. We don't currently have any syntax where
@ -1840,20 +1842,40 @@ impl<'a> Parser<'a> {
}
}
recovered
}
/// Matches `lit = true | false | token_lit`.
/// Returns `None` if the next token is not a literal.
pub(super) fn parse_opt_token_lit(&mut self) -> Option<(token::Lit, Span)> {
let recovered = self.recover_after_dot();
let token = recovered.as_ref().unwrap_or(&self.token);
match Lit::from_token(token) {
let span = token.span;
token::Lit::from_token(token).map(|token_lit| {
self.bump();
(token_lit, span)
})
}
/// Matches `lit = true | false | token_lit`.
/// Returns `None` if the next token is not a literal.
pub(super) fn parse_opt_ast_lit(&mut self) -> Option<Lit> {
let recovered = self.recover_after_dot();
let token = recovered.as_ref().unwrap_or(&self.token);
match token::Lit::from_token(token) {
Some(token_lit) => {
match Lit::from_token_lit(token_lit, token.span) {
Ok(lit) => {
self.bump();
Some(lit)
}
Err(LitError::NotLiteral) => None,
Err(err) => {
let span = token.span;
let token::Literal(lit) = token.kind else {
unreachable!();
};
self.bump();
self.report_lit_error(err, lit, span);
report_lit_error(&self.sess, err, lit, span);
// Pack possible quotes and prefixes from the original literal into
// the error literal's symbol so they can be pretty-printed faithfully.
let suffixless_lit = token::Lit::new(lit.kind, lit.symbol, None);
@ -1863,77 +1885,7 @@ impl<'a> Parser<'a> {
}
}
}
fn report_lit_error(&self, err: LitError, lit: token::Lit, span: Span) {
// Checks if `s` looks like i32 or u1234 etc.
fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool {
s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit())
}
// Try to lowercase the prefix if it's a valid base prefix.
fn fix_base_capitalisation(s: &str) -> Option<String> {
if let Some(stripped) = s.strip_prefix('B') {
Some(format!("0b{stripped}"))
} else if let Some(stripped) = s.strip_prefix('O') {
Some(format!("0o{stripped}"))
} else if let Some(stripped) = s.strip_prefix('X') {
Some(format!("0x{stripped}"))
} else {
None
}
}
let token::Lit { kind, suffix, .. } = lit;
match err {
// `NotLiteral` is not an error by itself, so we don't report
// it and give the parser opportunity to try something else.
LitError::NotLiteral => {}
// `LexerError` *is* an error, but it was already reported
// by lexer, so here we don't report it the second time.
LitError::LexerError => {}
LitError::InvalidSuffix => {
if let Some(suffix) = suffix {
self.sess.emit_err(InvalidLiteralSuffix {
span,
kind: format!("{}", kind.descr()),
suffix,
});
}
}
LitError::InvalidIntSuffix => {
let suf = suffix.expect("suffix error with no suffix");
let suf = suf.as_str();
if looks_like_width_suffix(&['i', 'u'], &suf) {
// If it looks like a width, try to be helpful.
self.sess.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() });
} else if let Some(fixed) = fix_base_capitalisation(suf) {
self.sess.emit_err(InvalidNumLiteralBasePrefix { span, fixed });
} else {
self.sess.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() });
}
}
LitError::InvalidFloatSuffix => {
let suf = suffix.expect("suffix error with no suffix");
let suf = suf.as_str();
if looks_like_width_suffix(&['f'], suf) {
// If it looks like a width, try to be helpful.
self.sess
.emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() });
} else {
self.sess.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() });
}
}
LitError::NonDecimalFloat(base) => {
match base {
16 => self.sess.emit_err(HexadecimalFloatLiteralNotSupported { span }),
8 => self.sess.emit_err(OctalFloatLiteralNotSupported { span }),
2 => self.sess.emit_err(BinaryFloatLiteralNotSupported { span }),
_ => unreachable!(),
};
}
LitError::IntTooLarge => {
self.sess.emit_err(IntLiteralTooLarge { span });
}
None => None,
}
}
@ -1958,8 +1910,8 @@ impl<'a> Parser<'a> {
let lo = self.token.span;
let minus_present = self.eat(&token::BinOp(token::Minus));
let lit = self.parse_lit()?;
let expr = self.mk_expr(lit.span, ExprKind::Lit(lit));
let (token_lit, span) = self.parse_token_lit()?;
let expr = self.mk_expr(span, ExprKind::Lit(token_lit));
if minus_present {
Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_unary(UnOp::Neg, expr)))

View File

@ -420,7 +420,7 @@ impl<'a> Parser<'a> {
err.span_label(self_.token.span, format!("expected {}", expected));
err
});
PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit)))
PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit.token_lit)))
} else {
// Try to parse everything else as literal with optional minus
match self.parse_literal_maybe_minus() {

View File

@ -359,7 +359,7 @@ impl<'a> Parser<'a> {
/// report error for `let 1x = 123`
pub fn report_invalid_identifier_error(&mut self) -> PResult<'a, ()> {
if let token::Literal(lit) = self.token.uninterpolate().kind &&
let Err(_) = rustc_ast::Lit::from_token(&self.token) &&
rustc_ast::Lit::from_token(&self.token).is_none() &&
(lit.kind == token::LitKind::Integer || lit.kind == token::LitKind::Float) &&
self.look_ahead(1, |t| matches!(t.kind, token::Eq) || matches!(t.kind, token::Colon ) ) {
return Err(self.sess.create_err(InvalidIdentiferStartsWithNumber { span: self.token.span }));

View File

@ -49,10 +49,12 @@ pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Meta
MetaItemKind::List(nmis)
}
MacArgs::Eq(_, MacArgsEq::Ast(expr)) => {
if let ast::ExprKind::Lit(lit) = &expr.kind {
if !lit.kind.is_unsuffixed() {
if let ast::ExprKind::Lit(token_lit) = expr.kind
&& let Ok(lit) = ast::Lit::from_token_lit(token_lit, expr.span)
{
if token_lit.suffix.is_some() {
let mut err = sess.span_diagnostic.struct_span_err(
lit.span,
expr.span,
"suffixed literals are not allowed in attributes",
);
err.help(
@ -61,7 +63,7 @@ pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Meta
);
return Err(err);
} else {
MetaItemKind::NameValue(lit.clone())
MetaItemKind::NameValue(lit)
}
} else {
// The non-error case can happen with e.g. `#[foo = 1+1]`. The error case can

View File

@ -1,6 +1,9 @@
use std::num::NonZeroU32;
use crate::cgu_reuse_tracker::CguReuse;
use crate::parse::ParseSess;
use rustc_ast::token;
use rustc_ast::util::literal::LitError;
use rustc_errors::MultiSpan;
use rustc_macros::Diagnostic;
use rustc_span::{Span, Symbol};
@ -191,3 +194,162 @@ pub enum UnleashedFeatureHelp {
span: Span,
},
}
#[derive(Diagnostic)]
#[diag(parser_invalid_literal_suffix)]
pub(crate) struct InvalidLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
// FIXME(#100717)
pub kind: String,
pub suffix: Symbol,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_int_literal_width)]
#[help]
pub(crate) struct InvalidIntLiteralWidth {
#[primary_span]
pub span: Span,
pub width: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_num_literal_base_prefix)]
#[note]
pub(crate) struct InvalidNumLiteralBasePrefix {
#[primary_span]
#[suggestion(applicability = "maybe-incorrect", code = "{fixed}")]
pub span: Span,
pub fixed: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_num_literal_suffix)]
#[help]
pub(crate) struct InvalidNumLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
pub suffix: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_float_literal_width)]
#[help]
pub(crate) struct InvalidFloatLiteralWidth {
#[primary_span]
pub span: Span,
pub width: String,
}
#[derive(Diagnostic)]
#[diag(parser_invalid_float_literal_suffix)]
#[help]
pub(crate) struct InvalidFloatLiteralSuffix {
#[primary_span]
#[label]
pub span: Span,
pub suffix: String,
}
#[derive(Diagnostic)]
#[diag(parser_int_literal_too_large)]
pub(crate) struct IntLiteralTooLarge {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_hexadecimal_float_literal_not_supported)]
pub(crate) struct HexadecimalFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_octal_float_literal_not_supported)]
pub(crate) struct OctalFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parser_binary_float_literal_not_supported)]
pub(crate) struct BinaryFloatLiteralNotSupported {
#[primary_span]
#[label(parser_not_supported)]
pub span: Span,
}
pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span: Span) {
// Checks if `s` looks like i32 or u1234 etc.
fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool {
s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit())
}
// Try to lowercase the prefix if it's a valid base prefix.
fn fix_base_capitalisation(s: &str) -> Option<String> {
if let Some(stripped) = s.strip_prefix('B') {
Some(format!("0b{stripped}"))
} else if let Some(stripped) = s.strip_prefix('O') {
Some(format!("0o{stripped}"))
} else if let Some(stripped) = s.strip_prefix('X') {
Some(format!("0x{stripped}"))
} else {
None
}
}
let token::Lit { kind, suffix, .. } = lit;
match err {
// `LexerError` is an error, but it was already reported
// by lexer, so here we don't report it the second time.
LitError::LexerError => {}
LitError::InvalidSuffix => {
if let Some(suffix) = suffix {
sess.emit_err(InvalidLiteralSuffix {
span,
kind: format!("{}", kind.descr()),
suffix,
});
}
}
LitError::InvalidIntSuffix => {
let suf = suffix.expect("suffix error with no suffix");
let suf = suf.as_str();
if looks_like_width_suffix(&['i', 'u'], &suf) {
// If it looks like a width, try to be helpful.
sess.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() });
} else if let Some(fixed) = fix_base_capitalisation(suf) {
sess.emit_err(InvalidNumLiteralBasePrefix { span, fixed });
} else {
sess.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() });
}
}
LitError::InvalidFloatSuffix => {
let suf = suffix.expect("suffix error with no suffix");
let suf = suf.as_str();
if looks_like_width_suffix(&['f'], suf) {
// If it looks like a width, try to be helpful.
sess.emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() });
} else {
sess.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() });
}
}
LitError::NonDecimalFloat(base) => {
match base {
16 => sess.emit_err(HexadecimalFloatLiteralNotSupported { span }),
8 => sess.emit_err(OctalFloatLiteralNotSupported { span }),
2 => sess.emit_err(BinaryFloatLiteralNotSupported { span }),
_ => unreachable!(),
};
}
LitError::IntTooLarge => {
sess.emit_err(IntLiteralTooLarge { span });
}
}
}

View File

@ -1,3 +1,9 @@
error[E0425]: cannot find value `a̐é` in this scope
--> $DIR/unicode_2.rs:4:13
|
LL | let _ = a̐é;
| ^^ not found in this scope
error: invalid width `7` for integer literal
--> $DIR/unicode_2.rs:2:25
|
@ -14,12 +20,6 @@ LL | let _ = ("아あ", 1i42);
|
= help: valid widths are 8, 16, 32, 64 and 128
error[E0425]: cannot find value `a̐é` in this scope
--> $DIR/unicode_2.rs:4:13
|
LL | let _ = a̐é;
| ^^ not found in this scope
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0425`.

View File

@ -1,31 +1,80 @@
// This test is about the treatment of invalid literals. In particular, some
// literals are only considered invalid if they survive to HIR lowering.
//
// Literals with bad suffixes
// --------------------------
// Literals consist of a primary part and an optional suffix.
// https://doc.rust-lang.org/reference/tokens.html#suffixes says:
//
// Any kind of literal (string, integer, etc) with any suffix is valid as a
// token, and can be passed to a macro without producing an error. The macro
// itself will decide how to interpret such a token and whether to produce an
// error or not.
//
// ```
// macro_rules! blackhole { ($tt:tt) => () }
// blackhole!("string"suffix); // OK
// ```
//
// However, suffixes on literal tokens parsed as Rust code are restricted.
// Any suffixes are rejected on non-numeric literal tokens, and numeric
// literal tokens are accepted only with suffixes from the list below.
//
// Integer: u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
// Floating-point: f32, f64
//
// This means that something like `"string"any_suffix` is a token accepted by
// the lexer, but rejected later for being an invalid combination of primary
// part and suffix.
//
// `0b10f32` is a similar case. `0b10` is a valid primary part that is a valid
// *integer* literal when no suffix is present. It only causes an error later
// when combined with the `f32` float suffix.
//
// However, `0b10.0f32` is different. It is rejected by the lexer because
// `0b10.0` is not a valid token even on its own.
//
// This difference is unfortunate, but it's baked into the language now.
//
// Too-large integer literals
// --------------------------
// https://doc.rust-lang.org/reference/tokens.html#integer-literals says that
// literals like `128_i8` and `256_u8` "are too big for their type, but are
// still valid tokens".
macro_rules! sink {
($($x:tt;)*) => {()}
}
// The invalid literals are ignored because the macro consumes them.
// The invalid literals are ignored because the macro consumes them. Except for
// `0b10.0f32` because it's a lexer error.
const _: () = sink! {
"string"any_suffix; // OK
10u123; // OK
10.0f123; // OK
0b10f32; // OK
0b10.0f32; //~ ERROR binary float literal is not supported
999340282366920938463463374607431768211455999; // OK
};
// The invalid literals cause errors.
// The invalid literals used to cause errors, but this was changed by #102944.
// Except for `0b010.0f32`, because it's a lexer error.
#[cfg(FALSE)]
fn configured_out() {
"string"any_suffix; //~ ERROR suffixes on string literals are invalid
10u123; //~ ERROR invalid width `123` for integer literal
10.0f123; //~ ERROR invalid width `123` for float literal
0b10f32; //~ ERROR binary float literal is not supported
999340282366920938463463374607431768211455999; //~ ERROR integer literal is too large
"string"any_suffix; // OK
10u123; // OK
10.0f123; // OK
0b10f32; // OK
0b10.0f32; //~ ERROR binary float literal is not supported
999340282366920938463463374607431768211455999; // OK
}
// The invalid literals cause errors.
// All the invalid literals cause errors.
fn main() {
"string"any_suffix; //~ ERROR suffixes on string literals are invalid
10u123; //~ ERROR invalid width `123` for integer literal
10.0f123; //~ ERROR invalid width `123` for float literal
0b10f32; //~ ERROR binary float literal is not supported
0b10.0f32; //~ ERROR binary float literal is not supported
999340282366920938463463374607431768211455999; //~ ERROR integer literal is too large
}

View File

@ -1,11 +1,29 @@
error: binary float literal is not supported
--> $DIR/error-stage.rs:56:5
|
LL | 0b10.0f32;
| ^^^^^^
error: binary float literal is not supported
--> $DIR/error-stage.rs:68:5
|
LL | 0b10.0f32;
| ^^^^^^
error: binary float literal is not supported
--> $DIR/error-stage.rs:78:5
|
LL | 0b10.0f32;
| ^^^^^^
error: suffixes on string literals are invalid
--> $DIR/error-stage.rs:17:5
--> $DIR/error-stage.rs:74:5
|
LL | "string"any_suffix;
| ^^^^^^^^^^^^^^^^^^ invalid suffix `any_suffix`
error: invalid width `123` for integer literal
--> $DIR/error-stage.rs:18:5
--> $DIR/error-stage.rs:75:5
|
LL | 10u123;
| ^^^^^^
@ -13,7 +31,7 @@ LL | 10u123;
= help: valid widths are 8, 16, 32, 64 and 128
error: invalid width `123` for float literal
--> $DIR/error-stage.rs:19:5
--> $DIR/error-stage.rs:76:5
|
LL | 10.0f123;
| ^^^^^^^^
@ -21,50 +39,16 @@ LL | 10.0f123;
= help: valid widths are 32 and 64
error: binary float literal is not supported
--> $DIR/error-stage.rs:20:5
--> $DIR/error-stage.rs:77:5
|
LL | 0b10f32;
| ^^^^^^^ not supported
error: integer literal is too large
--> $DIR/error-stage.rs:21:5
--> $DIR/error-stage.rs:79:5
|
LL | 999340282366920938463463374607431768211455999;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: suffixes on string literals are invalid
--> $DIR/error-stage.rs:26:5
|
LL | "string"any_suffix;
| ^^^^^^^^^^^^^^^^^^ invalid suffix `any_suffix`
error: invalid width `123` for integer literal
--> $DIR/error-stage.rs:27:5
|
LL | 10u123;
| ^^^^^^
|
= help: valid widths are 8, 16, 32, 64 and 128
error: invalid width `123` for float literal
--> $DIR/error-stage.rs:28:5
|
LL | 10.0f123;
| ^^^^^^^^
|
= help: valid widths are 32 and 64
error: binary float literal is not supported
--> $DIR/error-stage.rs:29:5
|
LL | 0b10f32;
| ^^^^^^^ not supported
error: integer literal is too large
--> $DIR/error-stage.rs:30:5
|
LL | 999340282366920938463463374607431768211455999;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 10 previous errors
error: aborting due to 8 previous errors

View File

@ -28,12 +28,11 @@ fn main() {
}
#[rustc_dummy = "string"suffix]
//~^ ERROR suffixes on string literals are invalid
//~^ ERROR unexpected expression: `"string"suffix`
fn f() {}
#[must_use = "string"suffix]
//~^ ERROR suffixes on string literals are invalid
//~^^ ERROR malformed `must_use` attribute input
//~^ ERROR unexpected expression: `"string"suffix`
fn g() {}
#[link(name = "string"suffix)]

View File

@ -10,6 +10,32 @@ error: suffixes on string literals are invalid
LL | "C"suffix
| ^^^^^^^^^ invalid suffix `suffix`
error: unexpected expression: `"string"suffix`
--> $DIR/bad-lit-suffixes.rs:30:17
|
LL | #[rustc_dummy = "string"suffix]
| ^^^^^^^^^^^^^^
error: unexpected expression: `"string"suffix`
--> $DIR/bad-lit-suffixes.rs:34:14
|
LL | #[must_use = "string"suffix]
| ^^^^^^^^^^^^^^
error: suffixes on string literals are invalid
--> $DIR/bad-lit-suffixes.rs:38:15
|
LL | #[link(name = "string"suffix)]
| ^^^^^^^^^^^^^^ invalid suffix `suffix`
error: invalid suffix `suffix` for number literal
--> $DIR/bad-lit-suffixes.rs:42:41
|
LL | #[rustc_layout_scalar_valid_range_start(0suffix)]
| ^^^^^^^ invalid suffix `suffix`
|
= help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.)
error: suffixes on string literals are invalid
--> $DIR/bad-lit-suffixes.rs:12:5
|
@ -110,44 +136,5 @@ LL | 1.0e10suffix;
|
= help: valid suffixes are `f32` and `f64`
error: suffixes on string literals are invalid
--> $DIR/bad-lit-suffixes.rs:30:17
|
LL | #[rustc_dummy = "string"suffix]
| ^^^^^^^^^^^^^^ invalid suffix `suffix`
error: suffixes on string literals are invalid
--> $DIR/bad-lit-suffixes.rs:34:14
|
LL | #[must_use = "string"suffix]
| ^^^^^^^^^^^^^^ invalid suffix `suffix`
error: malformed `must_use` attribute input
--> $DIR/bad-lit-suffixes.rs:34:1
|
LL | #[must_use = "string"suffix]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: the following are the possible correct uses
|
LL | #[must_use = "reason"]
|
LL | #[must_use]
|
error: suffixes on string literals are invalid
--> $DIR/bad-lit-suffixes.rs:39:15
|
LL | #[link(name = "string"suffix)]
| ^^^^^^^^^^^^^^ invalid suffix `suffix`
error: invalid suffix `suffix` for number literal
--> $DIR/bad-lit-suffixes.rs:43:41
|
LL | #[rustc_layout_scalar_valid_range_start(0suffix)]
| ^^^^^^^ invalid suffix `suffix`
|
= help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.)
error: aborting due to 21 previous errors
error: aborting due to 20 previous errors

View File

@ -73,12 +73,21 @@ impl EarlyLintPass for AlmostCompleteLetterRange {
}
fn check_range(cx: &EarlyContext<'_>, span: Span, start: &Expr, end: &Expr, sugg: Option<(Span, &str)>) {
if let ExprKind::Lit(start_lit) = &start.peel_parens().kind
&& let ExprKind::Lit(end_lit) = &end.peel_parens().kind
if let ExprKind::Lit(start_token_lit) = start.peel_parens().kind
&& let ExprKind::Lit(end_token_lit) = end.peel_parens().kind
&& matches!(
(&start_lit.kind, &end_lit.kind),
(LitKind::Byte(b'a') | LitKind::Char('a'), LitKind::Byte(b'z') | LitKind::Char('z'))
| (LitKind::Byte(b'A') | LitKind::Char('A'), LitKind::Byte(b'Z') | LitKind::Char('Z'))
(
LitKind::from_token_lit(start_token_lit),
LitKind::from_token_lit(end_token_lit),
),
(
Ok(LitKind::Byte(b'a') | LitKind::Char('a')),
Ok(LitKind::Byte(b'z') | LitKind::Char('z'))
)
| (
Ok(LitKind::Byte(b'A') | LitKind::Char('A')),
Ok(LitKind::Byte(b'Z') | LitKind::Char('Z')),
)
)
&& !in_external_macro(cx.sess(), span)
{

View File

@ -2,7 +2,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind};
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind};
use rustc_ast::token;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -52,8 +53,8 @@ enum Side {
impl IntPlusOne {
#[expect(clippy::cast_sign_loss)]
fn check_lit(lit: &Lit, target_value: i128) -> bool {
if let LitKind::Int(value, ..) = lit.kind {
fn check_lit(token_lit: token::Lit, target_value: i128) -> bool {
if let Ok(LitKind::Int(value, ..)) = LitKind::from_token_lit(token_lit) {
return value == (target_value as u128);
}
false
@ -65,11 +66,11 @@ impl IntPlusOne {
(BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
// `-1 + x`
(BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
(BinOpKind::Add, &ExprKind::Lit(lit), _) if Self::check_lit(lit, -1) => {
Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
},
// `x - 1`
(BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
(BinOpKind::Sub, _, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
},
_ => None,
@ -81,10 +82,10 @@ impl IntPlusOne {
{
match (&rhslhs.kind, &rhsrhs.kind) {
// `y + 1` and `1 + y`
(&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
(&ExprKind::Lit(lit), _) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
},
(_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
(_, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
},
_ => None,
@ -96,10 +97,10 @@ impl IntPlusOne {
{
match (&lhslhs.kind, &lhsrhs.kind) {
// `1 + x` and `x + 1`
(&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
(&ExprKind::Lit(lit), _) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
},
(_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
(_, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
},
_ => None,
@ -109,11 +110,11 @@ impl IntPlusOne {
(BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
// `-1 + y`
(BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
(BinOpKind::Add, &ExprKind::Lit(lit), _) if Self::check_lit(lit, -1) => {
Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
},
// `y - 1`
(BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
(BinOpKind::Sub, _, &ExprKind::Lit(lit)) if Self::check_lit(lit, 1) => {
Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
},
_ => None,

View File

@ -5,11 +5,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use std::iter;
declare_clippy_lint! {
@ -236,8 +238,8 @@ impl EarlyLintPass for LiteralDigitGrouping {
return;
}
if let ExprKind::Lit(ref lit) = expr.kind {
self.check_lit(cx, lit);
if let ExprKind::Lit(lit) = expr.kind {
self.check_lit(cx, lit, expr.span);
}
}
}
@ -252,12 +254,13 @@ impl LiteralDigitGrouping {
}
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
if_chain! {
if let Some(src) = snippet_opt(cx, lit.span);
if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
if let Some(src) = snippet_opt(cx, span);
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
then {
if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
return;
}
@ -293,14 +296,14 @@ impl LiteralDigitGrouping {
| WarningType::InconsistentDigitGrouping
| WarningType::UnusualByteGroupings
| WarningType::LargeDigitGroups => {
!lit.span.from_expansion()
!span.from_expansion()
}
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
true
}
};
if should_warn {
warning_type.display(num_lit.format(), cx, lit.span);
warning_type.display(num_lit.format(), cx, span);
}
}
}
@ -458,8 +461,8 @@ impl EarlyLintPass for DecimalLiteralRepresentation {
return;
}
if let ExprKind::Lit(ref lit) = expr.kind {
self.check_lit(cx, lit);
if let ExprKind::Lit(lit) = expr.kind {
self.check_lit(cx, lit, expr.span);
}
}
}
@ -469,19 +472,20 @@ impl DecimalLiteralRepresentation {
pub fn new(threshold: u64) -> Self {
Self { threshold }
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
// Lint integral literals.
if_chain! {
if let LitKind::Int(val, _) = lit.kind;
if let Some(src) = snippet_opt(cx, lit.span);
if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let LitKind::Int(val, _) = lit_kind;
if let Some(src) = snippet_opt(cx, span);
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
if num_lit.radix == Radix::Decimal;
if val >= u128::from(self.threshold);
then {
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, lit.span);
warning_type.display(num_lit.format(), cx, span);
});
}
}

View File

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::Lit;
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::Span;
use super::{SEPARATED_LITERAL_SUFFIX, UNSEPARATED_LITERAL_SUFFIX};
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) {
pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str, suffix: &str, sugg_type: &str) {
let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
return; // It's useless so shouldn't lint.
};
@ -15,7 +15,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &s
span_lint_and_sugg(
cx,
SEPARATED_LITERAL_SUFFIX,
lit.span,
lit_span,
&format!("{sugg_type} type suffix should not be separated by an underscore"),
"remove the underscore",
format!("{}{suffix}", &lit_snip[..maybe_last_sep_idx]),
@ -25,7 +25,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &s
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
lit_span,
&format!("{sugg_type} type suffix should be separated by an underscore"),
"add an underscore",
format!("{}_{suffix}", &lit_snip[..=maybe_last_sep_idx]),

View File

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::Lit;
use rustc_lint::EarlyContext;
use rustc_span::Span;
use super::MIXED_CASE_HEX_LITERALS;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &str) {
pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, suffix: &str, lit_snip: &str) {
let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
return; // It's useless so shouldn't lint.
};
@ -23,7 +23,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &s
span_lint(
cx,
MIXED_CASE_HEX_LITERALS,
lit.span,
lit_span,
"inconsistent casing in hexadecimal literal",
);
break;

View File

@ -9,7 +9,8 @@ mod zero_prefixed_literal;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, ExprKind, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
use rustc_ast::token;
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
@ -374,42 +375,43 @@ impl EarlyLintPass for MiscEarlyLints {
return;
}
if let ExprKind::Lit(ref lit) = expr.kind {
MiscEarlyLints::check_lit(cx, lit);
if let ExprKind::Lit(lit) = expr.kind {
MiscEarlyLints::check_lit(cx, lit, expr.span);
}
double_neg::check(cx, expr);
}
}
impl MiscEarlyLints {
fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
fn check_lit(cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
// We test if first character in snippet is a number, because the snippet could be an expansion
// from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
// Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
let lit_snip = match snippet_opt(cx, lit.span) {
let lit_snip = match snippet_opt(cx, span) {
Some(snip) if snip.chars().next().map_or(false, |c| c.is_ascii_digit()) => snip,
_ => return,
};
if let LitKind::Int(value, lit_int_type) = lit.kind {
let lit_kind = LitKind::from_token_lit(lit);
if let Ok(LitKind::Int(value, lit_int_type)) = lit_kind {
let suffix = match lit_int_type {
LitIntType::Signed(ty) => ty.name_str(),
LitIntType::Unsigned(ty) => ty.name_str(),
LitIntType::Unsuffixed => "",
};
literal_suffix::check(cx, lit, &lit_snip, suffix, "integer");
literal_suffix::check(cx, span, &lit_snip, suffix, "integer");
if lit_snip.starts_with("0x") {
mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip);
mixed_case_hex_literals::check(cx, span, suffix, &lit_snip);
} else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
// nothing to do
} else if value != 0 && lit_snip.starts_with('0') {
zero_prefixed_literal::check(cx, lit, &lit_snip);
zero_prefixed_literal::check(cx, span, &lit_snip);
}
} else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
} else if let Ok(LitKind::Float(_, LitFloatType::Suffixed(float_ty))) = lit_kind {
let suffix = float_ty.name_str();
literal_suffix::check(cx, lit, &lit_snip, suffix, "float");
literal_suffix::check(cx, span, &lit_snip, suffix, "float");
}
}
}

View File

@ -1,20 +1,20 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::Lit;
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::Span;
use super::ZERO_PREFIXED_LITERAL;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str) {
let trimmed_lit_snip = lit_snip.trim_start_matches(|c| c == '_' || c == '0');
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
lit.span,
lit_span,
"this is a decimal constant",
|diag| {
diag.span_suggestion(
lit.span,
lit_span,
"if you mean to use a decimal constant, remove the `0` to avoid confusion",
trimmed_lit_snip.to_string(),
Applicability::MaybeIncorrect,
@ -22,7 +22,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
// do not advise to use octal form if the literal cannot be expressed in base 8.
if !lit_snip.contains(|c| c == '8' || c == '9') {
diag.span_suggestion(
lit.span,
lit_span,
"if you mean to use an octal constant, use `0o`",
format!("0o{trimmed_lit_snip}"),
Applicability::MaybeIncorrect,

View File

@ -56,11 +56,11 @@ impl EarlyLintPass for OctalEscapes {
return;
}
if let ExprKind::Lit(lit) = &expr.kind {
if matches!(lit.token_lit.kind, LitKind::Str) {
check_lit(cx, &lit.token_lit, lit.span, true);
} else if matches!(lit.token_lit.kind, LitKind::ByteStr) {
check_lit(cx, &lit.token_lit, lit.span, false);
if let ExprKind::Lit(token_lit) = &expr.kind {
if matches!(token_lit.kind, LitKind::Str) {
check_lit(cx, &token_lit, expr.span, true);
} else if matches!(token_lit.kind, LitKind::ByteStr) {
check_lit(cx, &token_lit, expr.span, false);
}
}
}

View File

@ -1,7 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_ast::token;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -120,7 +121,7 @@ impl EarlyLintPass for Precedence {
if_chain! {
if !all_odd;
if let ExprKind::Lit(lit) = &arg.kind;
if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind;
if let token::LitKind::Integer | token::LitKind::Float = &lit.kind;
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{Expr, ExprKind, LitFloatType, LitKind};
use rustc_ast::ast::{Expr, ExprKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -33,14 +33,14 @@ fn is_useless_rounding(expr: &Expr) -> Option<(&str, String)> {
if let ExprKind::MethodCall(name_ident, receiver, _, _) = &expr.kind
&& let method_name = name_ident.ident.name.as_str()
&& (method_name == "ceil" || method_name == "round" || method_name == "floor")
&& let ExprKind::Lit(spanned) = &receiver.kind
&& let LitKind::Float(symbol, ty) = spanned.kind {
let f = symbol.as_str().parse::<f64>().unwrap();
let f_str = symbol.to_string() + if let LitFloatType::Suffixed(ty) = ty {
ty.name_str()
} else {
""
};
&& let ExprKind::Lit(token_lit) = &receiver.kind
&& token_lit.is_semantic_float() {
let f = token_lit.symbol.as_str().parse::<f64>().unwrap();
let mut f_str = token_lit.symbol.to_string();
match token_lit.suffix {
Some(suffix) => f_str.push_str(suffix.as_str()),
None => {}
}
if f.fract() == 0.0 {
Some((method_name, f_str))
} else {

View File

@ -152,7 +152,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
},
(Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
(Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
(Lit(l), Lit(r)) => l.kind == r.kind,
(Lit(l), Lit(r)) => l == r,
(Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
(Let(lp, le, _), Let(rp, re, _)) => eq_pat(lp, rp) && eq_expr(le, re),
(If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),

View File

@ -1,4 +1,4 @@
use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use std::iter;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@ -46,10 +46,6 @@ pub struct NumericLiteral<'a> {
}
impl<'a> NumericLiteral<'a> {
pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
NumericLiteral::from_lit_kind(src, &lit.kind)
}
pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
let unsigned_src = src.strip_prefix('-').map_or(src, |s| s);
if lit_kind.is_numeric()

View File

@ -260,7 +260,9 @@ impl Rewrite for ast::NestedMetaItem {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
match self {
ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape),
ast::NestedMetaItem::Literal(ref l) => {
rewrite_literal(context, l.token_lit, l.span, shape)
}
}
}
}
@ -318,7 +320,7 @@ impl Rewrite for ast::MetaItem {
// we might be better off ignoring the fact that the attribute
// is longer than the max width and continue on formatting.
// See #2479 for example.
let value = rewrite_literal(context, literal, lit_shape)
let value = rewrite_literal(context, literal.token_lit, literal.span, lit_shape)
.unwrap_or_else(|| context.snippet(literal.span).to_owned());
format!("{} = {}", path, value)
}

View File

@ -3,7 +3,7 @@ use std::cmp::min;
use itertools::Itertools;
use rustc_ast::token::{Delimiter, LitKind};
use rustc_ast::{ast, ptr};
use rustc_ast::{ast, ptr, token};
use rustc_span::{BytePos, Span};
use crate::chains::rewrite_chain;
@ -75,12 +75,12 @@ pub(crate) fn format_expr(
choose_separator_tactic(context, expr.span),
None,
),
ast::ExprKind::Lit(ref l) => {
if let Some(expr_rw) = rewrite_literal(context, l, shape) {
ast::ExprKind::Lit(token_lit) => {
if let Some(expr_rw) = rewrite_literal(context, token_lit, expr.span, shape) {
Some(expr_rw)
} else {
if let LitKind::StrRaw(_) = l.token_lit.kind {
Some(context.snippet(l.span).trim().into())
if let LitKind::StrRaw(_) = token_lit.kind {
Some(context.snippet(expr.span).trim().into())
} else {
None
}
@ -274,9 +274,9 @@ pub(crate) fn format_expr(
fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool {
match lhs.kind {
ast::ExprKind::Lit(ref lit) => match lit.kind {
ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) => {
context.snippet(lit.span).ends_with('.')
ast::ExprKind::Lit(token_lit) => match token_lit.kind {
token::LitKind::Float if token_lit.suffix.is_none() => {
context.snippet(lhs.span).ends_with('.')
}
_ => false,
},
@ -1185,14 +1185,15 @@ pub(crate) fn is_unsafe_block(block: &ast::Block) -> bool {
pub(crate) fn rewrite_literal(
context: &RewriteContext<'_>,
l: &ast::Lit,
token_lit: token::Lit,
span: Span,
shape: Shape,
) -> Option<String> {
match l.kind {
ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape),
ast::LitKind::Int(..) => rewrite_int_lit(context, l, shape),
match token_lit.kind {
token::LitKind::Str => rewrite_string_lit(context, span, shape),
token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape),
_ => wrap_str(
context.snippet(l.span).to_owned(),
context.snippet(span).to_owned(),
context.config.max_width(),
shape,
),
@ -1225,9 +1226,13 @@ fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) ->
)
}
fn rewrite_int_lit(context: &RewriteContext<'_>, lit: &ast::Lit, shape: Shape) -> Option<String> {
let span = lit.span;
let symbol = lit.token_lit.symbol.as_str();
fn rewrite_int_lit(
context: &RewriteContext<'_>,
token_lit: token::Lit,
span: Span,
shape: Shape,
) -> Option<String> {
let symbol = token_lit.symbol.as_str();
if let Some(symbol_stripped) = symbol.strip_prefix("0x") {
let hex_lit = match context.config.hex_literal_case() {
@ -1240,9 +1245,7 @@ fn rewrite_int_lit(context: &RewriteContext<'_>, lit: &ast::Lit, shape: Shape) -
format!(
"0x{}{}",
hex_lit,
lit.token_lit
.suffix
.map_or(String::new(), |s| s.to_string())
token_lit.suffix.map_or(String::new(), |s| s.to_string())
),
context.config.max_width(),
shape,