Implement AST visitors using a derive macro.

This commit is contained in:
Camille GILLOT 2025-07-03 22:24:21 +00:00
parent ca9eecda36
commit 3c81faec23
7 changed files with 1219 additions and 1675 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
use rustc_data_structures::fx::FxHashMap;
use rustc_macros::{Decodable, Encodable};
use rustc_macros::{Decodable, Encodable, Walkable};
use rustc_span::{Ident, Span, Symbol};
use crate::Expr;
@ -41,7 +41,7 @@ use crate::token::LitKind;
/// Basically the "AST" for a complete `format_args!()`.
///
/// E.g., `format_args!("hello {name}");`.
#[derive(Clone, Encodable, Decodable, Debug)]
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct FormatArgs {
pub span: Span,
pub template: Vec<FormatArgsPiece>,
@ -63,7 +63,7 @@ pub struct FormatArgs {
/// A piece of a format template string.
///
/// E.g. "hello" or "{name}".
#[derive(Clone, Encodable, Decodable, Debug)]
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub enum FormatArgsPiece {
Literal(Symbol),
Placeholder(FormatPlaceholder),
@ -73,7 +73,7 @@ pub enum FormatArgsPiece {
///
/// E.g. `1, 2, name="ferris", n=3`,
/// but also implicit captured arguments like `x` in `format_args!("{x}")`.
#[derive(Clone, Encodable, Decodable, Debug)]
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct FormatArguments {
arguments: Vec<FormatArgument>,
num_unnamed_args: usize,
@ -144,13 +144,13 @@ impl FormatArguments {
}
}
#[derive(Clone, Encodable, Decodable, Debug)]
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct FormatArgument {
pub kind: FormatArgumentKind,
pub expr: P<Expr>,
}
#[derive(Clone, Encodable, Decodable, Debug)]
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub enum FormatArgumentKind {
/// `format_args(…, arg)`
Normal,
@ -170,24 +170,28 @@ impl FormatArgumentKind {
}
}
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)]
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, Walkable)]
pub struct FormatPlaceholder {
/// Index into [`FormatArgs::arguments`].
pub argument: FormatArgPosition,
/// The span inside the format string for the full `{…}` placeholder.
pub span: Option<Span>,
/// `{}`, `{:?}`, or `{:x}`, etc.
#[visitable(ignore)]
pub format_trait: FormatTrait,
/// `{}` or `{:.5}` or `{:-^20}`, etc.
#[visitable(ignore)]
pub format_options: FormatOptions,
}
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)]
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, Walkable)]
pub struct FormatArgPosition {
/// Which argument this position refers to (Ok),
/// or would've referred to if it existed (Err).
#[visitable(ignore)]
pub index: Result<usize, usize>,
/// What kind of position this is. See [`FormatArgPositionKind`].
#[visitable(ignore)]
pub kind: FormatArgPositionKind,
/// The span of the name or number.
pub span: Option<Span>,

View file

@ -12,14 +12,14 @@ use std::panic;
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
use rustc_span::source_map::Spanned;
use rustc_span::{Ident, Span};
use rustc_span::{Ident, Span, Symbol};
use smallvec::{SmallVec, smallvec};
use thin_vec::ThinVec;
use crate::ast::*;
use crate::ptr::P;
use crate::tokenstream::*;
use crate::visit::{AssocCtxt, BoundKind, FnCtxt, VisitorResult, try_visit, visit_opt, walk_list};
use crate::visit::{AssocCtxt, BoundKind, FnCtxt, LifetimeCtxt, VisitorResult, try_visit};
mod sealed {
use rustc_ast_ir::visit::VisitorResult;
@ -36,11 +36,249 @@ mod sealed {
use sealed::MutVisitorResult;
pub(crate) trait MutVisitable<V: MutVisitor> {
type Extra: Copy;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra);
}
impl<V: MutVisitor, T: ?Sized> MutVisitable<V> for P<T>
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
(**self).visit_mut(visitor, extra)
}
}
impl<V: MutVisitor, T> MutVisitable<V> for Option<T>
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
if let Some(this) = self {
this.visit_mut(visitor, extra)
}
}
}
impl<V: MutVisitor, T> MutVisitable<V> for Spanned<T>
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
let Spanned { span, node } = self;
span.visit_mut(visitor, ());
node.visit_mut(visitor, extra);
}
}
impl<V: MutVisitor, T> MutVisitable<V> for [T]
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
for item in self {
item.visit_mut(visitor, extra);
}
}
}
impl<V: MutVisitor, T> MutVisitable<V> for Vec<T>
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
for item in self {
item.visit_mut(visitor, extra);
}
}
}
impl<V: MutVisitor, T> MutVisitable<V> for (T,)
where
T: MutVisitable<V>,
{
type Extra = T::Extra;
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
self.0.visit_mut(visitor, extra);
}
}
impl<V: MutVisitor, T1, T2> MutVisitable<V> for (T1, T2)
where
T1: MutVisitable<V, Extra = ()>,
T2: MutVisitable<V, Extra = ()>,
{
type Extra = ();
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
self.0.visit_mut(visitor, extra);
self.1.visit_mut(visitor, extra);
}
}
impl<V: MutVisitor, T1, T2, T3> MutVisitable<V> for (T1, T2, T3)
where
T1: MutVisitable<V, Extra = ()>,
T2: MutVisitable<V, Extra = ()>,
T3: MutVisitable<V, Extra = ()>,
{
type Extra = ();
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
self.0.visit_mut(visitor, extra);
self.1.visit_mut(visitor, extra);
self.2.visit_mut(visitor, extra);
}
}
impl<V: MutVisitor, T1, T2, T3, T4> MutVisitable<V> for (T1, T2, T3, T4)
where
T1: MutVisitable<V, Extra = ()>,
T2: MutVisitable<V, Extra = ()>,
T3: MutVisitable<V, Extra = ()>,
T4: MutVisitable<V, Extra = ()>,
{
type Extra = ();
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
self.0.visit_mut(visitor, extra);
self.1.visit_mut(visitor, extra);
self.2.visit_mut(visitor, extra);
self.3.visit_mut(visitor, extra);
}
}
pub trait MutWalkable<V: MutVisitor> {
fn walk_mut(&mut self, visitor: &mut V);
}
macro_rules! visit_visitable {
(mut $visitor:expr, $($expr:expr),* $(,)?) => {{
$(MutVisitable::visit_mut($expr, $visitor, ());)*
}};
}
macro_rules! visit_visitable_with {
(mut $visitor:expr, $expr:expr, $extra:expr $(,)?) => {
MutVisitable::visit_mut($expr, $visitor, $extra)
};
}
macro_rules! walk_walkable {
($visitor:expr, $expr:expr, mut) => {
MutWalkable::walk_mut($expr, $visitor)
};
}
macro_rules! impl_visitable {
(|&mut $self:ident: $self_ty:ty,
$vis:ident: &mut $vis_ty:ident,
$extra:ident: $extra_ty:ty| $block:block) => {
#[allow(unused_parens, non_local_definitions)]
impl<$vis_ty: MutVisitor> MutVisitable<$vis_ty> for $self_ty {
type Extra = $extra_ty;
fn visit_mut(&mut $self, $vis: &mut $vis_ty, $extra: Self::Extra) -> V::Result {
$block
}
}
};
}
macro_rules! impl_walkable {
($(<$K:ident: $Kb:ident>)? |&mut $self:ident: $self_ty:ty,
$vis:ident: &mut $vis_ty:ident| $block:block) => {
#[allow(unused_parens, non_local_definitions)]
impl<$($K: $Kb,)? $vis_ty: MutVisitor> MutWalkable<$vis_ty> for $self_ty {
fn walk_mut(&mut $self, $vis: &mut $vis_ty) -> V::Result {
$block
}
}
};
}
macro_rules! impl_visitable_noop {
(<mut> $($ty:ty,)*) => {
$(
impl_visitable!(|&mut self: $ty, _vis: &mut V, _extra: ()| {});
)*
};
}
macro_rules! impl_visitable_list {
(<mut> $($ty:ty,)*) => {
$(impl<V: MutVisitor, T> MutVisitable<V> for $ty
where
for<'a> &'a mut $ty: IntoIterator<Item = &'a mut T>,
T: MutVisitable<V>,
{
type Extra = <T as MutVisitable<V>>::Extra;
#[inline]
fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra) {
for i in self {
i.visit_mut(visitor, extra);
}
}
})*
}
}
macro_rules! impl_visitable_direct {
(<mut> $($ty:ty,)*) => {
$(impl_visitable!(
|&mut self: $ty, visitor: &mut V, _extra: ()| {
MutWalkable::walk_mut(self, visitor)
}
);)*
}
}
macro_rules! impl_visitable_calling_walkable {
(<mut>
$( fn $method:ident($ty:ty $(, $extra_name:ident: $extra_ty:ty)?); )*
) => {
$(fn $method(&mut self, node: &mut $ty $(, $extra_name:$extra_ty)?) {
impl_visitable!(|&mut self: $ty, visitor: &mut V, extra: ($($extra_ty)?)| {
let ($($extra_name)?) = extra;
visitor.$method(self $(, $extra_name)?);
});
walk_walkable!(self, node, mut)
})*
}
}
macro_rules! define_named_walk {
((mut) $Visitor:ident
$( pub fn $method:ident($ty:ty); )*
) => {
$(pub fn $method<V: $Visitor>(visitor: &mut V, node: &mut $ty) {
walk_walkable!(visitor, node, mut)
})*
};
}
super::common_visitor_and_walkers!((mut) MutVisitor);
macro_rules! generate_flat_map_visitor_fns {
($($name:ident, $Ty:ty, $flat_map_fn:ident$(, $param:ident: $ParamTy:ty)*;)+) => {
$(
#[allow(unused_parens)]
impl<V: MutVisitor> MutVisitable<V> for ThinVec<$Ty> {
type Extra = ($($ParamTy),*);
#[inline]
fn visit_mut(
&mut self,
visitor: &mut V,
($($param),*): Self::Extra,
) -> V::Result {
$name(visitor, self $(, $param)*)
}
}
fn $name<V: MutVisitor>(
vis: &mut V,
values: &mut ThinVec<$Ty>,
@ -78,15 +316,6 @@ pub fn walk_flat_map_pat_field<T: MutVisitor>(
smallvec![fp]
}
fn visit_nested_use_tree<V: MutVisitor>(
vis: &mut V,
nested_tree: &mut UseTree,
nested_id: &mut NodeId,
) {
vis.visit_id(nested_id);
vis.visit_use_tree(nested_tree);
}
macro_rules! generate_walk_flat_map_fns {
($($fn_name:ident($Ty:ty$(,$extra_name:ident: $ExtraTy:ty)*) => $visit_fn_name:ident;)+) => {$(
pub fn $fn_name<V: MutVisitor>(vis: &mut V, mut value: $Ty$(,$extra_name: $ExtraTy)*) -> SmallVec<[$Ty; 1]> {
@ -109,14 +338,6 @@ generate_walk_flat_map_fns! {
walk_flat_map_assoc_item(P<AssocItem>, ctxt: AssocCtxt) => visit_assoc_item;
}
fn walk_ty_alias_where_clauses<T: MutVisitor>(vis: &mut T, tawcs: &mut TyAliasWhereClauses) {
let TyAliasWhereClauses { before, after, split: _ } = tawcs;
let TyAliasWhereClause { has_where_token: _, span: span_before } = before;
let TyAliasWhereClause { has_where_token: _, span: span_after } = after;
vis.visit_span(span_before);
vis.visit_span(span_after);
}
pub fn walk_filter_map_expr<T: MutVisitor>(vis: &mut T, mut e: P<Expr>) -> Option<P<Expr>> {
vis.visit_expr(&mut e);
Some(e)

View file

@ -20,7 +20,7 @@ use std::{cmp, fmt, iter, mem};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::sync;
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
use rustc_macros::{Decodable, Encodable, HashStable_Generic, Walkable};
use rustc_serialize::{Decodable, Encodable};
use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym};
use thin_vec::ThinVec;
@ -977,7 +977,7 @@ impl TokenCursor {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Encodable, Decodable, HashStable_Generic)]
#[derive(Debug, Copy, Clone, PartialEq, Encodable, Decodable, HashStable_Generic, Walkable)]
pub struct DelimSpan {
pub open: Span,
pub close: Span,

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@ mod symbols;
mod try_from;
mod type_foldable;
mod type_visitable;
mod visitable;
// Reads the rust version (e.g. "1.75.0") from the CFG_RELEASE env var and
// produces a `RustcVersion` literal containing that version (e.g.
@ -101,6 +102,16 @@ decl_derive!(
/// visited (and its type is not required to implement `TypeVisitable`).
type_visitable::type_visitable_derive
);
decl_derive!(
[Walkable, attributes(visitable)] =>
/// Derives `Walkable` for the annotated `struct` or `enum` (`union` is not supported).
///
/// Each field of the struct or enum variant will be visited in definition order, using the
/// `Walkable` implementation for its type. However, if a field of a struct or an enum
/// variant is annotated with `#[visitable(ignore)]` then that field will not be
/// visited (and its type is not required to implement `Walkable`).
visitable::visitable_derive
);
decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
decl_derive!(
[Diagnostic, attributes(

View file

@ -0,0 +1,82 @@
use quote::quote;
use synstructure::BindingInfo;
pub(super) fn visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data {
panic!("cannot derive on union")
}
let has_attr = |bind: &BindingInfo<'_>, name| {
let mut found = false;
bind.ast().attrs.iter().for_each(|attr| {
if !attr.path().is_ident("visitable") {
return;
}
let _ = attr.parse_nested_meta(|nested| {
if nested.path.is_ident(name) {
found = true;
}
Ok(())
});
});
found
};
let get_attr = |bind: &BindingInfo<'_>, name: &str| {
let mut content = None;
bind.ast().attrs.iter().for_each(|attr| {
if !attr.path().is_ident("visitable") {
return;
}
let _ = attr.parse_nested_meta(|nested| {
if nested.path.is_ident(name) {
let value = nested.value()?;
let value = value.parse()?;
content = Some(value);
}
Ok(())
});
});
content
};
s.add_bounds(synstructure::AddBounds::Generics);
s.bind_with(|_| synstructure::BindStyle::Ref);
let ref_visit = s.each(|bind| {
let extra = get_attr(bind, "extra").unwrap_or(quote! {});
if has_attr(bind, "ignore") {
quote! {}
} else {
quote! { rustc_ast_ir::try_visit!(crate::visit::Visitable::visit(#bind, __visitor, (#extra))) }
}
});
s.bind_with(|_| synstructure::BindStyle::RefMut);
let mut_visit = s.each(|bind| {
let extra = get_attr(bind, "extra").unwrap_or(quote! {});
if has_attr(bind, "ignore") {
quote! {}
} else {
quote! { crate::mut_visit::MutVisitable::visit_mut(#bind, __visitor, (#extra)) }
}
});
s.gen_impl(quote! {
gen impl<'__ast, __V> crate::visit::Walkable<'__ast, __V> for @Self
where __V: crate::visit::Visitor<'__ast>,
{
fn walk_ref(&'__ast self, __visitor: &mut __V) -> __V::Result {
match *self { #ref_visit }
<__V::Result as rustc_ast_ir::visit::VisitorResult>::output()
}
}
gen impl<__V> crate::mut_visit::MutWalkable<__V> for @Self
where __V: crate::mut_visit::MutVisitor,
{
fn walk_mut(&mut self, __visitor: &mut __V) {
match *self { #mut_visit }
}
}
})
}