From cffd7bcf6758ef05f6e9f71e5a1ecb9ca6cf3232 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 6 Aug 2026 19:30:40 -0700 Subject: [PATCH 1/3] Make `WhereClause` optional in generics Many items don't have generics, and many generics don't have where clauses. `WhereClause` is 24 bytes: a `bool`, a `ThinVec` of predicates, and a `Span`. Reduce that to 8 bytes by making it an `Option` wrapper. The `has_where_token` case gets encoded in the `Option`: a `WhereClause(None)` has no `where` token, while a `WhereClause(Some(...))` with a non-empty span has one (even if the predicate list is empty, like `where {}`). This reduces the size of several key AST structures: - `Impl` goes from 80 bytes to 64, making it one cache line. - `Item` goes from 144 bytes to 128, making it two cache lines and bringing it below the threshold where copies don't use `memcpy`. - `ItemKind` goes from 88 bytes to 72, tantalizingly close to 64. - `Fn` goes from 192 bytes to 176. - `Generics` goes from 40 bytes to 24. rustc_builtin_macros now collects predicates and creates a `WhereClause` at the end. rustc_ast_lowering now needs another way to get the span for inserting a `where` clause if there isn't already one. Add that as a parameter to `lower_generics`, and pass it in from the various callers, determined from other spans we already have. This requires a little care, but allows us to avoid an allocation for common cases of items that don't have `where` clauses. As an exception, we do capture the insertion span for `Impl` block `where` clauses, because reconstructing those hits a corner case with spans partially coming coming from macros; in that case, the logic to find the `def_span` needs the where-clause span, so we ensure we have it in that case. We also record an insertion span for synthetic derives, pointing to the original structure definition. Relevant `-Zinput-stats` diff for compiling the huge `aws-sdk-ec2` crate: ```diff -ast-stats Item 14_353_632 ( 4.4%) 99_678 144 -ast-stats - ExternCrate 144 ( 0.0%) 1 -ast-stats - MacroDef 144 ( 0.0%) 1 -ast-stats - Trait 576 ( 0.0%) 4 -ast-stats - TyAlias 12_960 ( 0.0%) 90 -ast-stats - Const 114_768 ( 0.0%) 797 -ast-stats - Enum 180_432 ( 0.1%) 1_253 -ast-stats - Static 453_312 ( 0.1%) 3_148 -ast-stats - Fn 976_320 ( 0.3%) 6_780 -ast-stats - Mod 1_241_136 ( 0.4%) 8_619 -ast-stats - Struct 1_351_152 ( 0.4%) 9_383 -ast-stats - Use 1_488_672 ( 0.5%) 10_338 -ast-stats - Impl 8_534_016 ( 2.6%) 59_264 +ast-stats Item 12_758_784 ( 4.0%) 99_678 128 +ast-stats - ExternCrate 128 ( 0.0%) 1 +ast-stats - MacroDef 128 ( 0.0%) 1 +ast-stats - Trait 512 ( 0.0%) 4 +ast-stats - TyAlias 11_520 ( 0.0%) 90 +ast-stats - Const 102_016 ( 0.0%) 797 +ast-stats - Enum 160_384 ( 0.0%) 1_253 +ast-stats - Static 402_944 ( 0.1%) 3_148 +ast-stats - Fn 867_840 ( 0.3%) 6_780 +ast-stats - Mod 1_103_232 ( 0.3%) 8_619 +ast-stats - Struct 1_201_024 ( 0.4%) 9_383 +ast-stats - Use 1_323_264 ( 0.4%) 10_338 +ast-stats - Impl 7_585_792 ( 2.4%) 59_264 [...] -ast-stats Total 322_653_552 7_596_184 +ast-stats Total 321_058_704 7_596_184 ``` --- compiler/rustc_ast/src/ast.rs | 113 +++++++++++++++--- compiler/rustc_ast/src/visit.rs | 1 + compiler/rustc_ast_lowering/src/item.rs | 96 +++++++++++---- .../rustc_ast_passes/src/ast_validation.rs | 40 ++++--- .../rustc_ast_pretty/src/pprust/state/item.rs | 5 +- .../src/deriving/coerce_pointee.rs | 51 +++++--- .../src/deriving/generic/mod.rs | 53 +++++--- .../src/deriving/generic/ty.rs | 10 +- compiler/rustc_parse/src/parser/function.rs | 2 +- compiler/rustc_parse/src/parser/generics.rs | 25 +--- compiler/rustc_parse/src/parser/item.rs | 41 +++---- compiler/rustc_resolve/src/late.rs | 2 +- .../rustc_resolve/src/late/diagnostics.rs | 2 +- .../const-generics/issues/issue-71202.stderr | 8 +- tests/ui/stats/input-stats.stderr | 18 +-- 15 files changed, 300 insertions(+), 167 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 110c64c103acb..8179b711d3bd1 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -32,8 +32,8 @@ use rustc_macros::{Decodable, Encodable, StableHash, Walkable}; pub use rustc_span::AttrId; use rustc_span::def_id::LocalDefId; use rustc_span::{ - ByteSymbol, DUMMY_SP, ErrorGuaranteed, Ident, LocalExpnId, Span, Spanned, Symbol, kw, respan, - sym, + BytePos, ByteSymbol, DUMMY_SP, ErrorGuaranteed, Ident, LocalExpnId, Span, Spanned, Symbol, kw, + respan, sym, }; use thin_vec::{ThinVec, thin_vec}; @@ -476,20 +476,78 @@ pub struct Generics { } /// A where-clause in a definition. +/// +/// This uses a thin representation, because many items don't have generics and many of those that +/// do don't have where clauses. +/// +/// - `None` means no where clause. +/// - `Some` with an empty `predicates` and a non-empty span means an empty where clause, like +/// `struct Foo where {}`. This allows us to pretty-print accurately and provide correct +/// suggestion diagnostics. +/// - `Some` with an empty `span` means a generated synthetic where clause where the `span` is the +/// insertion point if a suggestion wants to add a where clause. #[derive(Clone, Encodable, Decodable, Debug, Default, Walkable)] -pub struct WhereClause { - /// `true` if we ate a `where` token. - /// - /// This can happen if we parsed no predicates, e.g., `struct Foo where {}`. - /// This allows us to pretty-print accurately and provide correct suggestion diagnostics. - pub has_where_token: bool, - pub predicates: ThinVec, - pub span: Span, +pub struct WhereClause(Option>); + +/// A where-clause in a definition. +#[derive(Clone, Encodable, Decodable, Debug, Default, Walkable)] +pub struct WhereClauseData { + predicates: ThinVec, + span: Span, } impl WhereClause { - pub fn is_empty(&self) -> bool { - !self.has_where_token && self.predicates.is_empty() + /// Create a new `WhereClause` that has a `where` keyword (even if the clause is empty) + /// + /// This will panic if `span` is empty. + /// + /// If generating a synthetic where clause for an item that doesn't have an existing one to use + /// the span from, compute the appropriate insertion point and call `new_synthetic` instead. + /// + /// If generating a non-synthetic `WhereClause` with no insertion point and no predicates, use + /// `WhereClause::default()`. + pub fn new_with_where(span: Span, predicates: ThinVec) -> Self { + assert!(!span.is_empty()); + WhereClause(Some(Box::new(WhereClauseData { predicates, span }))) + } + + /// Create a new synthetic `WhereClause` that has a maybe-empty insertion span + /// + /// Unlike `new_with_where`, this allows an empty span that serves as an insertion point, and + /// preserves it along with the predicates even if the predicates are empty. + pub fn new_synthetic(span: Span, predicates: ThinVec) -> Self { + WhereClause(Some(Box::new(WhereClauseData { predicates, span }))) + } + + pub fn span(&self) -> Option { + self.0.as_ref().map(|d| d.span) + } + + pub fn has_where_token(&self) -> bool { + // This handles the case from `new_synthetic` + self.0.as_ref().is_some_and(|d| !d.span.is_empty() || !d.predicates.is_empty()) + } + + pub fn predicates(&self) -> &[WherePredicate] { + match self.0 { + None => &[], + Some(ref d) => &d.predicates, + } + } + + /// Merge another where clause into this one, to provide better spans and recovery. + /// + /// `prefer_first_span` determines which span to use if both where clauses exist. + pub fn merge_from(self: &mut WhereClause, w2: &WhereClause, prefer_first_span: bool) { + let Some(ref d2) = w2.0 else { return }; + let Some(ref mut d) = self.0 else { + self.0 = Some(d2.clone()); + return; + }; + d.predicates.extend_from_slice(&d2.predicates); + if !prefer_first_span { + d.span = d2.span; + } } } @@ -2349,6 +2407,21 @@ impl FnSig { self.header.span().unwrap_or(self.span.shrink_to_lo()) } + /// Return a span for where to insert a `where` clause if there isn't one. + /// + /// This is only the right insertion point if there is not already a `where` clause. + /// + /// `has_body` is `true` if the function has a body, and `false` if the function ends in a `;`, + /// so that this span can go before the `;`. + pub fn where_insert_span(&self, has_body: bool) -> Span { + if has_body { + self.span.shrink_to_hi() + } else { + // Before the `;` + self.span.with_hi(self.span.hi() - BytePos(1)).shrink_to_hi() + } + } + /// The span of the header's safety, or where to insert it if empty. pub fn safety_span(&self) -> Span { match self.header.safety { @@ -3729,6 +3802,11 @@ impl VariantData { VariantData::Tuple(_, id) | VariantData::Unit(id) => Some(id), } } + + /// Does this type of variant require a trailing semicolon? + pub fn requires_semi(&self) -> bool { + !matches!(*self, VariantData::Struct { .. }) + } } /// An item definition. @@ -4456,7 +4534,7 @@ mod size_asserts { static_assert_size!(Expr, 64); static_assert_size!(ExprKind, 32); static_assert_size!(FieldDef, 80); - static_assert_size!(Fn, 192); + static_assert_size!(Fn, 176); static_assert_size!(FnDecl, 24); static_assert_size!(FnHeader, 76); static_assert_size!(FnSig, 96); @@ -4466,10 +4544,10 @@ mod size_asserts { static_assert_size!(GenericArgs, 40); static_assert_size!(GenericBound, 80); static_assert_size!(GenericParam, 80); - static_assert_size!(Generics, 40); - static_assert_size!(Impl, 80); - static_assert_size!(Item, 144); - static_assert_size!(ItemKind, 88); + static_assert_size!(Generics, 24); + static_assert_size!(Impl, 64); + static_assert_size!(Item, 128); + static_assert_size!(ItemKind, 72); static_assert_size!(Lifetime, 16); static_assert_size!(LitKind, 24); static_assert_size!(Local, 96); @@ -4488,5 +4566,6 @@ mod size_asserts { static_assert_size!(TraitImplHeader, 64); static_assert_size!(Ty, 56); static_assert_size!(TyKind, 40); + static_assert_size!(WhereClause, 8); // tidy-alphabetical-end } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 9d4c32825e1e4..720867b6b6a94 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -488,6 +488,7 @@ macro_rules! common_visitor_and_walkers { VisibilityKind, WhereBoundPredicate, WhereClause, + WhereClauseData, WhereEqPredicate, WhereRegionPredicate, YieldKind, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 3cc27be600965..b3ece0cf8217a 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -13,7 +13,7 @@ use rustc_middle::ty::data_structures::IndexMap; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::edit_distance::find_best_match_for_name; -use rustc_span::{DUMMY_SP, DesugaringKind, Ident, Span, Symbol, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Ident, Span, Symbol, kw, sym}; use smallvec::SmallVec; use thin_vec::ThinVec; use tracing::instrument; @@ -41,15 +41,7 @@ fn add_ty_alias_where_clause( after_where_clause: &ast::WhereClause, prefer_first: bool, ) { - generics.where_clause.predicates.extend_from_slice(&after_where_clause.predicates); - - let mut before = (generics.where_clause.has_where_token, generics.where_clause.span); - let mut after = (after_where_clause.has_where_token, after_where_clause.span); - if !prefer_first { - (before, after) = (after, before); - } - (generics.where_clause.has_where_token, generics.where_clause.span) = - if before.0 || !after.0 { before } else { after }; + generics.where_clause.merge_from(after_where_clause, prefer_first); } impl<'hir> ItemLowerer<'_, 'hir> { @@ -276,8 +268,10 @@ impl<'hir> LoweringContext<'_, 'hir> { define_opaque, }) => { let ident = self.lower_ident(*ident); + let where_sp = body.as_ref().map(|b| b.span).unwrap_or(ty.span).shrink_to_hi(); let (generics, (ty, rhs)) = self.lower_generics( generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -305,7 +299,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }), ), ItemKind::Fn(Fn { - sig: FnSig { decl, header, span: fn_sig_span }, + sig: sig @ FnSig { decl, header, span: fn_sig_span }, ident, generics, body, @@ -331,7 +325,8 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let itctx = ImplTraitContext::Universal; - let (generics, decl) = this.lower_generics(generics, itctx, |this| { + let where_sp = sig.where_insert_span(body.is_some()); + let (generics, decl) = this.lower_generics(generics, where_sp, itctx, |this| { this.lower_fn_decl(decl, id, *fn_sig_span, FnDeclKind::Fn, coroutine_kind) }); let sig = hir::FnSig { @@ -371,7 +366,9 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_body(|this| (&[], this.expr(span, hir::ExprKind::InlineAsm(asm)))); hir::ItemKind::GlobalAsm { asm, fake_body } } - ItemKind::TyAlias(TyAlias { ident, generics, after_where_clause, ty, .. }) => { + ItemKind::TyAlias(TyAlias { + ident, generics, after_where_clause, ty, bounds, .. + }) => { // We lower // // type Foo = impl Trait @@ -383,8 +380,13 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let mut generics = generics.clone(); add_ty_alias_where_clause(&mut generics, after_where_clause, true); + let where_sp = bounds + .last() + .map(|b| b.span().shrink_to_hi()) + .unwrap_or(generics.span.shrink_to_hi()); let (generics, ty) = self.lower_generics( &generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -411,6 +413,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, variants) = self.lower_generics( generics, + generics.span.shrink_to_hi(), ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.arena.alloc_from_iter( @@ -422,8 +425,14 @@ impl<'hir> LoweringContext<'_, 'hir> { } ItemKind::Struct(ident, generics, struct_def) => { let ident = self.lower_ident(*ident); + let where_sp = if struct_def.requires_semi() { + span.with_hi(span.hi() - BytePos(1)).shrink_to_hi() + } else { + generics.span.shrink_to_hi() + }; let (generics, struct_def) = self.lower_generics( generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, struct_def), ); @@ -433,6 +442,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, vdata) = self.lower_generics( generics, + generics.span.shrink_to_hi(), ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, vdata), ); @@ -459,8 +469,12 @@ impl<'hir> LoweringContext<'_, 'hir> { // lifetime to be added, but rather a reference to a // parent lifetime. let itctx = ImplTraitContext::Universal; + // We always store a where-clause insertion span for impls. + if ast_generics.where_clause.span().is_none_or(|sp| sp.is_dummy()) { + span_bug!(span, "Impl missing where-clause insertion span"); + } let (generics, (of_trait, lowered_ty)) = - self.lower_generics(ast_generics, itctx, |this| { + self.lower_generics(ast_generics, DUMMY_SP, itctx, |this| { let of_trait = of_trait .as_deref() .map(|of_trait| this.lower_trait_impl_header(of_trait)); @@ -500,8 +514,13 @@ impl<'hir> LoweringContext<'_, 'hir> { let constness = self.lower_constness(attrs, *constness); let impl_restriction = self.lower_impl_restriction(impl_restriction); let ident = self.lower_ident(*ident); + let where_sp = bounds + .last() + .map(|b| b.span().shrink_to_hi()) + .unwrap_or(generics.span.shrink_to_hi()); let (generics, (safety, items, bounds)) = self.lower_generics( generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let bounds = this.lower_param_bounds( @@ -530,8 +549,11 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::TraitAlias(TraitAlias { constness, ident, generics, bounds }) => { let constness = self.lower_constness(attrs, *constness); let ident = self.lower_ident(*ident); + // Right before the semicolon + let where_sp = span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(); let (generics, bounds) = self.lower_generics( generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.lower_param_bounds( @@ -729,14 +751,16 @@ impl<'hir> LoweringContext<'_, 'hir> { let (ident, kind) = match &i.kind { ForeignItemKind::Fn(Fn { sig, ident, generics, define_opaque, .. }) => { let fdec = &sig.decl; + let where_sp = sig.where_insert_span(false); let itctx = ImplTraitContext::Universal; - let (generics, (decl, fn_args)) = self.lower_generics(generics, itctx, |this| { - ( - // Disallow `impl Trait` in foreign items. - this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), - this.lower_fn_params_to_idents(fdec), - ) - }); + let (generics, (decl, fn_args)) = + self.lower_generics(generics, where_sp, itctx, |this| { + ( + // Disallow `impl Trait` in foreign items. + this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), + this.lower_fn_params_to_idents(fdec), + ) + }); // Unmarked safety in unsafe block defaults to unsafe. let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe, attrs); @@ -924,8 +948,10 @@ impl<'hir> LoweringContext<'_, 'hir> { define_opaque, .. }) => { + let where_sp = body.as_ref().map(|b| b.span).unwrap_or(ty.span).shrink_to_hi(); let (generics, kind) = self.lower_generics( generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -962,6 +988,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let (generics, sig) = self.lower_method_sig( generics, sig, + false, i.id, FnDeclKind::Trait, sig.header.coroutine_kind, @@ -1002,6 +1029,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let (generics, sig) = self.lower_method_sig( generics, sig, + true, i.id, FnDeclKind::Trait, sig.header.coroutine_kind, @@ -1025,8 +1053,13 @@ impl<'hir> LoweringContext<'_, 'hir> { }) => { let mut generics = generics.clone(); add_ty_alias_where_clause(&mut generics, after_where_clause, false); + let where_sp = match ty { + None => i.span.with_hi(i.span.hi() - BytePos(1)).shrink_to_hi(), + Some(ty) => ty.span.shrink_to_hi(), + }; let (generics, kind) = self.lower_generics( &generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = ty.as_ref().map(|x| { @@ -1190,6 +1223,7 @@ impl<'hir> LoweringContext<'_, 'hir> { *ident, self.lower_generics( generics, + body.as_ref().map(|b| b.span).unwrap_or(ty.span).shrink_to_hi(), ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -1218,6 +1252,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let (generics, sig) = self.lower_method_sig( generics, sig, + body.is_some(), i.id, if is_in_trait_impl { FnDeclKind::Impl } else { FnDeclKind::Inherent }, sig.header.coroutine_kind, @@ -1230,10 +1265,15 @@ impl<'hir> LoweringContext<'_, 'hir> { AssocItemKind::Type(TyAlias { ident, generics, after_where_clause, ty, .. }) => { let mut generics = generics.clone(); add_ty_alias_where_clause(&mut generics, after_where_clause, false); + let where_sp = ty + .as_ref() + .map(|ty| ty.span.shrink_to_hi()) + .unwrap_or(generics.span.shrink_to_hi()); ( *ident, self.lower_generics( &generics, + where_sp, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -1664,14 +1704,16 @@ impl<'hir> LoweringContext<'_, 'hir> { &mut self, generics: &Generics, sig: &FnSig, + has_body: bool, id: NodeId, kind: FnDeclKind, coroutine_kind: Option, attrs: &[hir::Attribute], ) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) { let header = self.lower_fn_header(sig.header, hir::Safety::Safe, attrs); + let where_sp = sig.where_insert_span(has_body); let itctx = ImplTraitContext::Universal; - let (generics, decl) = self.lower_generics(generics, itctx, |this| { + let (generics, decl) = self.lower_generics(generics, where_sp, itctx, |this| { this.lower_fn_decl(&sig.decl, id, sig.span, kind, coroutine_kind) }); (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span) }) @@ -1847,6 +1889,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_generics( &mut self, generics: &Generics, + where_clause_insert_span: Span, itctx: ImplTraitContext, f: impl FnOnce(&mut Self) -> T, ) -> (&'hir hir::Generics<'hir>, T) { @@ -1872,7 +1915,7 @@ impl<'hir> LoweringContext<'_, 'hir> { PredicateOrigin::GenericParam, ) })); - predicates.extend(generics.where_clause.predicates.iter().map(|predicate| { + predicates.extend(generics.where_clause.predicates().iter().map(|predicate| { self.lower_where_predicate(predicate, &generics.params, &mut dedup_map) })); @@ -1891,8 +1934,11 @@ impl<'hir> LoweringContext<'_, 'hir> { ) })); - let has_where_clause_predicates = !generics.where_clause.predicates.is_empty(); - let where_clause_span = self.lower_span(generics.where_clause.span); + let has_where_clause_predicates = !generics.where_clause.predicates().is_empty(); + let where_clause_span = self.lower_span(match generics.where_clause.span() { + Some(span) if !span.is_dummy() => span, + _ => where_clause_insert_span, + }); let span = self.lower_span(generics.span); let res = f(self); diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index d1368d8f9f633..5791b9fe938db 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -196,26 +196,27 @@ impl<'a> AstValidator<'a> { &mut self, ty_alias: &TyAlias, ) -> Result<(), diagnostics::WhereClauseBeforeTypeAlias> { - if ty_alias.ty.is_none() || !ty_alias.generics.where_clause.has_where_token { + let Some(ref ty) = ty_alias.ty else { return Ok(()); - } - - let span = ty_alias.generics.where_clause.span; + }; + let Some(span) = ty_alias.generics.where_clause.span() else { + return Ok(()); + }; - let sugg = if !ty_alias.generics.where_clause.predicates.is_empty() - || !ty_alias.after_where_clause.has_where_token + let sugg = if !ty_alias.generics.where_clause.predicates().is_empty() + || !ty_alias.after_where_clause.has_where_token() { let mut state = State::new(); - let mut needs_comma = !ty_alias.after_where_clause.predicates.is_empty(); - if !ty_alias.after_where_clause.has_where_token { + let mut needs_comma = !ty_alias.after_where_clause.predicates().is_empty(); + if !ty_alias.after_where_clause.has_where_token() { state.space(); state.word_space("where"); } else if !needs_comma { state.space(); } - for p in &ty_alias.generics.where_clause.predicates { + for p in ty_alias.generics.where_clause.predicates() { if needs_comma { state.word_space(","); } @@ -223,10 +224,11 @@ impl<'a> AstValidator<'a> { state.print_where_predicate(p); } + let right = ty_alias.after_where_clause.span().unwrap_or(ty.span).shrink_to_hi(); diagnostics::WhereClauseBeforeTypeAliasSugg::Move { left: span, snippet: state.s.eof(), - right: ty_alias.after_where_clause.span.shrink_to_hi(), + right, } } else { diagnostics::WhereClauseBeforeTypeAliasSugg::Remove { span } @@ -825,8 +827,8 @@ impl<'a> AstValidator<'a> { } let check_where_clause = |where_clause: &WhereClause| { - if where_clause.has_where_token { - cannot_have(where_clause.span, "`where` clauses", "`where` clause"); + if let Some(span) = where_clause.span() { + cannot_have(span, "`where` clauses", "`where` clause"); } }; @@ -1093,12 +1095,14 @@ impl<'a> AstValidator<'a> { } fn deny_where_clause(&self, where_clause: &WhereClause, ident: Span) { - if !where_clause.predicates.is_empty() { + if !where_clause.predicates().is_empty() + && let Some(span) = where_clause.span() + { // FIXME: The current diagnostic is misleading since it only talks about // super trait and lifetime bounds while we should just say “bounds”. self.dcx().emit_err(diagnostics::AutoTraitBounds { - span: vec![where_clause.span], - removal: where_clause.span, + span: vec![span], + removal: span, ident, }); } @@ -1703,9 +1707,9 @@ impl Visitor<'_> for AstValidator<'_> { if let Err(err) = self.check_type_alias_where_clause_location(ty_alias) { self.dcx().emit_err(err); } - } else if after_where_clause.has_where_token { + } else if let Some(span) = after_where_clause.span() { self.dcx().emit_err(diagnostics::WhereClauseAfterTypeAlias { - span: after_where_clause.span, + span, help: self.sess.is_nightly_build(), }); } @@ -1818,7 +1822,7 @@ impl Visitor<'_> for AstValidator<'_> { validate_generic_param_order(self.dcx(), &generics.params, generics.span); walk_list!(self, visit_generic_param, &generics.params); - for predicate in &generics.where_clause.predicates { + for predicate in generics.where_clause.predicates() { match &predicate.kind { WherePredicateKind::BoundPredicate(bound_pred) => { // This is slightly complicated. Our representation for poly-trait-refs contains a single diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 04f78ea7f467a..ca1faee25e99d 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -834,15 +834,14 @@ impl<'a> State<'a> { } fn print_where_clause(&mut self, where_clause: &ast::WhereClause) { - let ast::WhereClause { has_where_token, ref predicates, span: _ } = *where_clause; - if predicates.is_empty() && !has_where_token { + if !where_clause.has_where_token() { return; } self.space(); self.word_space("where"); - for (i, predicate) in predicates.iter().enumerate() { + for (i, predicate) in where_clause.predicates().iter().enumerate() { if i != 0 { self.word_space(","); } diff --git a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs index 80296a43ee490..3aa817cc2ae47 100644 --- a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs +++ b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs @@ -3,13 +3,13 @@ use rustc_ast::mut_visit::MutVisitor; use rustc_ast::visit::BoundKind; use rustc_ast::{ self as ast, GenericArg, GenericBound, GenericParamKind, Generics, ItemKind, MetaItem, - TraitBoundModifiers, VariantData, WherePredicate, + TraitBoundModifiers, VariantData, WhereClause, }; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_errors::E0802; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_macros::Diagnostic; -use rustc_span::{Ident, Span, Symbol, sym}; +use rustc_span::{BytePos, Ident, Span, Symbol, sym}; use thin_vec::{ThinVec, thin_vec}; use crate::diagnostics; @@ -28,7 +28,7 @@ pub(crate) fn expand_deriving_coerce_pointee( ) { item.visit_with(&mut DetectNonGenericPointeeAttr { cx }); - let (name_ident, generics) = if let Annotatable::Item(aitem) = item + let (name_ident, generics, where_insert_sp) = if let Annotatable::Item(aitem) = item && let ItemKind::Struct(ident, g, struct_data) = &aitem.kind { if !matches!( @@ -39,7 +39,14 @@ pub(crate) fn expand_deriving_coerce_pointee( cx.dcx().emit_err(RequireOneField { span }); return; } - (*ident, g) + let where_insert_sp = if let Some(sp) = g.where_clause.span() { + sp + } else if struct_data.requires_semi() { + aitem.span.with_hi(aitem.span.hi() - BytePos(1)).shrink_to_hi() + } else { + g.span.shrink_to_hi() + }; + (*ident, g, where_insert_sp) } else { cx.dcx().emit_err(RequireTransparent { span }); return; @@ -104,6 +111,11 @@ pub(crate) fn expand_deriving_coerce_pointee( let trait_path = cx.path_all(span, true, path!(span, core::marker::CoercePointeeValidated), vec![]); let trait_ref = cx.trait_ref(trait_path); + let where_clause = if generics.where_clause.span().is_some() { + generics.where_clause.clone() + } else { + ast::WhereClause::new_synthetic(where_insert_sp, ThinVec::new()) + }; push(Annotatable::Item( cx.item( span, @@ -130,7 +142,7 @@ pub(crate) fn expand_deriving_coerce_pointee( ), }) .collect(), - where_clause: generics.where_clause.clone(), + where_clause, span: generics.span, }, of_trait: Some(Box::new(ast::TraitImplHeader { @@ -180,17 +192,14 @@ pub(crate) fn expand_deriving_coerce_pointee( // # Add `Unsize<__S>` bound to `#[pointee]` at the generic parameter location // // Find the `#[pointee]` parameter and add an `Unsize<__S>` bound to it. - let mut impl_generics = generics.clone(); + let mut impl_generics_params = generics.params.clone(); let pointee_ty_ident = generics.params[pointee_param_idx].ident; let mut self_bounds; { - let pointee = &mut impl_generics.params[pointee_param_idx]; + let pointee = &mut impl_generics_params[pointee_param_idx]; self_bounds = pointee.bounds.clone(); if !contains_maybe_sized_bound(&self_bounds) - && !contains_maybe_sized_bound_on_pointee( - &generics.where_clause.predicates, - pointee_ty_ident.name, - ) + && !contains_maybe_sized_bound_on_pointee(&generics.where_clause, pointee_ty_ident.name) { cx.dcx().emit_err(RequiresMaybeSized { span: pointee_ty_ident.span, @@ -225,7 +234,7 @@ pub(crate) fn expand_deriving_coerce_pointee( // The new bound marked with (*) has to be done separately. // See next section for (idx, (params, orig_params)) in - impl_generics.params.iter_mut().zip(&generics.params).enumerate() + impl_generics_params.iter_mut().zip(&generics.params).enumerate() { // Default type parameters are rejected for `impl` block. // We should drop them now. @@ -294,7 +303,9 @@ pub(crate) fn expand_deriving_coerce_pointee( // // We should also write a few new `where` bounds from `#[pointee] T` to `__S` // as well as any bound that indirectly involves the `#[pointee] T` type. - for predicate in &generics.where_clause.predicates { + let mut predicates = ThinVec::new(); + for predicate in generics.where_clause.predicates() { + predicates.push(predicate.clone()); if let ast::WherePredicateKind::BoundPredicate(bound) = &predicate.kind { let mut substitution = TypeSubstitution { from_name: pointee_ty_ident.name, @@ -311,22 +322,26 @@ pub(crate) fn expand_deriving_coerce_pointee( id: ast::DUMMY_NODE_ID, is_placeholder: false, }; - impl_generics.where_clause.predicates.push(predicate); + predicates.push(predicate); } } } let extra_param = cx.typaram(span, Ident::new(sym::__S, span), self_bounds, None); - impl_generics.params.insert(pointee_param_idx + 1, extra_param); + impl_generics_params.insert(pointee_param_idx + 1, extra_param); + + let where_clause = ast::WhereClause::new_synthetic(where_insert_sp, predicates); + let impl_generics = + Generics { params: impl_generics_params, where_clause, span: generics.span }; // Add the impl blocks for `DispatchFromDyn` and `CoerceUnsized`. let gen_args = vec![GenericArg::Type(alt_self_type)]; add_impl_block(impl_generics.clone(), sym::DispatchFromDyn, gen_args.clone()); - add_impl_block(impl_generics.clone(), sym::CoerceUnsized, gen_args); + add_impl_block(impl_generics, sym::CoerceUnsized, gen_args); } -fn contains_maybe_sized_bound_on_pointee(predicates: &[WherePredicate], pointee: Symbol) -> bool { - for bound in predicates { +fn contains_maybe_sized_bound_on_pointee(where_clause: &WhereClause, pointee: Symbol) -> bool { + for bound in where_clause.predicates() { if let ast::WherePredicateKind::BoundPredicate(bound) = &bound.kind && bound.bounded_ty.kind.is_simple_path().is_some_and(|name| name == pointee) { diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 03ccdbb902a96..1759e6343a011 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -190,7 +190,7 @@ use rustc_attr_parsing::AttributeParser; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_hir::Attribute; use rustc_hir::attrs::{AttributeKind, ReprPacked}; -use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use ty::{Bounds, Path, Ref, Self_, Ty}; @@ -498,14 +498,22 @@ impl<'a> TraitDef<'a> { ); let newitem = match &item.kind { - ast::ItemKind::Struct(ident, generics, struct_def) => self.expand_struct_def( - cx, - struct_def, - *ident, - generics, - from_scratch, - is_packed, - ), + ast::ItemKind::Struct(ident, generics, struct_def) => { + let where_sp = if struct_def.requires_semi() { + item.span.with_hi(item.span.hi() - BytePos(1)).shrink_to_hi() + } else { + generics.span.shrink_to_hi() + }; + self.expand_struct_def( + cx, + struct_def, + *ident, + generics, + where_sp, + from_scratch, + is_packed, + ) + } ast::ItemKind::Enum(ident, generics, enum_def) => { // We ignore `is_packed` here, because `repr(packed)` // enums cause an error later on. @@ -521,6 +529,7 @@ impl<'a> TraitDef<'a> { struct_def, *ident, generics, + generics.span.shrink_to_hi(), from_scratch, is_packed, ) @@ -595,6 +604,7 @@ impl<'a> TraitDef<'a> { cx: &ExtCtxt<'_>, type_ident: Ident, generics: &Generics, + where_insert_span: Span, field_tys: Vec<&ast::Ty>, methods: Vec>, is_packed: bool, @@ -623,8 +633,6 @@ impl<'a> TraitDef<'a> { }) }); - let mut where_clause = ast::WhereClause::default(); - where_clause.span = generics.where_clause.span; let ctxt = self.span.ctxt(); let span = generics.span.with_ctxt(ctxt); @@ -694,15 +702,18 @@ impl<'a> TraitDef<'a> { .collect(); // and similarly for where clauses - where_clause.predicates.extend(generics.where_clause.predicates.iter().map(|clause| { - ast::WherePredicate { + let mut where_clause_predicates: ThinVec<_> = generics + .where_clause + .predicates() + .iter() + .map(|clause| ast::WherePredicate { attrs: clause.attrs.clone(), kind: clause.kind.clone(), id: ast::DUMMY_NODE_ID, span: clause.span.with_ctxt(ctxt), is_placeholder: false, - } - })); + }) + .collect(); let ty_param_names: Vec = params .iter() @@ -762,12 +773,16 @@ impl<'a> TraitDef<'a> { span: self.span, is_placeholder: false, }; - where_clause.predicates.push(predicate); + where_clause_predicates.push(predicate); } } } } + let where_clause = ast::WhereClause::new_synthetic( + generics.where_clause.span().unwrap_or(where_insert_span), + where_clause_predicates, + ); let trait_generics = Generics { params, where_clause, span }; // Create the reference to the trait. @@ -860,6 +875,7 @@ impl<'a> TraitDef<'a> { struct_def: &'a VariantData, type_ident: Ident, generics: &Generics, + where_sp: Span, from_scratch: bool, is_packed: bool, ) -> Box { @@ -904,7 +920,7 @@ impl<'a> TraitDef<'a> { }) .collect(); - self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed) + self.create_derived_impl(cx, type_ident, generics, where_sp, field_tys, methods, is_packed) } fn expand_enum_def( @@ -962,7 +978,8 @@ impl<'a> TraitDef<'a> { .collect(); let is_packed = false; // enums are never packed - self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed) + let where_sp = generics.span.shrink_to_hi(); + self.create_derived_impl(cx, type_ident, generics, where_sp, field_tys, methods, is_packed) } } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs index e7972c5436e13..f3095b86d7cf2 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -185,15 +185,7 @@ impl Bounds { .map(|&(name, ref bounds)| mk_ty_param(cx, span, name, bounds, self_ty, self_generics)) .collect(); - Generics { - params, - where_clause: ast::WhereClause { - has_where_token: false, - predicates: ThinVec::new(), - span, - }, - span, - } + Generics { params, where_clause: ast::WhereClause::default(), span } } } diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 1523aba4be9ab..51a36da98f8ed 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -148,7 +148,7 @@ impl<'a> Parser<'a> { // `fn_params_end` is needed only when it's followed by a where clause. let fn_params_end = - if generics.where_clause.has_where_token { Some(fn_params_end) } else { None }; + if generics.where_clause.has_where_token() { Some(fn_params_end) } else { None }; let mut sig_hi = self.prev_token.span; // Either `;` or `{ ... }`. diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index ec9860c0f35bf..0ba2c5b66e84d 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -332,15 +332,7 @@ impl<'a> Parser<'a> { } else { (ThinVec::new(), self.prev_token.span.shrink_to_hi()) }; - Ok(ast::Generics { - params, - where_clause: WhereClause { - has_where_token: false, - predicates: ThinVec::new(), - span: self.prev_token.span.shrink_to_hi(), - }, - span, - }) + Ok(ast::Generics { params, where_clause: WhereClause::default(), span }) } /// Parses an experimental fn contract @@ -411,15 +403,10 @@ impl<'a> Parser<'a> { &mut self, struct_: Option<(Ident, Span)>, ) -> PResult<'a, (WhereClause, Option>)> { - let mut where_clause = WhereClause { - has_where_token: false, - predicates: ThinVec::new(), - span: self.prev_token.span.shrink_to_hi(), - }; let mut tuple_struct_body = None; if !self.eat_keyword(exp!(Where)) { - return Ok((where_clause, None)); + return Ok((WhereClause::default(), None)); } if self.eat_noexpect(&token::Colon) { @@ -435,7 +422,6 @@ impl<'a> Parser<'a> { .emit(); } - where_clause.has_where_token = true; let where_lo = self.prev_token.span; // We are considering adding generics to the `where` keyword as an alternative higher-rank @@ -446,6 +432,7 @@ impl<'a> Parser<'a> { self.dcx().emit_err(diagnostics::WhereOnGenerics { span: generics.span }); } + let mut predicates = ThinVec::new(); loop { let where_sp = where_lo.to(self.prev_token.span); let attrs = self.parse_outer_attributes()?; @@ -498,7 +485,7 @@ impl<'a> Parser<'a> { Ok((predicate, Trailing::No, UsePreAttrPos::No)) })?; match predicate { - Some(predicate) => where_clause.predicates.push(predicate), + Some(predicate) => predicates.push(predicate), None => break, } @@ -516,8 +503,8 @@ impl<'a> Parser<'a> { } } - where_clause.span = where_lo.to(self.prev_token.span); - Ok((where_clause, tuple_struct_body)) + let where_clause_span = where_lo.to(self.prev_token.span); + Ok((WhereClause::new_with_where(where_clause_span, predicates), tuple_struct_body)) } fn parse_ty_where_predicate_kind_or_recover_tuple_struct_body( diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 1306f1fcfb1ce..45c8e83c25485 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -750,7 +750,11 @@ impl<'a> Parser<'a> { None }; + let where_sp = self.prev_token.span.shrink_to_hi(); generics.where_clause = self.parse_where_clause()?; + if generics.where_clause.span().is_none() { + generics.where_clause = WhereClause::new_synthetic(where_sp, ThinVec::new()); + } let impl_items = if is_reuse { Default::default() @@ -1711,19 +1715,19 @@ impl<'a> Parser<'a> { // Provide a nice error message if the user placed a where-clause before the item body. // Users may be tempted to write such code if they are still used to the deprecated // where-clause location on type aliases and associated types. See also #89122. - if before_where_clause.has_where_token + if let Some(span) = before_where_clause.span() && let Some(rhs) = &rhs { self.dcx().emit_err(diagnostics::WhereClauseBeforeConstBody { - span: before_where_clause.span, + span, name: ident.span, body: rhs.span, - sugg: if !after_where_clause.has_where_token { + sugg: if !after_where_clause.has_where_token() { self.psess.source_map().span_to_snippet(rhs.span).ok().map(|body_s| { diagnostics::WhereClauseBeforeConstBodySugg { - left: before_where_clause.span.shrink_to_lo(), + left: span.shrink_to_lo(), snippet: body_s, - right: before_where_clause.span.shrink_to_hi().to(rhs.span), + right: span.shrink_to_hi().to(rhs.span), } }) } else { @@ -1740,21 +1744,10 @@ impl<'a> Parser<'a> { // in `after_where_clause`. Further, both of them might contain predicates iff two // where-clauses were provided which is syntactically ill-formed but we want to recover from // it and treat them as one large where-clause. - let mut predicates = before_where_clause.predicates; - predicates.extend(after_where_clause.predicates); - let where_clause = WhereClause { - has_where_token: before_where_clause.has_where_token - || after_where_clause.has_where_token, - predicates, - span: if after_where_clause.has_where_token { - after_where_clause.span - } else { - before_where_clause.span - }, - }; - - if where_clause.has_where_token { - self.psess.gated_spans.gate(sym::generic_const_items, where_clause.span); + let mut where_clause = before_where_clause; + where_clause.merge_from(&after_where_clause, false); + if let Some(span) = where_clause.span() { + self.psess.gated_spans.gate(sym::generic_const_items, span); } generics.where_clause = where_clause; @@ -1984,7 +1977,7 @@ impl<'a> Parser<'a> { let (fields, recovered) = self.parse_record_struct_body( "struct", ident.span, - generics.where_clause.has_where_token, + generics.where_clause.has_where_token(), )?; VariantData::Struct { fields, recovered } } @@ -1996,7 +1989,7 @@ impl<'a> Parser<'a> { let (fields, recovered) = self.parse_record_struct_body( "struct", ident.span, - generics.where_clause.has_where_token, + generics.where_clause.has_where_token(), )?; VariantData::Struct { fields, recovered } // Tuple-style struct definition with optional where-clause. @@ -2024,14 +2017,14 @@ impl<'a> Parser<'a> { let (fields, recovered) = self.parse_record_struct_body( "union", ident.span, - generics.where_clause.has_where_token, + generics.where_clause.has_where_token(), )?; VariantData::Struct { fields, recovered } } else if self.token == token::OpenBrace { let (fields, recovered) = self.parse_record_struct_body( "union", ident.span, - generics.where_clause.has_where_token, + generics.where_clause.has_where_token(), )?; VariantData::Struct { fields, recovered } } else { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index fc723586c1acc..9cd210b242f37 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -1284,7 +1284,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc fn visit_generics(&mut self, generics: &'ast Generics) { self.visit_generic_params(&generics.params, self.diag_metadata.current_self_item.is_some()); - for p in &generics.where_clause.predicates { + for p in generics.where_clause.predicates() { self.visit_where_predicate(p); } } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index b126272583692..1417fe55004ad 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -282,7 +282,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } - for predicate in &generics.where_clause.predicates { + for predicate in generics.where_clause.predicates() { let ast::WherePredicateKind::BoundPredicate(where_bound) = &predicate.kind else { continue; }; diff --git a/tests/ui/const-generics/issues/issue-71202.stderr b/tests/ui/const-generics/issues/issue-71202.stderr index dd0611a7223cb..6f56e002ad0b9 100644 --- a/tests/ui/const-generics/issues/issue-71202.stderr +++ b/tests/ui/const-generics/issues/issue-71202.stderr @@ -11,7 +11,7 @@ LL | | } as usize] = []; | help: try adding a `where` bound | -LL ~ } as usize] where [(); 1 - { +LL ~ } as usize] = [] where [(); 1 - { LL + trait NotCopy { LL + const VALUE: bool = false; LL + } @@ -28,7 +28,7 @@ LL + const VALUE: bool = true; LL + } LL + LL + >::VALUE -LL ~ } as usize]: = []; +LL ~ } as usize]:; | error: unconstrained generic constant @@ -39,7 +39,7 @@ LL | } as usize] = []; | help: try adding a `where` bound | -LL ~ } as usize] where [(); 1 - { +LL ~ } as usize] = [] where [(); 1 - { LL + trait NotCopy { LL + const VALUE: bool = false; LL + } @@ -56,7 +56,7 @@ LL + const VALUE: bool = true; LL + } LL + LL + >::VALUE -LL ~ } as usize]: = []; +LL ~ } as usize]:; | error[E0308]: mismatched types diff --git a/tests/ui/stats/input-stats.stderr b/tests/ui/stats/input-stats.stderr index 420b01102e41e..52310edd8e455 100644 --- a/tests/ui/stats/input-stats.stderr +++ b/tests/ui/stats/input-stats.stderr @@ -2,14 +2,14 @@ ast-stats ================================================================ ast-stats POST EXPANSION AST STATS: input_stats ast-stats Name Accumulated Size Count Item Size ast-stats ---------------------------------------------------------------- -ast-stats Item 1_584 (NN.N%) 11 144 -ast-stats - Enum 144 (NN.N%) 1 -ast-stats - ExternCrate 144 (NN.N%) 1 -ast-stats - ForeignMod 144 (NN.N%) 1 -ast-stats - Impl 144 (NN.N%) 1 -ast-stats - Trait 144 (NN.N%) 1 -ast-stats - Fn 288 (NN.N%) 2 -ast-stats - Use 576 (NN.N%) 4 +ast-stats Item 1_408 (NN.N%) 11 128 +ast-stats - Enum 128 (NN.N%) 1 +ast-stats - ExternCrate 128 (NN.N%) 1 +ast-stats - ForeignMod 128 (NN.N%) 1 +ast-stats - Impl 128 (NN.N%) 1 +ast-stats - Trait 128 (NN.N%) 1 +ast-stats - Fn 256 (NN.N%) 2 +ast-stats - Use 512 (NN.N%) 4 ast-stats PathSegment 840 (NN.N%) 35 24 ast-stats Ty 784 (NN.N%) 14 56 ast-stats - Ptr 56 (NN.N%) 1 @@ -58,7 +58,7 @@ ast-stats GenericArgs 40 (NN.N%) 1 40 ast-stats - AngleBracketed 40 (NN.N%) 1 ast-stats Crate 40 (NN.N%) 1 40 ast-stats ---------------------------------------------------------------- -ast-stats Total 6_872 126 +ast-stats Total 6_696 126 ast-stats ================================================================ hir-stats ================================================================ hir-stats HIR STATS: input_stats From 85e0bc998f0a22f82c2b9892e2fa452d6a8ae67d Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 6 Aug 2026 19:30:40 -0700 Subject: [PATCH 2/3] Fix up clippy for optional `WhereClause`s --- src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs | 7 ++++--- .../clippy/clippy_lints/src/multiple_bound_locations.rs | 4 ++-- src/tools/clippy/clippy_utils/src/ast_utils/mod.rs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs b/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs index 87e63e8732fe8..b3d5a3354ccef 100644 --- a/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs +++ b/src/tools/clippy/clippy_lints/src/inline_trait_bounds.rs @@ -116,13 +116,14 @@ fn lint_fn(cx: &EarlyContext<'_>, f: &Fn) { let predicate_text = predicates.join(", "); let where_clause = &generics.where_clause; - if where_clause.has_where_token { - let (insert_at, suffix) = if let Some(last_pred) = where_clause.predicates.last() { + if where_clause.has_where_token() { + let (insert_at, suffix) = if let Some(last_pred) = where_clause.predicates().last() { // existing `where` with predicates: append after last predicate (last_pred.span.shrink_to_hi(), format!(", {predicate_text}")) } else { // `where` token present but empty predicate list - (where_clause.span.shrink_to_hi(), format!(" {predicate_text}")) + let where_clause_span = where_clause.span().expect("`where` but no span"); + (where_clause_span.shrink_to_hi(), format!(" {predicate_text}")) }; edits.push((insert_at, suffix)); diff --git a/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs b/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs index a78f53188ba8b..ad3ac79f0b6b9 100644 --- a/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs +++ b/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs @@ -41,7 +41,7 @@ impl EarlyLintPass for MultipleBoundLocations { fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) { if let FnKind::Fn(_, _, Fn { generics, .. }) = kind && !generics.params.is_empty() - && !generics.where_clause.predicates.is_empty() + && !generics.where_clause.predicates().is_empty() { let mut generic_params_with_bounds = FxHashMap::default(); @@ -50,7 +50,7 @@ impl EarlyLintPass for MultipleBoundLocations { generic_params_with_bounds.insert(param.ident.as_str(), param.ident.span); } } - for clause in &generics.where_clause.predicates { + for clause in generics.where_clause.predicates() { match &clause.kind { WherePredicateKind::BoundPredicate(pred) => { if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty()) diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 523e799ef2522..e7baff3cee5ef 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -601,7 +601,7 @@ fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { eq_defaultness(*ld, *rd) && eq_id(*li, *ri) && eq_generics(lg, rg) - && over(&lw.predicates, &rw.predicates, eq_where_predicate) + && over(lw.predicates(), rw.predicates(), eq_where_predicate) && over(lb, rb, eq_generic_bound) && both(lt.as_ref(), rt.as_ref(), |l, r| eq_ty(l, r)) }, @@ -690,7 +690,7 @@ fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { eq_defaultness(*ld, *rd) && eq_id(*li, *ri) && eq_generics(lg, rg) - && over(&lw.predicates, &rw.predicates, eq_where_predicate) + && over(lw.predicates(), rw.predicates(), eq_where_predicate) && over(lb, rb, eq_generic_bound) && both(lt.as_ref(), rt.as_ref(), |l, r| eq_ty(l, r)) }, @@ -768,7 +768,7 @@ fn eq_opt_fn_contract(l: &Option>, r: &Option>) fn eq_generics(l: &Generics, r: &Generics) -> bool { over(&l.params, &r.params, eq_generic_param) - && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| { + && over(l.where_clause.predicates(), r.where_clause.predicates(), |l, r| { eq_where_predicate(l, r) }) } From c02eb7725019fa53093c2350caaebb6d3ccdbf80 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 7 Aug 2026 03:33:21 -0700 Subject: [PATCH 3/3] Fix up rustfmt for optional `WhereClause`s --- src/tools/rustfmt/src/items.rs | 92 +++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 5619d948fde97..bc246d8f59544 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -814,7 +814,7 @@ pub(crate) fn format_impl( let open_pos = snippet.find_uncommented("{").unknown_error()? + 1; if !contains_comment(&snippet[open_pos..]) && items.is_empty() - && generics.where_clause.predicates.len() == 1 + && generics.where_clause.predicates().len() == 1 && !result.contains('\n') { option.suppress_comma(); @@ -838,7 +838,7 @@ pub(crate) fn format_impl( // If there is no where-clause, we may have missing comments between the trait name and // the opening brace. - if generics.where_clause.predicates.is_empty() { + if generics.where_clause.predicates().is_empty() { if let Some(hi) = where_span_end { match recover_missing_comment_in_span( mk_sp(self_ty.span.hi(), hi), @@ -860,7 +860,7 @@ pub(crate) fn format_impl( // If there is only one where-clause predicate // and the where-clause spans multiple lines, // then recover the suppressed comma in single line where-clause formatting - if generics.where_clause.predicates.len() == 1 { + if generics.where_clause.predicates().len() == 1 { result.push(','); } } @@ -890,7 +890,11 @@ pub(crate) fn format_impl( result.push('{'); // this is an impl body snippet(impl SampleImpl { /* here */ }) - let lo = max(self_ty.span.hi(), generics.where_clause.span.hi()); + let lo = self_ty.span.hi(); + let lo = max( + lo, + generics.where_clause.span().map(|sp| sp.hi()).unwrap_or(lo), + ); let snippet = context.snippet(mk_sp(lo, item.span.hi())); let open_pos = snippet.find_uncommented("{").unknown_error()? + 1; @@ -995,7 +999,7 @@ fn format_impl_ref_and_type( } // Try to put the self type in a single line. - let curly_brace_overhead = if generics.where_clause.predicates.is_empty() { + let curly_brace_overhead = if generics.where_clause.predicates().is_empty() { // If there is no where-clause adapt budget for type formatting to take space and curly // brace into account. match context.config.brace_style() { @@ -1200,12 +1204,16 @@ pub(crate) fn format_trait( } // Rewrite where-clause. - if !generics.where_clause.predicates.is_empty() { + if !generics.where_clause.predicates().is_empty() { let where_on_new_line = context.config.indent_style() != IndentStyle::Block; let where_budget = context.budget(last_line_width(&result)); let pos_before_where = if bounds.is_empty() { - generics.where_clause.span.lo() + generics + .where_clause + .span() + .expect("where clause with predicates but no span") + .lo() } else { bounds[bounds.len() - 1].span().hi() }; @@ -1259,7 +1267,13 @@ pub(crate) fn format_trait( } } - let block_span = mk_sp(generics.where_clause.span.hi(), item.span.hi()); + let block_lo = generics + .where_clause + .span() + .or_else(|| bounds.last().map(|b| b.span())) + .unwrap_or(generics.span) + .hi(); + let block_span = mk_sp(block_lo, item.span.hi()); let snippet = context.snippet(block_span); let open_pos = snippet.find_uncommented("{").unknown_error()? + 1; @@ -1283,7 +1297,7 @@ pub(crate) fn format_trait( BraceStyle::PreferSameLine => result.push(' '), BraceStyle::SameLineWhere => { if result.contains('\n') - || (!generics.where_clause.predicates.is_empty() && !items.is_empty()) + || (!generics.where_clause.predicates().is_empty() && !items.is_empty()) { result.push_str(&offset.to_string_with_newline(context.config)); } else { @@ -1335,6 +1349,13 @@ impl<'a> Rewrite for TraitAliasBounds<'a> { let mut option = WhereClauseOption::new(true, WhereClauseSpace::None); option.allow_single_line(); + let hi = self + .generics + .where_clause + .span() + .map(|sp| sp.lo()) + .or_else(|| self.generic_bounds.last().map(|b| b.span().hi())) + .unwrap_or(self.generics.span.hi()); let where_str = rewrite_where_clause( context, &self.generics.where_clause, @@ -1343,7 +1364,7 @@ impl<'a> Rewrite for TraitAliasBounds<'a> { false, ";", None, - self.generics.where_clause.span.lo(), + hi, option, )?; @@ -1429,9 +1450,12 @@ pub(crate) fn format_struct_struct( result.push_str(&header_str); let header_hi = struct_parts.ident.span.hi(); - let body_lo = if let Some(generics) = struct_parts.generics { + let where_sp_hi = struct_parts + .generics + .map(|g| g.where_clause.span().unwrap_or(g.span).hi()); + let body_lo = if let Some(where_sp_hi) = where_sp_hi { // Adjust the span to start at the end of the generic arguments before searching for the '{' - let span = span.with_lo(generics.where_clause.span.hi()); + let span = span.with_lo(where_sp_hi); context.snippet_provider.span_after(span, "{") } else { context.snippet_provider.span_after(span, "{") @@ -1701,7 +1725,11 @@ pub(crate) fn rewrite_type_alias<'a>( let ty_opt = ty.as_ref(); let rhs_hi = ty .as_ref() - .map_or(generics.where_clause.span.hi(), |ty| ty.span.hi()); + .map(|ty| ty.span) + .or_else(|| generics.where_clause.span()) + .or_else(|| bounds.last().map(|b| b.span())) + .unwrap_or(generics.span) + .hi(); let rw_info = &TyAliasRewriteInfo(context, indent, generics, after_where_clause, ident, span); let op_ty = opaque_ty(ty); // Type Aliases are formatted slightly differently depending on the context @@ -1796,9 +1824,9 @@ fn rewrite_ty( // If there are any where clauses, add a newline before the assignment. // If there is a before where clause, do not indent, but if there is // only an after where clause, additionally indent the type. - if !generics.where_clause.predicates.is_empty() { + if !generics.where_clause.predicates().is_empty() { result.push_str(&indent.to_string_with_newline(context.config)); - } else if !after_where_clause.predicates.is_empty() { + } else if !after_where_clause.predicates().is_empty() { result.push_str( &indent .block_indent(context.config) @@ -1808,10 +1836,16 @@ fn rewrite_ty( result.push(' '); } + let comment_lo = generics + .where_clause + .span() + .or_else(|| generic_bounds_opt.and_then(|bounds| bounds.last().map(|b| b.span()))) + .unwrap_or(generics.span) + .hi(); let comment_span = context .snippet_provider .opt_span_before(span, "=") - .map(|op_lo| mk_sp(generics.where_clause.span.hi(), op_lo)); + .map(|op_lo| mk_sp(comment_lo, op_lo)); let lhs = match comment_span { Some(comment_span) @@ -1822,7 +1856,7 @@ fn rewrite_ty( .unknown_error()?, ) => { - let comment_shape = if !generics.where_clause.predicates.is_empty() { + let comment_shape = if !generics.where_clause.predicates().is_empty() { Shape::indented(indent, context.config) } else { let shape = Shape::indented(indent, context.config); @@ -1843,7 +1877,7 @@ fn rewrite_ty( // 1 = `;` unless there's a trailing where clause let shape = Shape::indented(indent, context.config); - let shape = if after_where_clause.predicates.is_empty() { + let shape = if after_where_clause.predicates().is_empty() { Shape::indented(indent, context.config).sub_width(1, span)? } else { shape @@ -1853,7 +1887,7 @@ fn rewrite_ty( result }; - if !after_where_clause.predicates.is_empty() { + if !after_where_clause.predicates().is_empty() { let option = WhereClauseOption::new(true, WhereClauseSpace::Newline); let after_where_clause_str = rewrite_where_clause( context, @@ -2109,7 +2143,7 @@ fn rewrite_static( // For now, if this static (or const) has generics, then bail. if static_parts .generics - .is_some_and(|g| !g.params.is_empty() || !g.where_clause.is_empty()) + .is_some_and(|g| !g.params.is_empty() || g.where_clause.has_where_token()) { return None; } @@ -2619,7 +2653,7 @@ fn rewrite_fn_base( // If there is no where-clause, take into account the space after the return type // and the brace. - if where_clause.predicates.is_empty() { + if where_clause.predicates().is_empty() { sig_length += 2; } @@ -2690,7 +2724,7 @@ fn rewrite_fn_base( // Comment between return type and the end of the decl. let snippet_lo = fd.output.span().hi(); - if where_clause.predicates.is_empty() { + if where_clause.predicates().is_empty() { let snippet_hi = span.hi(); let snippet = context.snippet(mk_sp(snippet_lo, snippet_hi)); // Try to preserve the layout of the original snippet. @@ -2961,7 +2995,7 @@ fn compute_budgets_for_params( } fn newline_for_brace(config: &Config, where_clause: &ast::WhereClause) -> FnBraceStyle { - let predicate_count = where_clause.predicates.len(); + let predicate_count = where_clause.predicates().len(); if config.where_single_line() && predicate_count == 1 { return FnBraceStyle::SameLine; @@ -3174,11 +3208,7 @@ fn rewrite_where_clause( span_end_before_where: BytePos, where_clause_option: WhereClauseOption, ) -> RewriteResult { - let ast::WhereClause { - ref predicates, - span: where_span, - has_where_token: _, - } = *where_clause; + let predicates = where_clause.predicates(); if predicates.is_empty() { return Ok(String::new()); @@ -3188,7 +3218,9 @@ fn rewrite_where_clause( return rewrite_where_clause_rfc_style( context, predicates, - where_span, + where_clause + .span() + .expect("where clause with predicates but no span"), shape, terminator, span_end, @@ -3358,7 +3390,7 @@ fn format_generics( } else { span.lo() }; - let (same_line_brace, missed_comments) = if !generics.where_clause.predicates.is_empty() { + let (same_line_brace, missed_comments) = if !generics.where_clause.predicates().is_empty() { let budget = context.budget(last_line_used_width(&result, offset.width())); let mut option = WhereClauseOption::snuggled(&result); if brace_pos == BracePos::None {