Skip to content

Commit 09239c2

Browse files
committed
Automatically skip traversal of boring fields
A trait, `rustc_type_ir::SkipTraversalAutoImplOnly`, is auto-implemented for types that do not contain anything that may be of interest to traversers (folders and visitors). "Auto-deref specialisation" is then used in the derive macros to avoid traversing fields of such types without requiring them to implement the traversable traits.
1 parent 1a9c3e5 commit 09239c2

File tree

10 files changed

+343
-109
lines changed

10 files changed

+343
-109
lines changed

compiler/rustc_macros/src/lib.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,26 @@ decl_derive!([TyEncodable] => serialize::type_encodable_derive);
120120
decl_derive!([MetadataDecodable] => serialize::meta_decodable_derive);
121121
decl_derive!([MetadataEncodable] => serialize::meta_encodable_derive);
122122
decl_derive!(
123-
[TypeFoldable, attributes(type_foldable)] =>
123+
[TypeFoldable, attributes(skip_traversal)] =>
124124
/// Derives `TypeFoldable` for the annotated `struct` or `enum` (`union` is not supported).
125125
///
126126
/// Folds will produce a value of the same struct or enum variant as the input, with each field
127127
/// respectively folded (in definition order) using the `TypeFoldable` implementation for its
128-
/// type. However, if a field of a struct or of an enum variant is annotated with
129-
/// `#[type_foldable(identity)]` then that field will retain its incumbent value (and its type
130-
/// is not required to implement `TypeFoldable`). However use of this attribute is dangerous
131-
/// and should be used with extreme caution: should the type of the annotated field contain
132-
/// (now or in the future) a type that is of interest to a folder, it will not get folded (which
133-
/// may result in unexpected, hard-to-track bugs that could result in unsoundness).
128+
/// type if it has one. Fields of non-generic types that do not contain anything that may be of
129+
/// interest to folders will automatically be left unchanged whether the type implements
130+
/// `TypeFoldable` or not; the same behaviour can be achieved for fields of generic types by
131+
/// applying `#[skip_traversal]` to the field definition (or even to a variant definition if it
132+
/// should apply to all fields therein), but the derived implementation will only be applicable
133+
/// to concrete types where such annotated fields do not contain anything that may be of
134+
/// interest to folders (thus preventing fields from being left unchanged erroneously).
135+
///
136+
/// In some rare situations, it may be necessary for `TypeFoldable` to be implemented for types
137+
/// that do not contain anything of interest to folders. Whilst the macro expansion in such
138+
/// cases would (as described above) result in folds that merely reconstruct `self`, this is
139+
/// accomplished via method calls that might not get optimised away. One can therefore annotate
140+
/// the type itself with `#[skip_traversal]` to immediately return `self` instead; as above,
141+
/// such derived implementations are only applicable if the annotated type does not contain
142+
/// anything that may be of interest to a folder.
134143
///
135144
/// If the annotated type has a `'tcx` lifetime parameter, then that will be used as the
136145
/// lifetime for the type context/interner; otherwise the lifetime of the type context/interner
@@ -146,17 +155,26 @@ decl_derive!(
146155
traversable::traversable_derive::<traversable::Foldable>
147156
);
148157
decl_derive!(
149-
[TypeVisitable, attributes(type_visitable)] =>
158+
[TypeVisitable, attributes(skip_traversal)] =>
150159
/// Derives `TypeVisitable` for the annotated `struct` or `enum` (`union` is not supported).
151160
///
152161
/// Each field of the struct or enum variant will be visited (in definition order) using the
153-
/// `TypeVisitable` implementation for its type. However, if a field of a struct or of an enum
154-
/// variant is annotated with `#[type_visitable(ignore)]` then that field will not be visited
155-
/// (and its type is not required to implement `TypeVisitable`). However use of this attribute
156-
/// is dangerous and should be used with extreme caution: should the type of the annotated
157-
/// field (now or in the future) a type that is of interest to a visitor, it will not get
158-
/// visited (which may result in unexpected, hard-to-track bugs that could result in
159-
/// unsoundness).
162+
/// `TypeVisitable` implementation for its type if it has one. Fields of non-generic types that
163+
/// do not contain anything that may be of interest to visitors will automatically be skipped
164+
/// whether the type implements `TypeVisitable` or not; the same behaviour can be achieved for
165+
/// fields of generic types by applying `#[skip_traversal]` to the field definition (or even to
166+
/// a variant definition if it should apply to all fields therein), but the derived
167+
/// implementation will only be applicable to concrete types where such annotated fields do not
168+
/// contain anything that may be of interest to visitors (thus preventing fields from being so
169+
/// skipped erroneously).
170+
///
171+
/// In some rare situations, it may be necessary for `TypeVisitable` to be implemented for
172+
/// types that do not contain anything of interest to visitors. Whilst the macro expansion in
173+
/// such cases would (as described above) result in visits that skip all fields, this is
174+
/// accomplished via method calls that might not get optimised away. One can therefore annotate
175+
/// the type itself with `#[skip_traversal]` to immediately return `Continue(())` instead; as
176+
/// above, such derived implementations are only applicable if the annotated type does not
177+
/// contain anything that may be of interest to visitors.
160178
///
161179
/// If the annotated type has a `'tcx` lifetime parameter, then that will be used as the
162180
/// lifetime for the type context/interner; otherwise the lifetime of the type context/interner
Lines changed: 182 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,141 @@
1-
use proc_macro2::TokenStream;
2-
use quote::{quote, ToTokens};
3-
use syn::{parse_quote, Attribute, Meta, NestedMeta};
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::{quote, quote_spanned, ToTokens};
3+
use std::collections::HashMap;
4+
use syn::{parse_quote, spanned::Spanned, Attribute, Generics, Ident, Lifetime, LifetimeDef};
5+
6+
/// Generate a type parameter with the given `suffix` that does not conflict with
7+
/// any of the `existing` generics.
8+
fn gen_param(suffix: impl ToString, existing: &Generics) -> Ident {
9+
let mut suffix = suffix.to_string();
10+
while existing.type_params().any(|t| t.ident == suffix) {
11+
suffix.insert(0, '_');
12+
}
13+
Ident::new(&suffix, Span::call_site())
14+
}
15+
16+
/// Return the `TyCtxt` interner for the given `structure`.
17+
///
18+
/// If the input represented by `structure` has a `'tcx` lifetime parameter, then that will be used
19+
/// used as the lifetime of the `TyCtxt`. Otherwise a `'tcx` lifetime parameter that is unrelated
20+
/// to the input will be used.
21+
fn gen_interner(structure: &mut synstructure::Structure<'_>) -> TokenStream {
22+
let lt = structure
23+
.ast()
24+
.generics
25+
.lifetimes()
26+
.find_map(|def| (def.lifetime.ident == "tcx").then_some(&def.lifetime))
27+
.cloned()
28+
.unwrap_or_else(|| {
29+
let tcx: Lifetime = parse_quote! { 'tcx };
30+
structure.add_impl_generic(LifetimeDef::new(tcx.clone()).into());
31+
tcx
32+
});
33+
34+
quote! { ::rustc_middle::ty::TyCtxt<#lt> }
35+
}
36+
37+
/// Returns the `Span` of the first `#[skip_traversal]` attribute in `attrs`.
38+
fn find_skip_traversal_attribute(attrs: &[Attribute]) -> Option<Span> {
39+
attrs.iter().find(|&attr| *attr == parse_quote! { #[skip_traversal] }).map(Spanned::span)
40+
}
441

542
pub struct Foldable;
643
pub struct Visitable;
744

845
/// An abstraction over traversable traits.
946
pub trait Traversable {
10-
/// The trait that this `Traversable` represents.
11-
fn traversable() -> TokenStream;
47+
/// The trait that this `Traversable` represents, parameterised by `interner`.
48+
fn traversable(interner: &impl ToTokens) -> TokenStream;
49+
50+
/// Any supertraits that this trait is required to implement.
51+
fn supertraits(interner: &impl ToTokens) -> TokenStream;
1252

13-
/// The `match` arms for a traversal of this type.
14-
fn arms(structure: &mut synstructure::Structure<'_>) -> TokenStream;
53+
/// A (`noop`) traversal of this trait upon the `bind` expression.
54+
fn traverse(bind: TokenStream, noop: bool) -> TokenStream;
1555

16-
/// The body of an implementation given the match `arms`.
17-
fn impl_body(arms: impl ToTokens) -> TokenStream;
56+
/// A `match` arm for `variant`, where `f` generates the tokens for each binding.
57+
fn arm(
58+
variant: &synstructure::VariantInfo<'_>,
59+
f: impl FnMut(&synstructure::BindingInfo<'_>) -> TokenStream,
60+
) -> TokenStream;
61+
62+
/// The body of an implementation given the `interner`, `traverser` and match expression `body`.
63+
fn impl_body(
64+
interner: impl ToTokens,
65+
traverser: impl ToTokens,
66+
body: impl ToTokens,
67+
) -> TokenStream;
1868
}
1969

2070
impl Traversable for Foldable {
21-
fn traversable() -> TokenStream {
22-
quote! { ::rustc_middle::ty::fold::TypeFoldable<::rustc_middle::ty::TyCtxt<'tcx>> }
71+
fn traversable(interner: &impl ToTokens) -> TokenStream {
72+
quote! { ::rustc_middle::ty::fold::TypeFoldable<#interner> }
2373
}
24-
fn arms(structure: &mut synstructure::Structure<'_>) -> TokenStream {
25-
structure.each_variant(|vi| {
26-
let bindings = vi.bindings();
27-
vi.construct(|_, index| {
28-
let bind = &bindings[index];
29-
30-
// retain value of fields with #[type_foldable(identity)]
31-
let fixed = bind
32-
.ast()
33-
.attrs
34-
.iter()
35-
.map(Attribute::parse_meta)
36-
.filter_map(Result::ok)
37-
.flat_map(|attr| match attr {
38-
Meta::List(list) if list.path.is_ident("type_foldable") => list.nested,
39-
_ => Default::default(),
40-
})
41-
.any(|nested| match nested {
42-
NestedMeta::Meta(Meta::Path(path)) => path.is_ident("identity"),
43-
_ => false,
44-
});
45-
46-
if fixed {
47-
bind.to_token_stream()
48-
} else {
49-
quote! {
50-
::rustc_middle::ty::fold::TypeFoldable::try_fold_with(#bind, __folder)?
51-
}
52-
}
53-
})
54-
})
74+
fn supertraits(interner: &impl ToTokens) -> TokenStream {
75+
Visitable::traversable(interner)
76+
}
77+
fn traverse(bind: TokenStream, noop: bool) -> TokenStream {
78+
if noop {
79+
bind
80+
} else {
81+
quote! { ::rustc_middle::ty::prefer_noop_traversal_if_applicable!(#bind.try_fold_with(folder))? }
82+
}
83+
}
84+
fn arm(
85+
variant: &synstructure::VariantInfo<'_>,
86+
mut f: impl FnMut(&synstructure::BindingInfo<'_>) -> TokenStream,
87+
) -> TokenStream {
88+
let bindings = variant.bindings();
89+
variant.construct(|_, index| f(&bindings[index]))
5590
}
56-
fn impl_body(arms: impl ToTokens) -> TokenStream {
91+
fn impl_body(
92+
interner: impl ToTokens,
93+
traverser: impl ToTokens,
94+
body: impl ToTokens,
95+
) -> TokenStream {
5796
quote! {
58-
fn try_fold_with<__F: ::rustc_middle::ty::fold::FallibleTypeFolder<::rustc_middle::ty::TyCtxt<'tcx>>>(
97+
fn try_fold_with<#traverser: ::rustc_middle::ty::fold::FallibleTypeFolder<#interner>>(
5998
self,
60-
__folder: &mut __F
61-
) -> ::core::result::Result<Self, __F::Error> {
62-
::core::result::Result::Ok(match self { #arms })
99+
folder: &mut #traverser
100+
) -> ::core::result::Result<Self, #traverser::Error> {
101+
::core::result::Result::Ok(#body)
63102
}
64103
}
65104
}
66105
}
67106

68107
impl Traversable for Visitable {
69-
fn traversable() -> TokenStream {
70-
quote! { ::rustc_middle::ty::visit::TypeVisitable<::rustc_middle::ty::TyCtxt<'tcx>> }
108+
fn traversable(interner: &impl ToTokens) -> TokenStream {
109+
quote! { ::rustc_middle::ty::visit::TypeVisitable<#interner> }
71110
}
72-
fn arms(structure: &mut synstructure::Structure<'_>) -> TokenStream {
73-
// ignore fields with #[type_visitable(ignore)]
74-
structure.filter(|bi| {
75-
!bi.ast()
76-
.attrs
77-
.iter()
78-
.map(Attribute::parse_meta)
79-
.filter_map(Result::ok)
80-
.flat_map(|attr| match attr {
81-
Meta::List(list) if list.path.is_ident("type_visitable") => list.nested,
82-
_ => Default::default(),
83-
})
84-
.any(|nested| match nested {
85-
NestedMeta::Meta(Meta::Path(path)) => path.is_ident("ignore"),
86-
_ => false,
87-
})
88-
});
89-
90-
structure.each(|bind| {
91-
quote! {
92-
::rustc_middle::ty::visit::TypeVisitable::visit_with(#bind, __visitor)?;
93-
}
94-
})
111+
fn supertraits(_: &impl ToTokens) -> TokenStream {
112+
quote! { ::core::clone::Clone + ::core::fmt::Debug }
113+
}
114+
fn traverse(bind: TokenStream, noop: bool) -> TokenStream {
115+
if noop {
116+
quote! {}
117+
} else {
118+
quote! { ::rustc_middle::ty::prefer_noop_traversal_if_applicable!(#bind.visit_with(visitor))?; }
119+
}
95120
}
96-
fn impl_body(arms: impl ToTokens) -> TokenStream {
121+
fn arm(
122+
variant: &synstructure::VariantInfo<'_>,
123+
f: impl FnMut(&synstructure::BindingInfo<'_>) -> TokenStream,
124+
) -> TokenStream {
125+
variant.bindings().iter().map(f).collect()
126+
}
127+
fn impl_body(
128+
interner: impl ToTokens,
129+
traverser: impl ToTokens,
130+
body: impl ToTokens,
131+
) -> TokenStream {
97132
quote! {
98-
fn visit_with<__V: ::rustc_middle::ty::visit::TypeVisitor<::rustc_middle::ty::TyCtxt<'tcx>>>(
133+
fn visit_with<#traverser: ::rustc_middle::ty::visit::TypeVisitor<#interner>>(
99134
&self,
100-
__visitor: &mut __V
101-
) -> ::std::ops::ControlFlow<__V::BreakTy> {
102-
match self { #arms }
103-
::std::ops::ControlFlow::Continue(())
135+
visitor: &mut #traverser
136+
) -> ::core::ops::ControlFlow<#traverser::BreakTy> {
137+
#body
138+
::core::ops::ControlFlow::Continue(())
104139
}
105140
}
106141
}
@@ -109,17 +144,78 @@ impl Traversable for Visitable {
109144
pub fn traversable_derive<T: Traversable>(
110145
mut structure: synstructure::Structure<'_>,
111146
) -> TokenStream {
112-
if let syn::Data::Union(_) = structure.ast().data {
113-
panic!("cannot derive on union")
114-
}
147+
let skip_traversal = quote! { ::rustc_middle::ty::SkipTraversalAutoImplOnly };
148+
149+
let interner = gen_interner(&mut structure);
150+
let traverser = gen_param("T", &structure.ast().generics);
151+
let traversable = T::traversable(&interner);
152+
let ast = structure.ast();
115153

116-
structure.add_bounds(synstructure::AddBounds::Generics);
154+
structure.add_bounds(synstructure::AddBounds::None);
117155
structure.bind_with(|_| synstructure::BindStyle::Move);
118156

119-
if !structure.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
120-
structure.add_impl_generic(parse_quote! { 'tcx });
157+
// If our derived implementation will be generic over the traversable type, then we must
158+
// constrain it to only those generic combinations that satisfy the traversable trait's
159+
// supertraits.
160+
let is_generic = !ast.generics.params.is_empty();
161+
if is_generic {
162+
let supertraits = T::supertraits(&interner);
163+
structure.add_where_predicate(parse_quote! { Self: #supertraits });
121164
}
122165

123-
let arms = T::arms(&mut structure);
124-
structure.bound_impl(T::traversable(), T::impl_body(arms))
166+
let body = if let Some(_span) = find_skip_traversal_attribute(&ast.attrs) {
167+
if !is_generic {
168+
// FIXME: spanned error causes ICE: "suggestion must not have overlapping parts"
169+
return quote!({
170+
::core::compile_error!("non-generic types are automatically skipped where possible");
171+
});
172+
}
173+
structure.add_where_predicate(parse_quote! { Self: #skip_traversal });
174+
T::traverse(quote! { self }, true)
175+
} else {
176+
// We add predicates to each generic field type, rather than to our generic type parameters.
177+
// This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations
178+
// into wrapping types, but it can result in trait solver cycles if any type parameters are involved
179+
// in recursive type definitions; fortunately that is not the case (yet).
180+
let mut predicates = HashMap::new();
181+
let arms = structure.each_variant(|variant| {
182+
let skipped_variant_span = find_skip_traversal_attribute(&variant.ast().attrs);
183+
if variant.referenced_ty_params().is_empty() {
184+
if let Some(span) = skipped_variant_span {
185+
return quote_spanned!(span => {
186+
::core::compile_error!("non-generic variants are automatically skipped where possible");
187+
});
188+
}
189+
}
190+
T::arm(variant, |bind| {
191+
let ast = bind.ast();
192+
let skipped_span = skipped_variant_span.or_else(|| find_skip_traversal_attribute(&ast.attrs));
193+
if bind.referenced_ty_params().is_empty() {
194+
if skipped_variant_span.is_none() && let Some(span) = skipped_span {
195+
return quote_spanned!(span => {
196+
::core::compile_error!("non-generic fields are automatically skipped where possible");
197+
});
198+
}
199+
} else if let Some(prev) = predicates.insert(ast.ty.clone(), skipped_span) && let Some(span) = prev.xor(skipped_span) {
200+
// It makes no sense to allow this. Skipping the field requires that its type impls the `SkipTraversalAutoImplOnly`
201+
// auto-trait, which means that it does not contain anything interesting to traversers; not also skipping all other
202+
// fields of identical type is indicative of a likely erroneous assumption on the author's part--especially since
203+
// such other (unannotated) fields will be skipped anyway, as the noop traversal takes precedence.
204+
return quote_spanned!(span => {
205+
::core::compile_error!("generic field skipped here, but another field of the same type is not skipped");
206+
});
207+
}
208+
T::traverse(bind.into_token_stream(), skipped_span.is_some())
209+
})
210+
});
211+
// the order in which `where` predicates appear in rust source is irrelevant
212+
#[allow(rustc::potential_query_instability)]
213+
for (ty, skip) in predicates {
214+
let bound = if skip.is_some() { &skip_traversal } else { &traversable };
215+
structure.add_where_predicate(parse_quote! { #ty: #bound });
216+
}
217+
quote! { match self { #arms } }
218+
};
219+
220+
structure.bound_impl(traversable, T::impl_body(interner, traverser, body))
125221
}

compiler/rustc_middle/src/mir/query.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,6 @@ pub struct GeneratorLayout<'tcx> {
161161
/// Which saved locals are storage-live at the same time. Locals that do not
162162
/// have conflicts with each other are allowed to overlap in the computed
163163
/// layout.
164-
#[type_foldable(identity)]
165-
#[type_visitable(ignore)]
166164
pub storage_conflicts: BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal>,
167165
}
168166

0 commit comments

Comments
 (0)