diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbf5be..2f43738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [0.13.0] — 2026-07-22 + +- Update `syn` to 3.0 (#72) + # [0.12.0] — 2026-06-10 - Add cargo-deny check (#67) diff --git a/Cargo.toml b/Cargo.toml index 82541d0..907be6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ edition = "2024" [package] name = "impl-tools" -version = "0.12.0" +version = "0.13.0" description = "Helper macros: autoimpl" keywords = ["proc-macro", "macro", "derive", "trait", "procedural"] categories = ["development-tools::procedural-macro-helpers"] @@ -36,10 +36,10 @@ version = "3.0.2" default-features = false [dependencies.syn] -version = "2.0.0" +version = "3.0.2" [dependencies.impl-tools-lib] -version = "0.12.0" +version = "0.13.0" path = "lib" [dev-dependencies] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index abecc67..1499467 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "impl-tools-lib" -version = "0.12.0" +version = "0.13.0" description = "Helper macros: autoimpl" keywords = ["derive", "trait"] readme = "README.md" @@ -24,7 +24,7 @@ version = "3.0.2" default-features = false [dependencies.syn] -version = "2.0.0" +version = "3.0.2" # We need 'extra-traits' for equality testing # We need 'full' for parsing macros within macro arguments features = ["extra-traits", "full", "visit", "visit-mut"] diff --git a/lib/src/anon.rs b/lib/src/anon.rs index 60fb4ad..8e2aa9a 100644 --- a/lib/src/anon.rs +++ b/lib/src/anon.rs @@ -5,9 +5,9 @@ //! The `impl_anon!` macro -use crate::IdentFormatter; use crate::fields::{Field, Fields, FieldsNamed, FieldsUnnamed, StructStyle}; use crate::scope::{Scope, ScopeItem}; +use crate::{IdentFormatter, error_on_attrs}; use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt, quote}; use syn::token::{Brace, Colon, Comma, Eq, Paren, Semi}; @@ -113,13 +113,20 @@ impl Anon { }; let ty: Type = match field.ty { - Type::ImplTrait(syn::TypeImplTrait { impl_token, bounds }) => { + Type::ImplTrait(syn::TypeImplTrait { + attrs, + impl_token, + bounds, + }) => { + error_on_attrs(&attrs); + let span = quote! { #impl_token #bounds }.span(); let ty = Ident::new(&ty_name, span); self.generics.params.push(parse_quote! { #ty: #bounds }); Type::Path(TypePath { + attrs: vec![], qself: None, path: ty.into(), }) @@ -129,6 +136,7 @@ impl Anon { self.generics.params.push(parse_quote! { #ty }); Type::Path(TypePath { + attrs: vec![], qself: None, path: ty.into(), }) @@ -150,7 +158,12 @@ impl Anon { impl<'a> syn::visit_mut::VisitMut for ReplaceInfers<'a> { fn visit_type_mut(&mut self, node: &mut Type) { let (span, bounds) = match node { - Type::ImplTrait(syn::TypeImplTrait { impl_token, bounds }) => { + Type::ImplTrait(syn::TypeImplTrait { + attrs, + impl_token, + bounds, + }) => { + error_on_attrs(attrs); (impl_token.span, std::mem::take(bounds)) } Type::Infer(infer) => (infer.span(), Punctuated::new()), @@ -167,11 +180,11 @@ impl Anon { ident: ident.clone(), colon_token: Some(Default::default()), bounds, - eq_token: None, default: None, })); *node = Type::Path(TypePath { + attrs: vec![], qself: None, path: ident.into(), }); @@ -331,8 +344,14 @@ mod parsing { while let Type::Group(ty) = first_ty { first_ty = *ty.elem; } - if let Type::Path(TypePath { qself: None, path }) = first_ty { - trait_ = Some((None, path, for_token)); + if let Type::Path(TypePath { + attrs, + qself: None, + path, + }) = first_ty + { + error_on_attrs(&attrs); + trait_ = Some((path, for_token)); } else { unreachable!(); } @@ -350,6 +369,7 @@ mod parsing { if self_ty != parse_quote! { Self } { if let Some(ident) = in_ident { if !matches!(self_ty, Type::Path(TypePath { + attrs: _, qself: None, path: syn::Path { leading_colon: None, @@ -379,9 +399,12 @@ mod parsing { items.push(content.parse()?); } + let mut modifiers = syn::ImplModifiers::default(); + modifiers.defaultness = defaultness; + Ok(ItemImpl { attrs, - defaultness, + modifiers, unsafety, impl_token, generics, diff --git a/lib/src/autoimpl/for_deref.rs b/lib/src/autoimpl/for_deref.rs index 0280ed5..74d275d 100644 --- a/lib/src/autoimpl/for_deref.rs +++ b/lib/src/autoimpl/for_deref.rs @@ -10,10 +10,11 @@ use crate::utils::propagate_attr_to_impl; use proc_macro_error3::{emit_call_site_error, emit_call_site_warning, emit_error}; use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt, quote}; +use syn::parse_quote; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::{Comma, Eq, PathSep}; -use syn::{FnArg, Ident, Item, Member, Pat, Token, TraitItem, Type, TypePath, parse_quote}; +use syn::{FnArg, Ident, Item, Member, Pat, ReceiverKind, Token, TraitItem, Type, TypePath}; mod kw { syn::custom_keyword!(using); @@ -68,8 +69,11 @@ mod parsing { if let WherePredicate::Type(pred) = pred { for bound in &pred.bounds { if matches!(bound, TypeParamBound::TraitSubst(_)) { - if let Type::Path(TypePath { qself: None, path }) = - &pred.bounded_ty + if let Type::Path(TypePath { + attrs: _, + qself: None, + path, + }) = &pred.bounded_ty { if let Some(ident) = path.get_ident() { definitive = Some(ident.clone()); @@ -242,7 +246,7 @@ impl ForDeref { if self.using.is_none() { bound = bound.max(match item.sig.inputs.first() { Some(FnArg::Receiver(rec)) => { - if rec.reference.is_some() { + if matches!(rec.kind, ReceiverKind::Reference(_, _, _)) { Bound::Deref(rec.mutability.is_some()) } else { emit_call_site_error!( @@ -273,11 +277,11 @@ impl ForDeref { } } if let Some(member) = self.using.as_ref() { - if let Some((r, _)) = arg.reference { + if let ReceiverKind::Reference(r, _, mutability) = arg.kind { r.to_tokens(&mut toks); - } - if let Some(m) = arg.mutability { - m.to_tokens(&mut toks); + if let Some(m) = mutability { + m.to_tokens(&mut toks); + } } let self_ = &arg.self_token; toks.append_all(quote! { #self_ . #member }); diff --git a/lib/src/generics.rs b/lib/src/generics.rs index 7767f81..fbd28ce 100644 --- a/lib/src/generics.rs +++ b/lib/src/generics.rs @@ -69,10 +69,8 @@ pub struct TypeParam { pub colon_token: Option, /// List of type bounds pub bounds: Punctuated, - /// `=` - pub eq_token: Option, /// Optional default value - pub default: Option, + pub default: Option<(Token![=], Type)>, } /// A trait or lifetime used as a bound on a type parameter. @@ -169,7 +167,6 @@ mod parsing { ident: input.call(Ident::parse_any)?, colon_token: None, bounds: Punctuated::new(), - eq_token: None, default: None, })); } else { @@ -217,8 +214,8 @@ mod parsing { } let eq_token: Option = input.parse()?; - let default = if eq_token.is_some() { - Some(input.parse::()?) + let default = if let Some(tok) = eq_token { + Some((tok, input.parse::()?)) } else { None }; @@ -228,7 +225,6 @@ mod parsing { ident, colon_token, bounds, - eq_token, default, }) } @@ -278,6 +274,7 @@ mod parsing { fn parse(input: ParseStream) -> Result { if input.peek(Lifetime) && input.peek2(Token![:]) { Ok(WherePredicate::Lifetime(PredicateLifetime { + attrs: vec![], lifetime: input.parse()?, colon_token: input.parse()?, bounds: { @@ -403,8 +400,8 @@ mod printing_subst { self.colon_token.unwrap_or_default().to_tokens(tokens); self.bounds.to_tokens_subst(tokens, subst); } - if let Some(default) = &self.default { - self.eq_token.unwrap_or_default().to_tokens(tokens); + if let Some((tok, default)) = &self.default { + tok.to_tokens(tokens); default.to_tokens(tokens); } } @@ -477,7 +474,6 @@ fn map_generic_param(param: &syn::GenericParam) -> GenericParam { .pairs() .map(|pair| map_pair(pair, map_type_param_bound)), ), - eq_token: ty.eq_token, default: ty.default.clone(), }), syn::GenericParam::Lifetime(lt) => GenericParam::Lifetime(lt.clone()), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 9957dc1..9b001e8 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -160,3 +160,12 @@ impl SimplePath { } } } + +// Attributes are supported in additional positions in syn v3.0. +// For now we reject attributes in these positions. +fn error_on_attrs(attrs: &[syn::Attribute]) { + use proc_macro_error3::emit_error; + for attr in attrs.iter() { + emit_error!(attr, "attributes are not (yet) supported in this context"); + } +} diff --git a/lib/src/scope.rs b/lib/src/scope.rs index 37ce4d3..4913606 100644 --- a/lib/src/scope.rs +++ b/lib/src/scope.rs @@ -234,6 +234,7 @@ impl Scope { mod parsing { use super::*; + use crate::error_on_attrs; use crate::fields::parsing::data_struct; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; @@ -388,8 +389,14 @@ mod parsing { while let Type::Group(ty) = first_ty { first_ty = *ty.elem; } - if let Type::Path(TypePath { qself: None, path }) = first_ty { - trait_ = Some((None, path, for_token)); + if let Type::Path(TypePath { + attrs, + qself: None, + path, + }) = first_ty + { + error_on_attrs(&attrs); + trait_ = Some((path, for_token)); } else { unreachable!(); } @@ -406,11 +413,12 @@ mod parsing { if self_ty != parse_quote! { Self } && !matches!(self_ty, Type::Path(TypePath { + attrs: _, qself: None, path: Path { leading_colon: None, ref segments, - } + }, }) if segments.len() == 1 && segments.first().unwrap().ident == *in_ident) { return Err(Error::new( @@ -431,9 +439,12 @@ mod parsing { items.push(content.parse()?); } + let mut modifiers = syn::ImplModifiers::default(); + modifiers.defaultness = defaultness; + Ok(ItemImpl { attrs, - defaultness, + modifiers, unsafety, impl_token, generics, diff --git a/lib/src/split_impl.rs b/lib/src/split_impl.rs index ce3fcac..4f1293c 100644 --- a/lib/src/split_impl.rs +++ b/lib/src/split_impl.rs @@ -66,7 +66,7 @@ impl SplitImpl { items.push(ImplItem::Const(syn::ImplItemConst { attrs: copy_non_doc_attrs(&item.attrs), vis: syn::Visibility::Inherited, - defaultness: None, + modifiers: syn::ConstModifiers::default(), const_token: item.const_token, ident: item.ident.clone(), generics: item.generics.clone(), @@ -86,7 +86,7 @@ impl SplitImpl { items.push(ImplItem::Fn(syn::ImplItemFn { attrs: copy_non_doc_attrs(&item.attrs), vis: syn::Visibility::Inherited, - defaultness: None, + modifiers: syn::FnModifiers::default(), sig: item.sig.clone(), block, })); @@ -102,7 +102,7 @@ impl SplitImpl { items.push(ImplItem::Type(syn::ImplItemType { attrs: copy_non_doc_attrs(&item.attrs), vis: syn::Visibility::Inherited, - defaultness: None, + modifiers: syn::TypeModifiers::default(), type_token: item.type_token, ident: item.ident.clone(), generics: item.generics.clone(), @@ -124,11 +124,11 @@ impl SplitImpl { let impl_ = syn::ItemImpl { attrs, - defaultness: None, + modifiers: syn::ImplModifiers::default(), unsafety: None, impl_token: Default::default(), generics, - trait_: Some((None, path, self.for_)), + trait_: Some((path, self.for_)), self_ty: self.target, brace_token: Default::default(), items, diff --git a/lib/src/utils.rs b/lib/src/utils.rs index 8787569..eaa3420 100644 --- a/lib/src/utils.rs +++ b/lib/src/utils.rs @@ -64,12 +64,10 @@ pub fn extend_generics(generics: &mut Generics, gen2: &Generics) { for param in &mut generics.params { match param { GenericParam::Type(p) => { - p.eq_token = None; p.default = None; } GenericParam::Lifetime(_) => (), GenericParam::Const(p) => { - p.eq_token = None; p.default = None; } } diff --git a/tests/test-cfg/Cargo.toml b/tests/test-cfg/Cargo.toml index d7400d7..0d78270 100644 --- a/tests/test-cfg/Cargo.toml +++ b/tests/test-cfg/Cargo.toml @@ -11,4 +11,4 @@ publish = false feature1 = [] [dependencies] -impl-tools = { version = "0.12", path = "../.." } +impl-tools = { version = "0.13", path = "../.." }