about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/ast.rs24
-rw-r--r--compiler/rustc_ast/src/attr/mod.rs12
-rw-r--r--compiler/rustc_ast/src/token.rs50
-rw-r--r--compiler/rustc_ast/src/util/literal.rs27
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs12
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs11
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs10
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs4
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/item.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/concat.rs25
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs83
-rw-r--r--compiler/rustc_expand/src/base.rs13
-rw-r--r--compiler/rustc_expand/src/build.rs4
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs17
-rw-r--r--compiler/rustc_lexer/src/lib.rs8
-rw-r--r--compiler/rustc_lint/src/builtin.rs7
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs15
-rw-r--r--compiler/rustc_parse/src/errors.rs90
-rw-r--r--compiler/rustc_parse/src/lexer/mod.rs13
-rw-r--r--compiler/rustc_parse/src/parser/attr.rs4
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs242
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs2
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs2
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs10
-rw-r--r--compiler/rustc_session/src/errors.rs162
-rw-r--r--src/test/ui/codemap_tests/unicode_2.stderr12
-rw-r--r--src/test/ui/lexer/error-stage.rs65
-rw-r--r--src/test/ui/lexer/error-stage.stderr48
-rw-r--r--src/test/ui/parser/bad-lit-suffixes.rs5
-rw-r--r--src/test/ui/parser/bad-lit-suffixes.stderr67
-rw-r--r--src/tools/clippy/clippy_lints/src/almost_complete_letter_range.rs19
-rw-r--r--src/tools/clippy/clippy_lints/src/int_plus_one.rs23
-rw-r--r--src/tools/clippy/clippy_lints/src/literal_representation.rs36
-rw-r--r--src/tools/clippy/clippy_lints/src/misc_early/literal_suffix.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/misc_early/mixed_case_hex_literals.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/misc_early/mod.rs24
-rw-r--r--src/tools/clippy/clippy_lints/src/misc_early/zero_prefixed_literal.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/octal_escapes.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/precedence.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/unused_rounding.rs18
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/numeric_literal.rs6
-rw-r--r--src/tools/rustfmt/src/attr.rs6
-rw-r--r--src/tools/rustfmt/src/expr.rs41
45 files changed, 693 insertions, 573 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 2cbab90aa61..5d9d0a5feca 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -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,
diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs
index 990f4f8f132..07f982b7e86 100644
--- a/compiler/rustc_ast/src/attr/mod.rs
+++ b/compiler/rustc_ast/src/attr/mod.rs
@@ -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));
diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs
index f6aac0b55f1..e0ff690e766 100644
--- a/compiler/rustc_ast/src/token.rs
+++ b/compiler/rustc_ast/src/token.rs
@@ -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);
 
diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs
index e267f8cd100..db2ac9626af 100644
--- a/compiler/rustc_ast/src/util/literal.rs
+++ b/compiler/rustc_ast/src/util/literal.rs
@@ -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.
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index a4ae493af86..b4a8283c4a0 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -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),
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index ff29d15f1b5..cc93774d846 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -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),
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index b87c6f78d72..5f01f555b30 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -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();
             }
         }
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 930276242c3..86f1d6bfecd 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -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);
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
index 159853c9e24..9c4425701e0 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
@@ -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();
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
index a1051d990b1..a34c17a4258 100644
--- a/compiler/rustc_builtin_macros/src/asm.rs
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -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 {
diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs
index 01454d0e98e..d579616ad1b 100644
--- a/compiler/rustc_builtin_macros/src/concat.rs
+++ b/compiler/rustc_builtin_macros/src/concat.rs
@@ -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;
+                }
+                Ok(ast::LitKind::Err) => {
+                    has_errors = true;
                 }
-                ast::LitKind::Err => {
+                Err(err) => {
+                    report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span);
                     has_errors = true;
                 }
             },
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
index 4886ca786a5..87658e60e9d 100644
--- a/compiler/rustc_builtin_macros/src/concat_bytes.rs
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -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;
                 }
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 1294f1e17d4..95fff929d46 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -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)),
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 0952e65cfee..8aa72e142f8 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -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> {
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index a929f6cb0a5..2e832deeecd 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -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(()),
diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs
index d4140cb295f..3fbabbc6344 100644
--- a/compiler/rustc_lexer/src/lib.rs
+++ b/compiler/rustc_lexer/src/lib.rs
@@ -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 },
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 27c04d82811..253ff1f793c 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -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(
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
index 7e884e990ce..7106e75dba2 100644
--- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -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");
     }
 }
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index 2b17cea9794..724d92254a4 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -305,61 +305,6 @@ pub(crate) struct FloatLiteralRequiresIntegerPart {
 }
 
 #[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 {
     #[primary_span]
@@ -741,41 +686,6 @@ pub(crate) struct InvalidInterpolatedExpression {
 }
 
 #[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 {
     #[primary_span]
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 645262bd2f1..f027843e6b4 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -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> {
                 );
             }
         });
-        (kind, Symbol::intern(lit_content))
+
+        // 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))
+        }
     }
 }
 
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs
index 9e45656946b..612accf3e3b 100644
--- a/compiler/rustc_parse/src/parser/attr.rs
+++ b/compiler/rustc_parse/src/parser/attr.rs
@@ -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 });
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index b072573af23..c9629ea49e0 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -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,41 +1776,47 @@ impl<'a> Parser<'a> {
         }
     }
 
-    pub(super) fn parse_lit(&mut self) -> PResult<'a, Lit> {
-        self.parse_opt_lit().ok_or(()).or_else(|()| {
-            if let token::Interpolated(inner) = &self.token.kind {
-                let expr = match inner.as_ref() {
-                    token::NtExpr(expr) => Some(expr),
-                    token::NtLiteral(expr) => Some(expr),
-                    _ => None,
-                };
-                if let Some(expr) = expr {
-                    if matches!(expr.kind, ExprKind::Err) {
-                        let mut err = InvalidInterpolatedExpression { span: self.token.span }
-                            .into_diagnostic(&self.sess.span_diagnostic);
-                        err.downgrade_to_delayed_bug();
-                        return Err(err);
-                    }
-                }
-            }
-            let token = self.token.clone();
-            let err = |self_: &mut Self| {
-                let msg = format!("unexpected token: {}", super::token_descr(&token));
-                self_.struct_span_err(token.span, &msg)
+    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),
+                token::NtLiteral(expr) => Some(expr),
+                _ => None,
             };
-            // On an error path, eagerly consider a lifetime to be an unclosed character lit
-            if self.token.is_lifetime() {
-                let lt = self.expect_lifetime();
-                Ok(self.recover_unclosed_char(lt.ident, err))
-            } else {
-                Err(err(self))
+            if let Some(expr) = expr {
+                if matches!(expr.kind, ExprKind::Err) {
+                    let mut err = InvalidInterpolatedExpression { span: self.token.span }
+                        .into_diagnostic(&self.sess.span_diagnostic);
+                    err.downgrade_to_delayed_bug();
+                    return Err(err);
+                }
             }
-        })
+        }
+        let token = self.token.clone();
+        let err = |self_: &Self| {
+            let msg = format!("unexpected token: {}", super::token_descr(&token));
+            self_.struct_span_err(token.span, &msg)
+        };
+        // On an error path, eagerly consider a lifetime to be an unclosed character lit
+        if self.token.is_lifetime() {
+            let lt = self.expect_lifetime();
+            Ok(self.recover_unclosed_char(lt.ident, err))
+        } 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,100 +1842,50 @@ impl<'a> Parser<'a> {
             }
         }
 
-        let token = recovered.as_ref().unwrap_or(&self.token);
-        match Lit::from_token(token) {
-            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);
-                // 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);
-                let symbol = Symbol::intern(&suffixless_lit.to_string());
-                let lit = token::Lit::new(token::Err, symbol, lit.suffix);
-                Some(Lit::from_token_lit(lit, span).unwrap_or_else(|_| unreachable!()))
-            }
-        }
+        recovered
     }
 
-    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
-            }
-        }
+    /// 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);
+        let span = token.span;
+        token::Lit::from_token(token).map(|token_lit| {
+            self.bump();
+            (token_lit, span)
+        })
+    }
 
-        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() });
+    /// 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(err) => {
+                        let span = token.span;
+                        let token::Literal(lit) = token.kind else {
+                            unreachable!();
+                        };
+                        self.bump();
+                        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);
+                        let symbol = Symbol::intern(&suffixless_lit.to_string());
+                        let lit = token::Lit::new(token::Err, symbol, lit.suffix);
+                        Some(Lit::from_token_lit(lit, span).unwrap_or_else(|_| unreachable!()))
+                    }
                 }
             }
-            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)))
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 52c11b4e35f..b3af37a5f70 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -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() {
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 18b661034e0..7820bbc1789 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -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 }));
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 47477898b24..8e7f8bfe0f5 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -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
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index bf542faec41..4bfa583fc72 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -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 });
+        }
+    }
+}
diff --git a/src/test/ui/codemap_tests/unicode_2.stderr b/src/test/ui/codemap_tests/unicode_2.stderr
index a776a4a1e7e..19aae1d3c95 100644
--- a/src/test/ui/codemap_tests/unicode_2.stderr
+++ b/src/test/ui/codemap_tests/unicode_2.stderr
@@ -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`.
diff --git a/src/test/ui/lexer/error-stage.rs b/src/test/ui/lexer/error-stage.rs
index 05ae0e82bfb..c8d88f745a1 100644
--- a/src/test/ui/lexer/error-stage.rs
+++ b/src/test/ui/lexer/error-stage.rs
@@ -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
 }
diff --git a/src/test/ui/lexer/error-stage.stderr b/src/test/ui/lexer/error-stage.stderr
index 024b7d94037..697a7c28da1 100644
--- a/src/test/ui/lexer/error-stage.stderr
+++ b/src/test/ui/lexer/error-stage.stderr
@@ -1,45 +1,29 @@
-error: suffixes on string literals are invalid
-  --> $DIR/error-stage.rs:17:5
-   |
-LL |     "string"any_suffix;
-   |     ^^^^^^^^^^^^^^^^^^ invalid suffix `any_suffix`
-
-error: invalid width `123` for integer literal
-  --> $DIR/error-stage.rs:18:5
+error: binary float literal is not supported
+  --> $DIR/error-stage.rs:56:5
    |
-LL |     10u123;
+LL |     0b10.0f32;
    |     ^^^^^^
-   |
-   = help: valid widths are 8, 16, 32, 64 and 128
-
-error: invalid width `123` for float literal
-  --> $DIR/error-stage.rs:19:5
-   |
-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:68:5
    |
-LL |     0b10f32;
-   |     ^^^^^^^ not supported
+LL |     0b10.0f32;
+   |     ^^^^^^
 
-error: integer literal is too large
-  --> $DIR/error-stage.rs:21:5
+error: binary float literal is not supported
+  --> $DIR/error-stage.rs:78:5
    |
-LL |     999340282366920938463463374607431768211455999;
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     0b10.0f32;
+   |     ^^^^^^
 
 error: suffixes on string literals are invalid
-  --> $DIR/error-stage.rs:26: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:27:5
+  --> $DIR/error-stage.rs:75:5
    |
 LL |     10u123;
    |     ^^^^^^
@@ -47,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:28:5
+  --> $DIR/error-stage.rs:76:5
    |
 LL |     10.0f123;
    |     ^^^^^^^^
@@ -55,16 +39,16 @@ LL |     10.0f123;
    = help: valid widths are 32 and 64
 
 error: binary float literal is not supported
-  --> $DIR/error-stage.rs:29:5
+  --> $DIR/error-stage.rs:77:5
    |
 LL |     0b10f32;
    |     ^^^^^^^ not supported
 
 error: integer literal is too large
-  --> $DIR/error-stage.rs:30:5
+  --> $DIR/error-stage.rs:79:5
    |
 LL |     999340282366920938463463374607431768211455999;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 10 previous errors
+error: aborting due to 8 previous errors
 
diff --git a/src/test/ui/parser/bad-lit-suffixes.rs b/src/test/ui/parser/bad-lit-suffixes.rs
index 58b30872677..8cb9ef7e0c9 100644
--- a/src/test/ui/parser/bad-lit-suffixes.rs
+++ b/src/test/ui/parser/bad-lit-suffixes.rs
@@ -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)]
diff --git a/src/test/ui/parser/bad-lit-suffixes.stderr b/src/test/ui/parser/bad-lit-suffixes.stderr
index 14c0eda81be..756f99ab12c 100644
--- a/src/test/ui/parser/bad-lit-suffixes.stderr
+++ b/src/test/ui/parser/bad-lit-suffixes.stderr
@@ -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
 
diff --git a/src/tools/clippy/clippy_lints/src/almost_complete_letter_range.rs b/src/tools/clippy/clippy_lints/src/almost_complete_letter_range.rs
index 073e4af1318..df92579a85d 100644
--- a/src/tools/clippy/clippy_lints/src/almost_complete_letter_range.rs
+++ b/src/tools/clippy/clippy_lints/src/almost_complete_letter_range.rs
@@ -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)
     {
diff --git a/src/tools/clippy/clippy_lints/src/int_plus_one.rs b/src/tools/clippy/clippy_lints/src/int_plus_one.rs
index 33491da3fc5..f793abdfda3 100644
--- a/src/tools/clippy/clippy_lints/src/int_plus_one.rs
+++ b/src/tools/clippy/clippy_lints/src/int_plus_one.rs
@@ -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,
diff --git a/src/tools/clippy/clippy_lints/src/literal_representation.rs b/src/tools/clippy/clippy_lints/src/literal_representation.rs
index 25f19b9c6e6..3a7b7835c99 100644
--- a/src/tools/clippy/clippy_lints/src/literal_representation.rs
+++ b/src/tools/clippy/clippy_lints/src/literal_representation.rs
@@ -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);
                 });
             }
         }
diff --git a/src/tools/clippy/clippy_lints/src/misc_early/literal_suffix.rs b/src/tools/clippy/clippy_lints/src/misc_early/literal_suffix.rs
index 27e7f8505eb..eda4376f200 100644
--- a/src/tools/clippy/clippy_lints/src/misc_early/literal_suffix.rs
+++ b/src/tools/clippy/clippy_lints/src/misc_early/literal_suffix.rs
@@ -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]),
diff --git a/src/tools/clippy/clippy_lints/src/misc_early/mixed_case_hex_literals.rs b/src/tools/clippy/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
index 263ee1e945a..ddb8b9173a5 100644
--- a/src/tools/clippy/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
+++ b/src/tools/clippy/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
@@ -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;
diff --git a/src/tools/clippy/clippy_lints/src/misc_early/mod.rs b/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
index c8227ca4450..78be6b9e23f 100644
--- a/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/misc_early/mod.rs
@@ -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");
         }
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/misc_early/zero_prefixed_literal.rs b/src/tools/clippy/clippy_lints/src/misc_early/zero_prefixed_literal.rs
index 9ead43ea4a4..4f9578d1b25 100644
--- a/src/tools/clippy/clippy_lints/src/misc_early/zero_prefixed_literal.rs
+++ b/src/tools/clippy/clippy_lints/src/misc_early/zero_prefixed_literal.rs
@@ -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,
diff --git a/src/tools/clippy/clippy_lints/src/octal_escapes.rs b/src/tools/clippy/clippy_lints/src/octal_escapes.rs
index f380a506582..2a7159764e4 100644
--- a/src/tools/clippy/clippy_lints/src/octal_escapes.rs
+++ b/src/tools/clippy/clippy_lints/src/octal_escapes.rs
@@ -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);
             }
         }
     }
diff --git a/src/tools/clippy/clippy_lints/src/precedence.rs b/src/tools/clippy/clippy_lints/src/precedence.rs
index e6e3ad05ad7..bee4a33fb4a 100644
--- a/src/tools/clippy/clippy_lints/src/precedence.rs
+++ b/src/tools/clippy/clippy_lints/src/precedence.rs
@@ -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(
diff --git a/src/tools/clippy/clippy_lints/src/unused_rounding.rs b/src/tools/clippy/clippy_lints/src/unused_rounding.rs
index 3164937293b..3c1998d0237 100644
--- a/src/tools/clippy/clippy_lints/src/unused_rounding.rs
+++ b/src/tools/clippy/clippy_lints/src/unused_rounding.rs
@@ -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 {
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 0133997560e..73d1ba727c8 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -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),
diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs
index c5dcd7b31f5..42bdfd4827f 100644
--- a/src/tools/clippy/clippy_utils/src/numeric_literal.rs
+++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs
@@ -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()
diff --git a/src/tools/rustfmt/src/attr.rs b/src/tools/rustfmt/src/attr.rs
index f5c1ee5fdd1..ccc2fd0d5f5 100644
--- a/src/tools/rustfmt/src/attr.rs
+++ b/src/tools/rustfmt/src/attr.rs
@@ -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)
             }
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index 7750df0fff3..b4f1a178dbf 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -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,