From d81cc6846c6aee24270390ed9c0df229d3ce488a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 11 Mar 2026 20:41:57 +0800 Subject: [PATCH 01/13] fix: add EII function aliases to exported symbols --- compiler/rustc_codegen_ssa/src/back/symbol_export.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 557b00b911aa9..3269fc5c9c1d5 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -199,6 +199,14 @@ fn exported_non_generic_symbols_provider_local<'tcx>( })) } + symbols.extend(sorted.iter().flat_map(|&(&def_id, &info)| { + tcx.codegen_fn_attrs(def_id).foreign_item_symbol_aliases.iter().map( + move |&(foreign_item, _linkage, _visibility)| { + (ExportedSymbol::NonGeneric(foreign_item), info) + }, + ) + })); + if tcx.entry_fn(()).is_some() { let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, tcx.sess.target.entry_name.as_ref())); From fd45124860b739675e0df36a6531dbc98536f5a8 Mon Sep 17 00:00:00 2001 From: zedddie Date: Thu, 12 Mar 2026 18:44:21 +0100 Subject: [PATCH 02/13] add generic & unresolved inference variables handling in `select_transmute_obligation_for_reporting` --- .../src/error_reporting/traits/fulfillment_errors.rs | 4 ++++ .../generic-transmute-from-regression.rs | 11 +++++++++++ .../generic-transmute-from-regression.stderr | 11 +++++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/ui/transmutability/generic-transmute-from-regression.rs create mode 100644 tests/ui/transmutability/generic-transmute-from-regression.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index d5383fd4d0831..598a7407c1aa8 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -2832,6 +2832,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_predicate: ty::PolyTraitPredicate<'tcx>, root_obligation: &PredicateObligation<'tcx>, ) -> (PredicateObligation<'tcx>, ty::PolyTraitPredicate<'tcx>) { + if obligation.predicate.has_non_region_param() || obligation.has_non_region_infer() { + return (obligation.clone(), trait_predicate); + } + let ocx = ObligationCtxt::new(self); let normalized_predicate = self.tcx.erase_and_anonymize_regions( self.tcx.instantiate_bound_regions_with_erased(trait_predicate), diff --git a/tests/ui/transmutability/generic-transmute-from-regression.rs b/tests/ui/transmutability/generic-transmute-from-regression.rs new file mode 100644 index 0000000000000..38568243ed9d0 --- /dev/null +++ b/tests/ui/transmutability/generic-transmute-from-regression.rs @@ -0,0 +1,11 @@ +//! Regression test for: +#![feature(transmutability)] + +fn foo(x: T) -> U { + unsafe { + std::mem::TransmuteFrom::transmute(x) + //~^ ERROR: the trait bound `U: TransmuteFrom` is not satisfied [E0277] + } +} + +fn main() {} diff --git a/tests/ui/transmutability/generic-transmute-from-regression.stderr b/tests/ui/transmutability/generic-transmute-from-regression.stderr new file mode 100644 index 0000000000000..3912aec0a9c84 --- /dev/null +++ b/tests/ui/transmutability/generic-transmute-from-regression.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `U: TransmuteFrom` is not satisfied + --> $DIR/generic-transmute-from-regression.rs:6:44 + | +LL | std::mem::TransmuteFrom::transmute(x) + | ---------------------------------- ^ the nightly-only, unstable trait `TransmuteFrom` is not implemented for `U` + | | + | required by a bound introduced by this call + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 69d9576b37dc62b9af763204dd7df8fff7935efe Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:09:53 -0400 Subject: [PATCH 03/13] refactor: move doc(rust_logo) check to parser --- .../rustc_attr_parsing/src/attributes/doc.rs | 23 ++++++++++++++++++- compiler/rustc_passes/src/check_attr.rs | 15 ++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index f8968639f98c2..3a8de4f1a3c21 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -1,10 +1,12 @@ use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; +use rustc_errors::msg; use rustc_feature::template; use rustc_hir::Target; use rustc_hir::attrs::{ AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow, }; use rustc_hir::lints::AttributeLintKind; +use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, edition, sym}; use thin_vec::ThinVec; @@ -553,7 +555,26 @@ impl DocParser { ), Some(sym::fake_variadic) => no_args_and_not_crate_level!(fake_variadic), Some(sym::search_unbox) => no_args_and_not_crate_level!(search_unbox), - Some(sym::rust_logo) => no_args_and_crate_level!(rust_logo), + Some(sym::rust_logo) => { + if let Err(span) = args.no_args() { + expected_no_args(cx, span); + return; + } + let span = path.span(); + if !check_attr_crate_level(cx, span) { + return; + } + if !cx.features().rustdoc_internals() { + feature_err( + cx.sess(), + sym::rustdoc_internals, + span, + msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"), + ) + .emit(); + } + self.attribute.rust_logo = Some(span); + } Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args), Some(sym::test) => { let Some(list) = args.list() else { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index bec6ab7e83551..5b6b214a09abe 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1145,7 +1145,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { html_no_source: _, // already checked in attr_parsing issue_tracker_base_url: _, - rust_logo, + // already checked in attr_parsing + rust_logo: _, // allowed anywhere test_attrs: _, // already checked in attr_parsing @@ -1174,18 +1175,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_doc_inline(hir_id, target, inline); - if let Some(span) = rust_logo - && !self.tcx.features().rustdoc_internals() - { - feature_err( - &self.tcx.sess, - sym::rustdoc_internals, - *span, - msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"), - ) - .emit(); - } - if let Some(span) = masked { self.check_doc_masked(*span, hir_id, target); } From 48987fdba7ccb3815bfa05d239f0f7b0deab1c0a Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:06:40 -0400 Subject: [PATCH 04/13] refactor: reuse doc attr helper for rust_logo --- .../rustc_attr_parsing/src/attributes/doc.rs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 3a8de4f1a3c21..099a75e11f3af 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -483,15 +483,19 @@ impl DocParser { } macro_rules! no_args_and_crate_level { ($ident: ident) => {{ + no_args_and_crate_level!($ident, |span| {}); + }}; + ($ident: ident, |$span:ident| $extra_validation:block) => {{ if let Err(span) = args.no_args() { expected_no_args(cx, span); return; } - let span = path.span(); - if !check_attr_crate_level(cx, span) { + let $span = path.span(); + if !check_attr_crate_level(cx, $span) { return; } - self.attribute.$ident = Some(span); + $extra_validation + self.attribute.$ident = Some($span); }}; } macro_rules! string_arg_and_crate_level { @@ -555,15 +559,7 @@ impl DocParser { ), Some(sym::fake_variadic) => no_args_and_not_crate_level!(fake_variadic), Some(sym::search_unbox) => no_args_and_not_crate_level!(search_unbox), - Some(sym::rust_logo) => { - if let Err(span) = args.no_args() { - expected_no_args(cx, span); - return; - } - let span = path.span(); - if !check_attr_crate_level(cx, span) { - return; - } + Some(sym::rust_logo) => no_args_and_crate_level!(rust_logo, |span| { if !cx.features().rustdoc_internals() { feature_err( cx.sess(), @@ -573,8 +569,7 @@ impl DocParser { ) .emit(); } - self.attribute.rust_logo = Some(span); - } + }), Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args), Some(sym::test) => { let Some(list) = args.list() else { From c461182521473cb7c786862b0ad6fbbce8a17baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 15 Mar 2026 01:23:27 +0000 Subject: [PATCH 05/13] Lex lifetimes using emoji and emit appropriate error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lex and parse emoji in lifetimes, and disallow them in the parser with a hard error. Allow emoji to start a lifetime name even if they are not XID_Start. ``` error: lifetimes cannot contain emoji --> $DIR/emoji-in-lifetime.rs:1:22 | LL | fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ>( | ^^^^^^^^^^^^^^^^^^^^^ ``` --- compiler/rustc_lexer/src/lib.rs | 19 ++++++++++++++++--- compiler/rustc_parse/src/lexer/mod.rs | 7 +++++-- tests/ui/lexer/emoji-in-lifetime.rs | 9 +++++++++ tests/ui/lexer/emoji-in-lifetime.stderr | 20 ++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/ui/lexer/emoji-in-lifetime.rs create mode 100644 tests/ui/lexer/emoji-in-lifetime.stderr diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index dc6e3b1f358dc..91cf3237bda8f 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -141,6 +141,7 @@ pub enum TokenKind { /// A lifetime, e.g. `'a`. Lifetime { starts_with_number: bool, + has_emoji: bool, }, /// `;` @@ -975,6 +976,7 @@ impl<'a> Cursor<'a> { fn lifetime_or_char(&mut self) -> TokenKind { debug_assert!(self.prev() == '\''); + let mut has_emoji = false; let can_be_a_lifetime = if self.second() == '\'' { // It's surely not a lifetime. false @@ -982,7 +984,12 @@ impl<'a> Cursor<'a> { // If the first symbol is valid for identifier, it can be a lifetime. // Also check if it's a number for a better error reporting (so '0 will // be reported as invalid lifetime and not as unterminated char literal). - is_id_start(self.first()) || self.first().is_ascii_digit() + let c = self.first(); + let is_emoji = !c.is_ascii() && c.is_emoji_char(); + if is_emoji { + has_emoji = true; + } + is_id_start(c) || c.is_ascii_digit() || is_emoji }; if !can_be_a_lifetime { @@ -1012,7 +1019,13 @@ impl<'a> Cursor<'a> { // First symbol can be a number (which isn't a valid identifier start), // so skip it without any checks. self.bump(); - self.eat_while(is_id_continue); + self.eat_while(|c| { + let is_emoji = !c.is_ascii() && c.is_emoji_char(); + if is_emoji { + has_emoji = true; + } + is_id_continue(c) || is_emoji + }); match self.first() { // Check if after skipping literal contents we've met a closing @@ -1024,7 +1037,7 @@ impl<'a> Cursor<'a> { Literal { kind, suffix_start: self.pos_within_token() } } '#' if !starts_with_number => UnknownPrefixLifetime, - _ => Lifetime { starts_with_number }, + _ => Lifetime { starts_with_number, has_emoji }, } } diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index cd90655125b2b..bfbc203754fc4 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -316,19 +316,22 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.lint_literal_unicode_text_flow(symbol, kind, self.mk_sp(start, self.pos), "literal"); token::Literal(token::Lit { kind, symbol, suffix }) } - rustc_lexer::TokenKind::Lifetime { starts_with_number } => { + rustc_lexer::TokenKind::Lifetime { starts_with_number, has_emoji } => { // Include the leading `'` in the real identifier, for macro // expansion purposes. See #12512 for the gory details of why // this is necessary. let lifetime_name = nfc_normalize(self.str_from(start)); self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1))); + let span = self.mk_sp(start, self.pos); if starts_with_number { - let span = self.mk_sp(start, self.pos); self.dcx() .struct_err("lifetimes cannot start with a number") .with_span(span) .stash(span, StashKey::LifetimeIsChar); } + if has_emoji { + self.dcx().struct_span_err(span, "lifetimes cannot contain emoji").emit(); + } token::Lifetime(lifetime_name, IdentIsRaw::No) } rustc_lexer::TokenKind::RawLifetime => { diff --git a/tests/ui/lexer/emoji-in-lifetime.rs b/tests/ui/lexer/emoji-in-lifetime.rs new file mode 100644 index 0000000000000..bbd0e27821a77 --- /dev/null +++ b/tests/ui/lexer/emoji-in-lifetime.rs @@ -0,0 +1,9 @@ +// #141081 +fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ()) {} +//~^ ERROR: lifetimes cannot contain emoji +//~| ERROR: lifetimes cannot contain emoji +fn main() { + '๐Ÿ›: { //~ ERROR: lifetimes cannot contain emoji + todo!(); + }; +} diff --git a/tests/ui/lexer/emoji-in-lifetime.stderr b/tests/ui/lexer/emoji-in-lifetime.stderr new file mode 100644 index 0000000000000..03f1f997b0d9e --- /dev/null +++ b/tests/ui/lexer/emoji-in-lifetime.stderr @@ -0,0 +1,20 @@ +error: lifetimes cannot contain emoji + --> $DIR/emoji-in-lifetime.rs:2:22 + | +LL | fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ ()) {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: lifetimes cannot contain emoji + --> $DIR/emoji-in-lifetime.rs:2:45 + | +LL | fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ ()) {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: lifetimes cannot contain emoji + --> $DIR/emoji-in-lifetime.rs:6:5 + | +LL | '๐Ÿ›: { + | ^^^ + +error: aborting due to 3 previous errors + From dfdc525ff244c0fa2c5f0e2181269ef76b90fd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 18 Mar 2026 18:09:47 +0000 Subject: [PATCH 06/13] Unify lifetime and identifier parsing --- Cargo.lock | 1 + compiler/rustc_lexer/src/lib.rs | 46 ++++++-------- compiler/rustc_parse/Cargo.toml | 1 + compiler/rustc_parse/src/lexer/mod.rs | 60 ++++++++++++++++--- tests/ui/lexer/emoji-in-lifetime.rs | 19 ++++-- tests/ui/lexer/emoji-in-lifetime.stderr | 40 +++++++++---- .../lex-bad-str-literal-as-char-1.stderr | 2 +- tests/ui/parser/numeric-lifetime.stderr | 4 +- 8 files changed, 119 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98567f858e9f1..2763d12fc9c33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4450,6 +4450,7 @@ dependencies = [ "thin-vec", "tracing", "unicode-normalization", + "unicode-properties", "unicode-width 0.2.2", ] diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index 91cf3237bda8f..fea8980103cce 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -140,8 +140,7 @@ pub enum TokenKind { /// A lifetime, e.g. `'a`. Lifetime { - starts_with_number: bool, - has_emoji: bool, + invalid: bool, }, /// `;` @@ -585,7 +584,7 @@ impl<'a> Cursor<'a> { let kind = RawStr { n_hashes: res.ok() }; Literal { kind, suffix_start } } - _ => self.ident_or_unknown_prefix(), + _ => self.ident_or_unknown_prefix(false), }, // Byte literal, byte string literal, raw byte string literal or identifier. @@ -604,7 +603,7 @@ impl<'a> Cursor<'a> { // Identifier (this should be checked after other variant that can // start as identifier). - c if is_id_start(c) => self.ident_or_unknown_prefix(), + c if is_id_start(c) => self.ident_or_unknown_prefix(false), // Numeric literal. c @ '0'..='9' => { @@ -662,7 +661,7 @@ impl<'a> Cursor<'a> { Literal { kind, suffix_start } } // Identifier starting with an emoji. Only lexed for graceful error recovery. - c if !c.is_ascii() && c.is_emoji_char() => self.invalid_ident(), + c if is_emoji(c) => self.invalid_ident(), _ => Unknown, }; if matches!(self.frontmatter_allowed, FrontmatterAllowed::Yes) @@ -833,25 +832,22 @@ impl<'a> Cursor<'a> { RawIdent } - fn ident_or_unknown_prefix(&mut self) -> TokenKind { - debug_assert!(is_id_start(self.prev())); + fn ident_or_unknown_prefix(&mut self, already_invalid: bool) -> TokenKind { + debug_assert!(is_id_start(self.prev()) || already_invalid); // Start is already eaten, eat the rest of identifier. self.eat_while(is_id_continue); // Known prefixes must have been handled earlier. So if // we see a prefix here, it is definitely an unknown prefix. match self.first() { '#' | '"' | '\'' => UnknownPrefix, - c if !c.is_ascii() && c.is_emoji_char() => self.invalid_ident(), + c if is_emoji(c) => self.invalid_ident(), _ => Ident, } } fn invalid_ident(&mut self) -> TokenKind { // Start is already eaten, eat the rest of identifier. - self.eat_while(|c| { - const ZERO_WIDTH_JOINER: char = '\u{200d}'; - is_id_continue(c) || (!c.is_ascii() && c.is_emoji_char()) || c == ZERO_WIDTH_JOINER - }); + self.eat_while(|c| is_id_continue(c) || is_emoji(c)); // An invalid identifier followed by '#' or '"' or '\'' could be // interpreted as an invalid literal prefix. We don't bother doing that // because the treatment of invalid identifiers and invalid prefixes @@ -896,7 +892,7 @@ impl<'a> Cursor<'a> { let kind = mk_kind_raw(res.ok()); Literal { kind, suffix_start } } - _ => self.ident_or_unknown_prefix(), + _ => self.ident_or_unknown_prefix(false), } } @@ -976,7 +972,7 @@ impl<'a> Cursor<'a> { fn lifetime_or_char(&mut self) -> TokenKind { debug_assert!(self.prev() == '\''); - let mut has_emoji = false; + let mut invalid = false; let can_be_a_lifetime = if self.second() == '\'' { // It's surely not a lifetime. false @@ -985,11 +981,9 @@ impl<'a> Cursor<'a> { // Also check if it's a number for a better error reporting (so '0 will // be reported as invalid lifetime and not as unterminated char literal). let c = self.first(); - let is_emoji = !c.is_ascii() && c.is_emoji_char(); - if is_emoji { - has_emoji = true; - } - is_id_start(c) || c.is_ascii_digit() || is_emoji + invalid |= c.is_ascii_digit(); + invalid |= is_emoji(c); + is_id_start(c) || invalid }; if !can_be_a_lifetime { @@ -1019,13 +1013,7 @@ impl<'a> Cursor<'a> { // First symbol can be a number (which isn't a valid identifier start), // so skip it without any checks. self.bump(); - self.eat_while(|c| { - let is_emoji = !c.is_ascii() && c.is_emoji_char(); - if is_emoji { - has_emoji = true; - } - is_id_continue(c) || is_emoji - }); + invalid |= matches!(self.ident_or_unknown_prefix(invalid), InvalidIdent); match self.first() { // Check if after skipping literal contents we've met a closing @@ -1037,7 +1025,7 @@ impl<'a> Cursor<'a> { Literal { kind, suffix_start: self.pos_within_token() } } '#' if !starts_with_number => UnknownPrefixLifetime, - _ => Lifetime { starts_with_number, has_emoji }, + _ => Lifetime { invalid }, } } @@ -1290,3 +1278,7 @@ impl<'a> Cursor<'a> { self.eat_while(is_id_continue); } } + +fn is_emoji(c: char) -> bool { + !c.is_ascii() && c.is_emoji_char() +} diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index 28a67ae12126b..2c9c4f54d1034 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -20,6 +20,7 @@ rustc_span = { path = "../rustc_span" } thin-vec = "0.2.12" tracing = "0.1" unicode-normalization = "0.1.25" +unicode-properties = { version = "0.1.4", default-features = false, features = ["emoji"] } unicode-width = "0.2.2" # tidy-alphabetical-end diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index bfbc203754fc4..4ecaea2aff437 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -17,6 +17,7 @@ use rustc_session::lint::builtin::{ use rustc_session::parse::ParseSess; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; use tracing::debug; +use unicode_properties::emoji::UnicodeEmoji; use crate::errors; use crate::lexer::diagnostics::TokenTreeDiagInfo; @@ -316,21 +317,62 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.lint_literal_unicode_text_flow(symbol, kind, self.mk_sp(start, self.pos), "literal"); token::Literal(token::Lit { kind, symbol, suffix }) } - rustc_lexer::TokenKind::Lifetime { starts_with_number, has_emoji } => { + rustc_lexer::TokenKind::Lifetime { invalid } => { // Include the leading `'` in the real identifier, for macro // expansion purposes. See #12512 for the gory details of why // this is necessary. let lifetime_name = nfc_normalize(self.str_from(start)); self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1))); let span = self.mk_sp(start, self.pos); - if starts_with_number { - self.dcx() - .struct_err("lifetimes cannot start with a number") - .with_span(span) - .stash(span, StashKey::LifetimeIsChar); - } - if has_emoji { - self.dcx().struct_span_err(span, "lifetimes cannot contain emoji").emit(); + if invalid { + let name = lifetime_name.as_str(); + // skip(1) to skip the `'` + let starts_with_number = matches!( + name.chars().skip(1).next(), + Some(c) if c.is_ascii_digit() + ); + let mut emoji = vec![]; + for (i, c) in name.char_indices().skip(1) { + let i = i as u32; + if !c.is_ascii() && c.is_emoji_char() { + let lo = start + BytePos(i); + emoji.push(self.mk_sp(lo, lo + Pos::from_usize(c.len_utf8()))); + } + } + let err = match (starts_with_number, &emoji[..]) { + (false, []) => { + unreachable!("lifetime {name:?} incorrectly marked as invalid?"); + } + (true, []) if name.len() > 2 => { + // Point at the first lifetime name character. + let start_span = self.mk_sp(start + BytePos(1), start + BytePos(2)); + self.dcx() + .struct_err(format!( + "lifetimes cannot start with a number: `{name}`" + )) + .with_span(start_span) + .with_span_label(span, "") + } + (true, []) => { + // Point at the whole lifetime name. + self.dcx() + .struct_err(format!( + "lifetimes cannot start with a number: `{name}`" + )) + .with_span(span) + } + (false, [_, ..]) => self.dcx() + .struct_err(format!("lifetimes cannot have emoji: `{name}`")) + .with_span(emoji.clone()) + .with_span_label(span, ""), + (true, [_, ..]) => self.dcx() + .struct_err(format!( + "invalid lifetime name: `{}`", + name.escape_default(), + )) + .with_span(span), + }; + err.stash(span, StashKey::LifetimeIsChar); } token::Lifetime(lifetime_name, IdentIsRaw::No) } diff --git a/tests/ui/lexer/emoji-in-lifetime.rs b/tests/ui/lexer/emoji-in-lifetime.rs index bbd0e27821a77..8994e592f8b46 100644 --- a/tests/ui/lexer/emoji-in-lifetime.rs +++ b/tests/ui/lexer/emoji-in-lifetime.rs @@ -1,9 +1,20 @@ // #141081 -fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ()) {} -//~^ ERROR: lifetimes cannot contain emoji -//~| ERROR: lifetimes cannot contain emoji +fn bad_lifetime_name< + '๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ,//~ ERROR: lifetimes cannot have emoji + '12, //~ ERROR: lifetimes cannot start with a number + 'a๐Ÿ›, //~ ERROR: lifetimes cannot have emoji + '1๐Ÿ›, //~ ERROR: invalid lifetime name + '1, //~ ERROR: lifetimes cannot start with a number + 'aโ€Œb // bare zero-width-joiners are accepted as XID_Continue +>() {} + + + + + + fn main() { - '๐Ÿ›: { //~ ERROR: lifetimes cannot contain emoji + '๐Ÿ›: { //~ ERROR: lifetimes cannot have emoji todo!(); }; } diff --git a/tests/ui/lexer/emoji-in-lifetime.stderr b/tests/ui/lexer/emoji-in-lifetime.stderr index 03f1f997b0d9e..4f5743fbed5ad 100644 --- a/tests/ui/lexer/emoji-in-lifetime.stderr +++ b/tests/ui/lexer/emoji-in-lifetime.stderr @@ -1,20 +1,38 @@ -error: lifetimes cannot contain emoji - --> $DIR/emoji-in-lifetime.rs:2:22 +error: lifetimes cannot have emoji: `'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ` + --> $DIR/emoji-in-lifetime.rs:3:6 | -LL | fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ ()) {} - | ^^^^^^^^^^^^^^^^^^^^^ +LL | '๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ, + | -^^^^^^------^^^^^^^^ -error: lifetimes cannot contain emoji - --> $DIR/emoji-in-lifetime.rs:2:45 +error: lifetimes cannot start with a number: `'12` + --> $DIR/emoji-in-lifetime.rs:4:6 | -LL | fn bad_lifetime_name<'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ>(_: &'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ ()) {} - | ^^^^^^^^^^^^^^^^^^^^^ +LL | '12, + | -^- -error: lifetimes cannot contain emoji +error: lifetimes cannot have emoji: `'a๐Ÿ›` + --> $DIR/emoji-in-lifetime.rs:5:7 + | +LL | 'a๐Ÿ›, + | --^^ + +error: invalid lifetime name: `\'1\u{1f41b}` --> $DIR/emoji-in-lifetime.rs:6:5 | +LL | '1๐Ÿ›, + | ^^^^ + +error: lifetimes cannot start with a number: `'1` + --> $DIR/emoji-in-lifetime.rs:7:5 + | +LL | '1, + | ^^ + +error: lifetimes cannot have emoji: `'๐Ÿ›` + --> $DIR/emoji-in-lifetime.rs:17:6 + | LL | '๐Ÿ›: { - | ^^^ + | -^^ -error: aborting due to 3 previous errors +error: aborting due to 6 previous errors diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr index 81ee697802b9c..7503774cd686f 100644 --- a/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr +++ b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr @@ -10,7 +10,7 @@ LL - println!('1 + 1'); LL + println!("1 + 1"); | -error: lifetimes cannot start with a number +error: lifetimes cannot start with a number: `'1` --> $DIR/lex-bad-str-literal-as-char-1.rs:3:14 | LL | println!('1 + 1'); diff --git a/tests/ui/parser/numeric-lifetime.stderr b/tests/ui/parser/numeric-lifetime.stderr index 7c1bcb7263171..46b06ae7dc3cb 100644 --- a/tests/ui/parser/numeric-lifetime.stderr +++ b/tests/ui/parser/numeric-lifetime.stderr @@ -6,13 +6,13 @@ LL | let x: usize = ""; | | | expected due to this -error: lifetimes cannot start with a number +error: lifetimes cannot start with a number: `'1` --> $DIR/numeric-lifetime.rs:1:10 | LL | struct S<'1> { s: &'1 usize } | ^^ -error: lifetimes cannot start with a number +error: lifetimes cannot start with a number: `'1` --> $DIR/numeric-lifetime.rs:1:20 | LL | struct S<'1> { s: &'1 usize } From 702197abd2ba344efce263a9b0e7e139032a8d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 18 Mar 2026 18:32:30 +0000 Subject: [PATCH 07/13] Use `bad_unicode_identifiers` for lifetimes --- compiler/rustc_parse/src/lexer/mod.rs | 56 ++++++++----------------- tests/ui/lexer/emoji-in-lifetime.rs | 9 ++-- tests/ui/lexer/emoji-in-lifetime.stderr | 41 +++++++++--------- 3 files changed, 45 insertions(+), 61 deletions(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 4ecaea2aff437..7d26ea9454f4f 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -331,48 +331,28 @@ impl<'psess, 'src> Lexer<'psess, 'src> { name.chars().skip(1).next(), Some(c) if c.is_ascii_digit() ); - let mut emoji = vec![]; - for (i, c) in name.char_indices().skip(1) { - let i = i as u32; - if !c.is_ascii() && c.is_emoji_char() { - let lo = start + BytePos(i); - emoji.push(self.mk_sp(lo, lo + Pos::from_usize(c.len_utf8()))); - } + if name.chars().any(|c| !c.is_ascii() && c.is_emoji_char()) { + self.psess + .bad_unicode_identifiers + .borrow_mut() + .entry(lifetime_name) + .or_default() + .push(span); } - let err = match (starts_with_number, &emoji[..]) { - (false, []) => { - unreachable!("lifetime {name:?} incorrectly marked as invalid?"); - } - (true, []) if name.len() > 2 => { + if starts_with_number { + let mut err = self.dcx() + .struct_err(format!( + "lifetimes cannot start with a number: `{name}`" + )) + .with_span(span); + if name.len() > 2 { // Point at the first lifetime name character. let start_span = self.mk_sp(start + BytePos(1), start + BytePos(2)); - self.dcx() - .struct_err(format!( - "lifetimes cannot start with a number: `{name}`" - )) - .with_span(start_span) - .with_span_label(span, "") + err.span(start_span); + err.span_label(span, ""); } - (true, []) => { - // Point at the whole lifetime name. - self.dcx() - .struct_err(format!( - "lifetimes cannot start with a number: `{name}`" - )) - .with_span(span) - } - (false, [_, ..]) => self.dcx() - .struct_err(format!("lifetimes cannot have emoji: `{name}`")) - .with_span(emoji.clone()) - .with_span_label(span, ""), - (true, [_, ..]) => self.dcx() - .struct_err(format!( - "invalid lifetime name: `{}`", - name.escape_default(), - )) - .with_span(span), - }; - err.stash(span, StashKey::LifetimeIsChar); + err.stash(span, StashKey::LifetimeIsChar); + } } token::Lifetime(lifetime_name, IdentIsRaw::No) } diff --git a/tests/ui/lexer/emoji-in-lifetime.rs b/tests/ui/lexer/emoji-in-lifetime.rs index 8994e592f8b46..3442a9380abe9 100644 --- a/tests/ui/lexer/emoji-in-lifetime.rs +++ b/tests/ui/lexer/emoji-in-lifetime.rs @@ -1,9 +1,10 @@ // #141081 fn bad_lifetime_name< - '๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ,//~ ERROR: lifetimes cannot have emoji + '๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ,//~ ERROR: identifiers cannot contain emoji '12, //~ ERROR: lifetimes cannot start with a number - 'a๐Ÿ›, //~ ERROR: lifetimes cannot have emoji - '1๐Ÿ›, //~ ERROR: invalid lifetime name + 'a๐Ÿ›, //~ ERROR: identifiers cannot contain emoji + '1๐Ÿ›, //~ ERROR: identifiers cannot contain emoji + //~^ ERROR: lifetimes cannot start with a number '1, //~ ERROR: lifetimes cannot start with a number 'aโ€Œb // bare zero-width-joiners are accepted as XID_Continue >() {} @@ -14,7 +15,7 @@ fn bad_lifetime_name< fn main() { - '๐Ÿ›: { //~ ERROR: lifetimes cannot have emoji + 'a๐Ÿ›: { // pointed at on the error from line 5 todo!(); }; } diff --git a/tests/ui/lexer/emoji-in-lifetime.stderr b/tests/ui/lexer/emoji-in-lifetime.stderr index 4f5743fbed5ad..7e205639a9a3c 100644 --- a/tests/ui/lexer/emoji-in-lifetime.stderr +++ b/tests/ui/lexer/emoji-in-lifetime.stderr @@ -1,8 +1,23 @@ -error: lifetimes cannot have emoji: `'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ` - --> $DIR/emoji-in-lifetime.rs:3:6 +error: identifiers cannot contain emoji: `'๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ` + --> $DIR/emoji-in-lifetime.rs:3:5 | LL | '๐Ÿ›๐Ÿ›๐Ÿ›family๐Ÿ‘จ๐Ÿ‘ฉ๐Ÿ‘ง๐Ÿ‘ฆ, - | -^^^^^^------^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ + +error: identifiers cannot contain emoji: `'a๐Ÿ›` + --> $DIR/emoji-in-lifetime.rs:5:5 + | +LL | 'a๐Ÿ›, + | ^^^^ +... +LL | 'a๐Ÿ›: { // pointed at on the error from line 5 + | ^^^^ + +error: identifiers cannot contain emoji: `'1๐Ÿ›` + --> $DIR/emoji-in-lifetime.rs:6:5 + | +LL | '1๐Ÿ›, + | ^^^^ error: lifetimes cannot start with a number: `'12` --> $DIR/emoji-in-lifetime.rs:4:6 @@ -10,29 +25,17 @@ error: lifetimes cannot start with a number: `'12` LL | '12, | -^- -error: lifetimes cannot have emoji: `'a๐Ÿ›` - --> $DIR/emoji-in-lifetime.rs:5:7 - | -LL | 'a๐Ÿ›, - | --^^ - -error: invalid lifetime name: `\'1\u{1f41b}` - --> $DIR/emoji-in-lifetime.rs:6:5 +error: lifetimes cannot start with a number: `'1๐Ÿ›` + --> $DIR/emoji-in-lifetime.rs:6:6 | LL | '1๐Ÿ›, - | ^^^^ + | -^-- error: lifetimes cannot start with a number: `'1` - --> $DIR/emoji-in-lifetime.rs:7:5 + --> $DIR/emoji-in-lifetime.rs:8:5 | LL | '1, | ^^ -error: lifetimes cannot have emoji: `'๐Ÿ›` - --> $DIR/emoji-in-lifetime.rs:17:6 - | -LL | '๐Ÿ›: { - | -^^ - error: aborting due to 6 previous errors From ea749eaa52dea61adc5f6113e6893ec3ec37513c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 18 Mar 2026 18:38:15 +0000 Subject: [PATCH 08/13] Fix rust-analyzer --- src/tools/rust-analyzer/crates/parser/src/lexed_str.rs | 6 +++--- .../test_data/lexer/err/lifetime_starts_with_a_number.rast | 4 ++-- .../test_data/lexer/err/lifetime_starts_with_a_number.txt | 4 ++-- .../test_data/lexer/err/unclosed_char_with_ferris.rast | 2 +- .../rust-analyzer/crates/proc-macro-srv/src/token_stream.rs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index d7eec6cde8c01..df7bef5843da8 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -255,9 +255,9 @@ impl<'a> Converter<'a> { return; } - rustc_lexer::TokenKind::Lifetime { starts_with_number } => { - if *starts_with_number { - errors.push("Lifetime name cannot start with a number".into()); + rustc_lexer::TokenKind::Lifetime { invalid } => { + if *invalid { + errors.push("Lifetime name contains invalid characters".into()); } LIFETIME_IDENT } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.rast b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.rast index e919bf2a4aef2..b2bf087e749f3 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.rast @@ -1,4 +1,4 @@ -LIFETIME_IDENT "'1" error: Lifetime name cannot start with a number +LIFETIME_IDENT "'1" error: Lifetime name contains invalid characters WHITESPACE "\n" -LIFETIME_IDENT "'1lifetime" error: Lifetime name cannot start with a number +LIFETIME_IDENT "'1lifetime" error: Lifetime name contains invalid characters WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.txt b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.txt index e919bf2a4aef2..b2bf087e749f3 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.txt +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/lifetime_starts_with_a_number.txt @@ -1,4 +1,4 @@ -LIFETIME_IDENT "'1" error: Lifetime name cannot start with a number +LIFETIME_IDENT "'1" error: Lifetime name contains invalid characters WHITESPACE "\n" -LIFETIME_IDENT "'1lifetime" error: Lifetime name cannot start with a number +LIFETIME_IDENT "'1lifetime" error: Lifetime name contains invalid characters WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_char_with_ferris.rast b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_char_with_ferris.rast index 56f19cce0784e..6ace31f434ef2 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_char_with_ferris.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_char_with_ferris.rast @@ -1 +1 @@ -CHAR "'๐Ÿฆ€" error: Missing trailing `'` symbol to terminate the character literal +LIFETIME_IDENT "'๐Ÿฆ€" error: Lifetime name contains invalid characters diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs index 2358f6963c79e..d6773153ed988 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs @@ -302,9 +302,9 @@ impl TokenStream { span: span.derive_ranged(range), })) } - rustc_lexer::TokenKind::Lifetime { starts_with_number } => { - if starts_with_number { - return Err("Lifetime cannot start with a number".to_owned()); + rustc_lexer::TokenKind::Lifetime { invalid } => { + if invalid { + return Err(format!("Invalid lifetime identifier: `{}`", &s[range])); } let range = range.start + 1..range.end; tokenstream.push(TokenTree::Punct(Punct { From 956917dd6d995708cef69747d6c602effff86076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 18 Mar 2026 18:39:42 +0000 Subject: [PATCH 09/13] remove whitespace --- tests/ui/lexer/emoji-in-lifetime.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/ui/lexer/emoji-in-lifetime.rs b/tests/ui/lexer/emoji-in-lifetime.rs index 3442a9380abe9..e4a12f332ae70 100644 --- a/tests/ui/lexer/emoji-in-lifetime.rs +++ b/tests/ui/lexer/emoji-in-lifetime.rs @@ -9,11 +9,6 @@ fn bad_lifetime_name< 'aโ€Œb // bare zero-width-joiners are accepted as XID_Continue >() {} - - - - - fn main() { 'a๐Ÿ›: { // pointed at on the error from line 5 todo!(); From 4f1676d11c2a13d69ae5e40fc649785b1b4645e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 18 Mar 2026 20:04:17 +0000 Subject: [PATCH 10/13] fix test --- compiler/rustc_lexer/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_lexer/src/tests.rs b/compiler/rustc_lexer/src/tests.rs index a7357ba38c8e4..f74ba7f532a68 100644 --- a/compiler/rustc_lexer/src/tests.rs +++ b/compiler/rustc_lexer/src/tests.rs @@ -231,7 +231,7 @@ fn lifetime() { "'abc", FrontmatterAllowed::No, expect![[r#" - Token { kind: Lifetime { starts_with_number: false }, len: 4 } + Token { kind: Lifetime { invalid: false }, len: 4 } "#]], ); } From d29c4895994dae92c6e8e47aec08fd4c81fb5d54 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 9 Mar 2026 01:41:56 +0900 Subject: [PATCH 11/13] add self-referential param-env normalization regression avoid ICE on invalid param-env normalization remove 120033 crash test fix comments use rustc_no_implicit_bounds set #![allow(incomplete_features)] --- .../rustc_trait_selection/src/traits/mod.rs | 19 +++----- tests/crashes/120033.rs | 16 ------- ...zed-param-env-unconstrained-type-120033.rs | 22 ++++++++++ ...param-env-unconstrained-type-120033.stderr | 43 +++++++++++++++++++ ...elf-referential-param-env-normalization.rs | 18 ++++++++ ...referential-param-env-normalization.stderr | 42 ++++++++++++++++++ 6 files changed, 132 insertions(+), 28 deletions(-) delete mode 100644 tests/crashes/120033.rs create mode 100644 tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.rs create mode 100644 tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.stderr create mode 100644 tests/ui/traits/self-referential-param-env-normalization.rs create mode 100644 tests/ui/traits/self-referential-param-env-normalization.stderr diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 1fde7e5d43b76..bdad1b259b733 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -300,19 +300,14 @@ fn do_normalize_predicates<'tcx>( Ok(predicates) => Ok(predicates), Err(fixup_err) => { // If we encounter a fixup error, it means that some type - // variable wound up unconstrained. I actually don't know - // if this can happen, and I certainly don't expect it to - // happen often, but if it did happen it probably - // represents a legitimate failure due to some kind of - // unconstrained variable. - // - // @lcnr: Let's still ICE here for now. I want a test case - // for that. - span_bug!( + // variable wound up unconstrained. That can happen for + // ill-formed impls, so we delay a bug here instead of + // immediately ICEing and let type checking report the + // actual user-facing errors. + Err(tcx.dcx().span_delayed_bug( span, - "inference variables in normalized parameter environment: {}", - fixup_err - ); + format!("inference variables in normalized parameter environment: {fixup_err}"), + )) } } } diff --git a/tests/crashes/120033.rs b/tests/crashes/120033.rs deleted file mode 100644 index 7584f98ec9060..0000000000000 --- a/tests/crashes/120033.rs +++ /dev/null @@ -1,16 +0,0 @@ -//@ known-bug: #120033 -#![feature(non_lifetime_binders)] -#![allow(sized_hierarchy_migration)] -#![feature(sized_hierarchy)] // added to keep parameters unconstrained - -pub trait Foo { - type Bar; -} - -pub struct Bar {} - -pub fn f() -where - T1: for Foo>, - T2: for Foo = T1::Bar>, -{} diff --git a/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.rs b/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.rs new file mode 100644 index 0000000000000..52f83966c6bd1 --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.rs @@ -0,0 +1,22 @@ +//@ compile-flags: --crate-type=lib + +// Regression test for + +#![feature(rustc_attrs)] +#![feature(non_lifetime_binders)] +#![allow(incomplete_features)] +#![rustc_no_implicit_bounds] + +pub trait Foo { + type Bar; +} + +pub struct Bar {} //~ ERROR cannot find trait `AutoTrait` + +pub fn f() +where + T1: for Foo>, //~ ERROR missing generics for associated type `Foo::Bar` + //~| ERROR missing generics for associated type `Foo::Bar` + T2: for Foo = T1::Bar>, +{ +} diff --git a/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.stderr b/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.stderr new file mode 100644 index 0000000000000..c95e6c68fc6a1 --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/normalized-param-env-unconstrained-type-120033.stderr @@ -0,0 +1,43 @@ +error[E0405]: cannot find trait `AutoTrait` in this scope + --> $DIR/normalized-param-env-unconstrained-type-120033.rs:14:20 + | +LL | pub struct Bar {} + | ^^^^^^^^^ not found in this scope + +error[E0107]: missing generics for associated type `Foo::Bar` + --> $DIR/normalized-param-env-unconstrained-type-120033.rs:18:27 + | +LL | T1: for Foo>, + | ^^^ expected 1 generic argument + | +note: associated type defined here, with 1 generic parameter: `K` + --> $DIR/normalized-param-env-unconstrained-type-120033.rs:11:10 + | +LL | type Bar; + | ^^^ - +help: add missing generic argument + | +LL | T1: for Foo = Bar>, + | +++ + +error[E0107]: missing generics for associated type `Foo::Bar` + --> $DIR/normalized-param-env-unconstrained-type-120033.rs:18:27 + | +LL | T1: for Foo>, + | ^^^ expected 1 generic argument + | +note: associated type defined here, with 1 generic parameter: `K` + --> $DIR/normalized-param-env-unconstrained-type-120033.rs:11:10 + | +LL | type Bar; + | ^^^ - + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: add missing generic argument + | +LL | T1: for Foo = Bar>, + | +++ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0107, E0405. +For more information about an error, try `rustc --explain E0107`. diff --git a/tests/ui/traits/self-referential-param-env-normalization.rs b/tests/ui/traits/self-referential-param-env-normalization.rs new file mode 100644 index 0000000000000..a40c8cc26bd94 --- /dev/null +++ b/tests/ui/traits/self-referential-param-env-normalization.rs @@ -0,0 +1,18 @@ +//~ ERROR overflow evaluating the requirement `Self: StreamingIterator<'_>` [E0275] +// Regression test for . + +trait StreamingIterator<'a> { + type Item: 'a; +} + +impl<'b, I, T> StreamingIterator<'b> for I +//~^ ERROR the type parameter `T` is not constrained by the impl trait, self type, or predicates [E0207] +where + I: IntoIterator, + T: FnMut(Self::Item, I::Item), +{ + type Item = T; + //~^ ERROR overflow evaluating the requirement `I: IntoIterator` [E0275] +} + +fn main() {} diff --git a/tests/ui/traits/self-referential-param-env-normalization.stderr b/tests/ui/traits/self-referential-param-env-normalization.stderr new file mode 100644 index 0000000000000..5e9c2c92eaac6 --- /dev/null +++ b/tests/ui/traits/self-referential-param-env-normalization.stderr @@ -0,0 +1,42 @@ +error[E0275]: overflow evaluating the requirement `Self: StreamingIterator<'_>` + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`self_referential_param_env_normalization`) +note: required for `Self` to implement `StreamingIterator<'_>` + --> $DIR/self-referential-param-env-normalization.rs:8:16 + | +LL | impl<'b, I, T> StreamingIterator<'b> for I + | ^^^^^^^^^^^^^^^^^^^^^ ^ +... +LL | T: FnMut(Self::Item, I::Item), + | -------------------------- unsatisfied trait bound introduced here + = note: 127 redundant requirements hidden + = note: required for `Self` to implement `StreamingIterator<'a>` + +error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates + --> $DIR/self-referential-param-env-normalization.rs:8:13 + | +LL | impl<'b, I, T> StreamingIterator<'b> for I + | ^ unconstrained type parameter + +error[E0275]: overflow evaluating the requirement `I: IntoIterator` + --> $DIR/self-referential-param-env-normalization.rs:14:17 + | +LL | type Item = T; + | ^ + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`self_referential_param_env_normalization`) +note: required for `I` to implement `StreamingIterator<'_>` + --> $DIR/self-referential-param-env-normalization.rs:8:16 + | +LL | impl<'b, I, T> StreamingIterator<'b> for I + | ^^^^^^^^^^^^^^^^^^^^^ ^ +... +LL | T: FnMut(Self::Item, I::Item), + | -------------------------- unsatisfied trait bound introduced here + = note: 127 redundant requirements hidden + = note: required for `I` to implement `StreamingIterator<'b>` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0207, E0275. +For more information about an error, try `rustc --explain E0207`. From 6518de37d450f35e18fc49502ea6183fda065714 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 30 Mar 2026 21:55:05 +0800 Subject: [PATCH 12/13] Skip suggestions pointing to extern macro def for assert_eq --- .../src/error_reporting/traits/suggestions.rs | 34 +++++++++--- .../assert-ne-no-invalid-help-issue-146204.rs | 23 ++++++++ ...ert-ne-no-invalid-help-issue-146204.stderr | 55 +++++++++++++++++++ 3 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 tests/ui/macros/assert-ne-no-invalid-help-issue-146204.rs create mode 100644 tests/ui/macros/assert-ne-no-invalid-help-issue-146204.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index a16bbf20238c0..151008790daf0 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -545,8 +545,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .all(|obligation| self.predicate_may_hold(obligation)) }) && steps > 0 { + if span.in_external_macro(self.tcx.sess.source_map()) { + return false; + } let derefs = "*".repeat(steps); let msg = "consider dereferencing here"; + let call_node = self.tcx.hir_node(*call_hir_id); let is_receiver = matches!( call_node, @@ -593,7 +597,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }) { // Suggest dereferencing the LHS, RHS, or both terms of a binop if possible - let trait_pred = predicate.unwrap_or(trait_pred); let lhs_ty = self.tcx.instantiate_bound_regions_with_erased(trait_pred.self_ty()); let lhs_autoderef = (self.autoderef_steps)(lhs_ty); @@ -644,6 +647,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }) { let make_sugg = |mut expr: &Expr<'_>, mut steps| { + if expr.span.in_external_macro(self.tcx.sess.source_map()) { + return None; + } let mut prefix_span = expr.span.shrink_to_lo(); let mut msg = "consider dereferencing here"; if let hir::ExprKind::AddrOf(_, _, inner) = expr.kind { @@ -661,10 +667,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } // Empty suggestions with empty spans ICE with debug assertions if steps == 0 { - return ( + return Some(( msg.trim_end_matches(" and dereferencing instead"), vec![(prefix_span, String::new())], - ); + )); } let derefs = "*".repeat(steps); let needs_parens = steps > 0 && expr_needs_parens(expr); @@ -686,7 +692,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { if !prefix_span.is_empty() { suggestion.push((prefix_span, String::new())); } - (msg, suggestion) + Some((msg, suggestion)) }; if let Some(lsteps) = lsteps @@ -694,8 +700,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { && lsteps > 0 && rsteps > 0 { - let mut suggestion = make_sugg(lhs, lsteps).1; - suggestion.append(&mut make_sugg(rhs, rsteps).1); + let Some((_, mut suggestion)) = make_sugg(lhs, lsteps) else { + return false; + }; + let Some((_, mut rhs_suggestion)) = make_sugg(rhs, rsteps) else { + return false; + }; + suggestion.append(&mut rhs_suggestion); err.multipart_suggestion( "consider dereferencing both sides of the expression", suggestion, @@ -705,13 +716,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } else if let Some(lsteps) = lsteps && lsteps > 0 { - let (msg, suggestion) = make_sugg(lhs, lsteps); + let Some((msg, suggestion)) = make_sugg(lhs, lsteps) else { + return false; + }; err.multipart_suggestion(msg, suggestion, Applicability::MachineApplicable); return true; } else if let Some(rsteps) = rsteps && rsteps > 0 { - let (msg, suggestion) = make_sugg(rhs, rsteps); + let Some((msg, suggestion)) = make_sugg(rhs, rsteps) else { + return false; + }; err.multipart_suggestion(msg, suggestion, Applicability::MachineApplicable); return true; } @@ -4815,6 +4830,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { candidate_impls: &[ImplCandidate<'tcx>], span: Span, ) { + if span.in_external_macro(self.tcx.sess.source_map()) { + return; + } // We can only suggest the slice coercion for function and binary operation arguments, // since the suggestion would make no sense in turbofish or call let (ObligationCauseCode::BinOp { .. } | ObligationCauseCode::FunctionArg { .. }) = diff --git a/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.rs b/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.rs new file mode 100644 index 0000000000000..5a6ef0421e2fa --- /dev/null +++ b/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.rs @@ -0,0 +1,23 @@ +macro_rules! local_assert_ne { + ($left:expr, $right:expr $(,)?) => { + match (&$left, &$right) { + (left_val, right_val) => { + if *left_val == *right_val { + //~^ ERROR can't compare `[u8; 4]` with `&[u8; 4]` + panic!(); + } + } + } + }; +} + +fn main() { + let buf = [0_u8; 4]; + assert_ne!(buf, b"----"); + //~^ ERROR can't compare `[u8; 4]` with `&[u8; 4]` + + assert_eq!(buf, b"----"); + //~^ ERROR can't compare `[u8; 4]` with `&[u8; 4]` + + local_assert_ne!(buf, b"----"); +} diff --git a/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.stderr b/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.stderr new file mode 100644 index 0000000000000..359deee7bee4b --- /dev/null +++ b/tests/ui/macros/assert-ne-no-invalid-help-issue-146204.stderr @@ -0,0 +1,55 @@ +error[E0277]: can't compare `[u8; 4]` with `&[u8; 4]` + --> $DIR/assert-ne-no-invalid-help-issue-146204.rs:16:5 + | +LL | assert_ne!(buf, b"----"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ no implementation for `[u8; 4] == &[u8; 4]` + | + = help: the trait `PartialEq<&[u8; 4]>` is not implemented for `[u8; 4]` + = help: the following other types implement trait `PartialEq`: + `&[T]` implements `PartialEq>` + `&[T]` implements `PartialEq<[U; N]>` + `&[u8; N]` implements `PartialEq` + `&[u8; N]` implements `PartialEq` + `&[u8]` implements `PartialEq` + `&[u8]` implements `PartialEq` + `&mut [T]` implements `PartialEq>` + `&mut [T]` implements `PartialEq<[U; N]>` + and 11 others + +error[E0277]: can't compare `[u8; 4]` with `&[u8; 4]` + --> $DIR/assert-ne-no-invalid-help-issue-146204.rs:19:5 + | +LL | assert_eq!(buf, b"----"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ no implementation for `[u8; 4] == &[u8; 4]` + | + = help: the trait `PartialEq<&[u8; 4]>` is not implemented for `[u8; 4]` + = help: the following other types implement trait `PartialEq`: + `&[T]` implements `PartialEq>` + `&[T]` implements `PartialEq<[U; N]>` + `&[u8; N]` implements `PartialEq` + `&[u8; N]` implements `PartialEq` + `&[u8]` implements `PartialEq` + `&[u8]` implements `PartialEq` + `&mut [T]` implements `PartialEq>` + `&mut [T]` implements `PartialEq<[U; N]>` + and 11 others + +error[E0277]: can't compare `[u8; 4]` with `&[u8; 4]` + --> $DIR/assert-ne-no-invalid-help-issue-146204.rs:5:30 + | +LL | if *left_val == *right_val { + | ^^ no implementation for `[u8; 4] == &[u8; 4]` +... +LL | local_assert_ne!(buf, b"----"); + | ------------------------------ in this macro invocation + | + = help: the trait `PartialEq<&[u8; 4]>` is not implemented for `[u8; 4]` + = note: this error originates in the macro `local_assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider dereferencing here + | +LL | if *left_val == **right_val { + | + + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. From 59fe28d0ff4e6fa757d2ad31149252ddaff0d1df Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 18 Jun 2025 06:25:32 +0000 Subject: [PATCH 13/13] compiler-builtins: Clean up features Remove the `compiler-builtins` feature from default because it prevents testing via the default `cargo test` command. It made more sense as a default when `compiler-builtins` was a dependency that some crates added via crates.io, but is no longer needed. The `rustc-dep-of-std` feature is also removed since it doesn't do anything beyond what the `compiler-builtins` feature already does. --- library/alloc/Cargo.toml | 2 +- library/compiler-builtins/builtins-shim/Cargo.toml | 8 +++----- library/compiler-builtins/compiler-builtins/Cargo.toml | 10 ++++------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/library/alloc/Cargo.toml b/library/alloc/Cargo.toml index 541257b6cda6e..b32e5e991cc74 100644 --- a/library/alloc/Cargo.toml +++ b/library/alloc/Cargo.toml @@ -16,7 +16,7 @@ bench = false [dependencies] core = { path = "../core", public = true } -compiler_builtins = { path = "../compiler-builtins/compiler-builtins", features = ["rustc-dep-of-std"] } +compiler_builtins = { path = "../compiler-builtins/compiler-builtins", features = ["compiler-builtins"] } [features] compiler-builtins-mem = ['compiler_builtins/mem'] diff --git a/library/compiler-builtins/builtins-shim/Cargo.toml b/library/compiler-builtins/builtins-shim/Cargo.toml index 37d3407e9f668..32b0308a7b3c9 100644 --- a/library/compiler-builtins/builtins-shim/Cargo.toml +++ b/library/compiler-builtins/builtins-shim/Cargo.toml @@ -39,7 +39,7 @@ test = false cc = { version = "1.2", optional = true } [features] -default = ["compiler-builtins"] +default = [] # Enable compilation of C code in compiler-rt, filling in some more optimized # implementations and also filling in unimplemented intrinsics @@ -50,7 +50,8 @@ c = ["dep:cc"] # the generic versions on all platforms. no-asm = [] -# Flag this library as the unstable compiler-builtins lib +# Flag this library as the unstable compiler-builtins lib. This must be enabled +# when using as `std`'s dependency.' compiler-builtins = [] # Generate memory-related intrinsics like memcpy @@ -60,9 +61,6 @@ mem = [] # compiler-rt implementations. Also used for testing mangled-names = [] -# Only used in the compiler's build system -rustc-dep-of-std = ["compiler-builtins"] - # This makes certain traits and function specializations public that # are not normally public but are required by the `builtins-test` unstable-public-internals = [] diff --git a/library/compiler-builtins/compiler-builtins/Cargo.toml b/library/compiler-builtins/compiler-builtins/Cargo.toml index a8b8920421b3e..d9acb8341d483 100644 --- a/library/compiler-builtins/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/compiler-builtins/Cargo.toml @@ -34,7 +34,7 @@ core = { path = "../../core", optional = true } cc = { version = "1.2", optional = true } [features] -default = ["compiler-builtins"] +default = [] # Enable compilation of C code in compiler-rt, filling in some more optimized # implementations and also filling in unimplemented intrinsics @@ -45,8 +45,9 @@ c = ["dep:cc"] # the generic versions on all platforms. no-asm = [] -# Flag this library as the unstable compiler-builtins lib -compiler-builtins = [] +# Flag this library as the unstable compiler-builtins lib. This must be enabled +# when using as `std`'s dependency.' +compiler-builtins = ["dep:core"] # Generate memory-related intrinsics like memcpy mem = [] @@ -55,9 +56,6 @@ mem = [] # compiler-rt implementations. Also used for testing mangled-names = [] -# Only used in the compiler's build system -rustc-dep-of-std = ["compiler-builtins", "dep:core"] - # This makes certain traits and function specializations public that # are not normally public but are required by the `builtins-test` unstable-public-internals = []