From 70615917820a8c8e2fe42ecccdc8b3df8aa893cf Mon Sep 17 00:00:00 2001 From: Sammy <41593264+stegaBOB@users.noreply.github.com> Date: Sun, 13 Apr 2025 02:59:34 -0500 Subject: [PATCH 1/6] Allow lifetime for bounds in non-binded generic params --- src/attr/item.rs | 33 +++++++++++++++++++++++---------- src/test/bound.rs | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/attr/item.rs b/src/attr/item.rs index e0b6d03..94418d7 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -7,7 +7,7 @@ use syn::{ parse::{discouraged::Speculative, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Attribute, Data, Ident, Meta, Path, PredicateType, Result, Token, TraitBound, + Attribute, BoundLifetimes, Data, Ident, Meta, Path, PredicateType, Result, Token, TraitBound, TraitBoundModifier, Type, TypeParamBound, TypePath, WhereClause, WherePredicate, }; @@ -241,7 +241,10 @@ impl DeriveWhere { /// Returns `true` if the given generic type parameter if present. pub fn has_type_param(&self, type_param: &Ident) -> bool { self.generics.iter().any(|generic| match generic { - Generic::NoBound(Type::Path(TypePath { qself: None, path })) => { + Generic::NoBound(GenericNoBound( + _lifetimes, + Type::Path(TypePath { qself: None, path }), + )) => { if let Some(ident) = path.get_ident() { ident == type_param } else { @@ -281,8 +284,8 @@ impl DeriveWhere { .predicates .push(WherePredicate::Type(match generic { Generic::CustomBound(type_bound) => type_bound.clone(), - Generic::NoBound(path) => PredicateType { - lifetimes: None, + Generic::NoBound(GenericNoBound(lifetimes, path)) => PredicateType { + lifetimes: lifetimes.clone(), bounded_ty: path.clone(), colon_token: ::default(), bounds: trait_.where_bounds(item), @@ -293,13 +296,23 @@ impl DeriveWhere { } } -/// Holds a single generic [type](Type) or [type with bound](PredicateType). +/// Holds the first part of a [`PredicateType`] prior to the `:`. Optionally contains lifetime `for` +/// bindings. +#[derive(Eq, PartialEq)] +pub struct GenericNoBound(Option, Type); +impl Parse for GenericNoBound { + fn parse(input: ParseStream) -> Result { + Ok(Self(input.parse()?, input.parse()?)) + } +} + +/// Holds a single generic [type](GenericNoBound) with optional lifetime bounds or [type with bound](PredicateType). #[derive(Eq, PartialEq)] pub enum Generic { /// Generic type with custom [specified bounds](PredicateType). CustomBound(PredicateType), - /// Generic [type](Type) which will be bound to the [`DeriveTrait`]. - NoBound(Type), + /// Generic [type](GenericNoBound) which will be bound to the [`DeriveTrait`]. + NoBound(GenericNoBound), } impl Parse for Generic { @@ -307,7 +320,7 @@ impl Parse for Generic { let fork = input.fork(); // Try to parse input as a `WherePredicate`. The problem is, both expressions - // start with a Type, so starting with the `WherePredicate` is the easiest way + // start with an optional lifetime for bound and then Type, so starting with the `WherePredicate` is the easiest way // of differentiating them. if let Ok(where_predicate) = WherePredicate::parse(&fork) { input.advance_to(&fork); @@ -319,8 +332,8 @@ impl Parse for Generic { Err(Error::generic(where_predicate.span())) } } else { - match Type::parse(input) { - Ok(type_) => Ok(Generic::NoBound(type_)), + match GenericNoBound::parse(input) { + Ok(no_bound) => Ok(Generic::NoBound(no_bound)), Err(error) => Err(Error::generic_syntax(error.span(), error)), } } diff --git a/src/test/bound.rs b/src/test/bound.rs index 07ba600..a84d0c2 100644 --- a/src/test/bound.rs +++ b/src/test/bound.rs @@ -111,6 +111,31 @@ fn where_() -> Result<()> { ) } +#[test] +fn for_lifetime() -> Result<()> { + test_derive( + quote! { + #[derive_where(Clone; for<'a> T)] + struct Test(T, std::marker::PhantomData) where T: std::fmt::Debug; + }, + quote! { + #[automatically_derived] + impl ::core::clone::Clone for Test + where + T: std::fmt::Debug, + for<'a> T: ::core::clone::Clone + { + #[inline] + fn clone(&self) -> Self { + match self { + Test(ref __field_0, ref __field_1) => Test(::core::clone::Clone::clone(__field_0), ::core::clone::Clone::clone(__field_1)), + } + } + } + }, + ) +} + #[test] fn associated_type() -> Result<()> { test_derive( From fe47a519b2dfc86dfcbabb0210e615512189f3d6 Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:09:46 -0500 Subject: [PATCH 2/6] Update src/attr/item.rs Co-authored-by: daxpedda --- src/attr/item.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/attr/item.rs b/src/attr/item.rs index 94418d7..2607411 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -300,6 +300,7 @@ impl DeriveWhere { /// bindings. #[derive(Eq, PartialEq)] pub struct GenericNoBound(Option, Type); + impl Parse for GenericNoBound { fn parse(input: ParseStream) -> Result { Ok(Self(input.parse()?, input.parse()?)) From 55572a5123a4399a6f9716990262fa3d204de8dd Mon Sep 17 00:00:00 2001 From: Sammy Harris Date: Mon, 14 Apr 2025 16:35:03 -0500 Subject: [PATCH 3/6] Named fields on GenericNoBound --- src/attr/item.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/attr/item.rs b/src/attr/item.rs index 2607411..0db8143 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -241,10 +241,10 @@ impl DeriveWhere { /// Returns `true` if the given generic type parameter if present. pub fn has_type_param(&self, type_param: &Ident) -> bool { self.generics.iter().any(|generic| match generic { - Generic::NoBound(GenericNoBound( - _lifetimes, - Type::Path(TypePath { qself: None, path }), - )) => { + Generic::NoBound(GenericNoBound { + lifetimes: _, + ty: Type::Path(TypePath { qself: None, path }), + }) => { if let Some(ident) = path.get_ident() { ident == type_param } else { @@ -284,9 +284,12 @@ impl DeriveWhere { .predicates .push(WherePredicate::Type(match generic { Generic::CustomBound(type_bound) => type_bound.clone(), - Generic::NoBound(GenericNoBound(lifetimes, path)) => PredicateType { - lifetimes: lifetimes.clone(), - bounded_ty: path.clone(), + Generic::NoBound(GenericNoBound { + lifetimes: bound_lifetimes, + ty, + }) => PredicateType { + lifetimes: bound_lifetimes.clone(), + bounded_ty: ty.clone(), colon_token: ::default(), bounds: trait_.where_bounds(item), }, @@ -299,11 +302,19 @@ impl DeriveWhere { /// Holds the first part of a [`PredicateType`] prior to the `:`. Optionally contains lifetime `for` /// bindings. #[derive(Eq, PartialEq)] -pub struct GenericNoBound(Option, Type); +pub struct GenericNoBound { + /// Any `for<'a, 'b, 'etc>` bindings for the type. + lifetimes: Option, + /// The type bound to the [`DeriveTrait`]. + ty: Type, +} impl Parse for GenericNoBound { fn parse(input: ParseStream) -> Result { - Ok(Self(input.parse()?, input.parse()?)) + Ok(Self { + lifetimes: input.parse()?, + ty: input.parse()?, + }) } } From d73a92708fea21e8df34d9da179fe64f81fd4eab Mon Sep 17 00:00:00 2001 From: Sammy Harris Date: Mon, 14 Apr 2025 16:57:05 -0500 Subject: [PATCH 4/6] formatting --- src/attr/item.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/attr/item.rs b/src/attr/item.rs index 0db8143..c021be0 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -299,8 +299,8 @@ impl DeriveWhere { } } -/// Holds the first part of a [`PredicateType`] prior to the `:`. Optionally contains lifetime `for` -/// bindings. +/// Holds the first part of a [`PredicateType`] prior to the `:`. Optionally +/// contains lifetime `for` bindings. #[derive(Eq, PartialEq)] pub struct GenericNoBound { /// Any `for<'a, 'b, 'etc>` bindings for the type. @@ -318,12 +318,14 @@ impl Parse for GenericNoBound { } } -/// Holds a single generic [type](GenericNoBound) with optional lifetime bounds or [type with bound](PredicateType). +/// Holds a single generic [type](GenericNoBound) with optional lifetime bounds +/// or [type with bound](PredicateType). #[derive(Eq, PartialEq)] pub enum Generic { /// Generic type with custom [specified bounds](PredicateType). CustomBound(PredicateType), - /// Generic [type](GenericNoBound) which will be bound to the [`DeriveTrait`]. + /// Generic [type](GenericNoBound) which will be bound to the + /// [`DeriveTrait`]. NoBound(GenericNoBound), } @@ -332,8 +334,8 @@ impl Parse for Generic { let fork = input.fork(); // Try to parse input as a `WherePredicate`. The problem is, both expressions - // start with an optional lifetime for bound and then Type, so starting with the `WherePredicate` is the easiest way - // of differentiating them. + // start with an optional lifetime for bound and then Type, so starting with the + // `WherePredicate` is the easiest way of differentiating them. if let Ok(where_predicate) = WherePredicate::parse(&fork) { input.advance_to(&fork); From 122b6986f536b4dbdbd154554a66ddc32d62533a Mon Sep 17 00:00:00 2001 From: Sammy Harris Date: Mon, 14 Apr 2025 17:01:35 -0500 Subject: [PATCH 5/6] add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24e1455..ee27feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Allow lifetime `for<'a, ...>` bounds in non-bounded generic parameters. ### Changed - Use the `Copy` implementation for `Clone` and the `Ord` implementation for From c882e1c2b3bf654ae06dc6f28c25fea6d11935f6 Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:17:13 -0500 Subject: [PATCH 6/6] Update CHANGELOG.md Co-authored-by: daxpedda --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee27feb..9e1ec24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + ### Added - Allow lifetime `for<'a, ...>` bounds in non-bounded generic parameters.