Skip to content

Commit df84c71

Browse files
committed
fix(dir-structure): add needed bounds on derived impls
1 parent 4fc13df commit df84c71

5 files changed

Lines changed: 115 additions & 32 deletions

File tree

nix-flake/flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
mandoc
3434
openssl
3535
tree
36+
curl
3637
];
3738

3839
env = {

src/dev/dir-structure-macros/src/dir_structure.rs

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use std::collections::HashSet;
2+
13
use proc_macro2::Ident;
24
use proc_macro2::TokenStream;
35
use quote::format_ident;
46
use quote::quote;
57
use syn::Field;
68
use syn::ImplGenerics;
79
use syn::ItemStruct;
10+
use syn::WherePredicate;
811
use syn::parse_quote;
912

1013
use crate::dir_structure_core::DirStructureCoreInfo;
@@ -13,7 +16,9 @@ use crate::dir_structure_core::compile_attrs;
1316

1417
struct DirStructureForField {
1518
read_code: TokenStream,
19+
read_bounds: Vec<WherePredicate>,
1620
write_code: TokenStream,
21+
write_bounds: Vec<WherePredicate>,
1722
}
1823

1924
fn expand_dir_structure_for_field(
@@ -50,48 +55,89 @@ fn expand_dir_structure_for_field(
5055
PathSpec::SelfPath => quote! { #path_param_name },
5156
};
5257
let actual_field_ty_perform = with_newtype.as_ref().unwrap_or(field_ty);
53-
let read_code = if self_path {
58+
let (read_code, read_bounds) = if self_path {
5459
// self_path field, just use the path directly
55-
quote! {
56-
#field_ty::from(::dir_structure::traits::vfs::PathType::owned(#path_param_name))
57-
}
60+
(
61+
quote! {
62+
#field_ty::from(::dir_structure::traits::vfs::PathType::owned(#path_param_name))
63+
},
64+
vec![parse_quote! {
65+
#field_ty: ::std::convert::From<<<Vfs as ::dir_structure::traits::vfs::VfsCore>::Path as ::dir_structure::traits::vfs::PathType>::OwnedPath>
66+
}],
67+
)
5868
} else {
5969
let value_name = format_ident!("__value");
60-
let end_expr = match &with_newtype {
61-
Some(nt) => quote! {
62-
<#nt as ::dir_structure::traits::sync::NewtypeToInner>::into_inner(#value_name)
63-
},
64-
None => quote! {
65-
#value_name
66-
},
70+
let (end_expr, read_bounds) = match &with_newtype {
71+
Some(nt) => (
72+
quote! {
73+
<#nt as ::dir_structure::traits::sync::NewtypeToInner>::into_inner(#value_name)
74+
},
75+
vec![
76+
parse_quote! {
77+
#nt: ::dir_structure::traits::sync::NewtypeToInner<Inner = #field_ty>
78+
},
79+
parse_quote! {
80+
#nt: ::dir_structure::traits::sync::ReadFrom<'vfs, Vfs>
81+
},
82+
],
83+
),
84+
None => (
85+
quote! {
86+
#value_name
87+
},
88+
vec![parse_quote! {
89+
#field_ty: ::dir_structure::traits::sync::ReadFrom<'vfs, Vfs>
90+
}],
91+
),
6792
};
6893

69-
quote! {{
70-
let #value_name = <#actual_field_ty_perform as ::dir_structure::traits::sync::ReadFrom<Vfs>>::read_from(#actual_path_expr, #vfs_param_name)?;
71-
#end_expr
72-
}}
94+
(
95+
quote! {{
96+
let #value_name = <#actual_field_ty_perform as ::dir_structure::traits::sync::ReadFrom<Vfs>>::read_from(#actual_path_expr, #vfs_param_name)?;
97+
#end_expr
98+
}},
99+
read_bounds,
100+
)
73101
};
74102

75-
let write_code = if self_path {
103+
let (write_code, write_bounds) = if self_path {
76104
// self_path does not need to write anything
77-
quote! {}
105+
(quote! {}, vec![])
78106
} else {
79-
let writer = match &with_newtype {
80-
Some(nt) => {
81-
quote! { &<#nt as ::dir_structure::traits::sync::FromRefForWriter<'_, '_, Vfs>>::from_ref_for_writer(&self.#field_name) }
82-
}
83-
None => quote! { &self.#field_name },
107+
let (writer, write_bounds) = match &with_newtype {
108+
Some(nt) => (
109+
quote! { &<#nt as ::dir_structure::traits::sync::FromRefForWriter<'_, '_, Vfs>>::from_ref_for_writer(&self.#field_name) },
110+
vec![
111+
parse_quote! {
112+
for<'a> #nt: ::dir_structure::traits::sync::FromRefForWriter<'a, 'vfs, Vfs, Inner = #field_ty>
113+
},
114+
parse_quote! {
115+
#nt: ::dir_structure::traits::sync::WriteTo<'vfs, Vfs>
116+
},
117+
],
118+
),
119+
None => (
120+
quote! { &self.#field_name },
121+
vec![parse_quote! {
122+
#field_ty: ::dir_structure::traits::sync::WriteTo<'vfs, Vfs>
123+
}],
124+
),
84125
};
85-
quote! {
86-
::dir_structure::traits::sync::WriteTo::write_to(#writer, #actual_path_expr, #vfs_param_name)?;
87-
}
126+
(
127+
quote! {
128+
::dir_structure::traits::sync::WriteTo::write_to(#writer, #actual_path_expr, #vfs_param_name)?;
129+
},
130+
write_bounds,
131+
)
88132
};
89133

90134
Ok(DirStructureForField {
91135
read_code: quote! {
92136
#field_name: #read_code
93137
},
138+
read_bounds,
94139
write_code,
140+
write_bounds,
95141
})
96142
}
97143

@@ -174,11 +220,15 @@ pub fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
174220

175221
let mut field_read_impls = Vec::new();
176222
let mut field_write_impls = Vec::new();
223+
let mut read_bounds_b = HashSet::new();
224+
let mut write_bounds_b = HashSet::new();
177225

178226
for field in &st.fields {
179227
let DirStructureForField {
180228
read_code,
229+
read_bounds,
181230
write_code,
231+
write_bounds,
182232
} = expand_dir_structure_for_field(
183233
(&impl_generics, name, &ty_generics, where_clause),
184234
&path_param_name,
@@ -187,12 +237,17 @@ pub fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
187237
)?;
188238
field_read_impls.push(read_code);
189239
field_write_impls.push(write_code);
240+
241+
read_bounds_b.extend(read_bounds);
242+
write_bounds_b.extend(write_bounds);
190243
}
191244

192-
#[cfg_attr(not(feature = "resolve-path"), expect(unused_mut))]
193-
let mut expanded = quote! {
245+
let where_read_clause = merge_where_clauses(where_clause, read_bounds_b);
246+
let where_write_clause = merge_where_clauses(where_clause, write_bounds_b);
247+
248+
let expanded = quote! {
194249
#[automatically_derived]
195-
impl #read_impl_generics ::dir_structure::traits::sync::ReadFrom<'vfs, Vfs> for #name #ty_generics #where_clause {
250+
impl #read_impl_generics ::dir_structure::traits::sync::ReadFrom<'vfs, Vfs> for #name #ty_generics #where_read_clause {
196251
fn read_from(#path_param_name: &Vfs::Path, #vfs_param_name: ::std::pin::Pin<&'vfs Vfs>) -> ::dir_structure::error::Result<Self, <Vfs::Path as ::dir_structure::traits::vfs::PathType>::OwnedPath>
197252
where
198253
Self: Sized,
@@ -203,7 +258,7 @@ pub fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
203258
}
204259
}
205260
#[automatically_derived]
206-
impl #write_impl_generics ::dir_structure::traits::sync::WriteTo<'vfs, Vfs> for #name #ty_generics #where_clause {
261+
impl #write_impl_generics ::dir_structure::traits::sync::WriteTo<'vfs, Vfs> for #name #ty_generics #where_write_clause {
207262
fn write_to(&self, #path_param_name: &Vfs::Path, #vfs_param_name: ::std::pin::Pin<&'vfs Vfs>) -> ::dir_structure::error::Result<(), <Vfs::Path as ::dir_structure::traits::vfs::PathType>::OwnedPath> {
208263
#(#field_write_impls)*
209264
Ok(())
@@ -215,3 +270,26 @@ pub fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
215270

216271
Ok(expanded)
217272
}
273+
274+
fn merge_where_clauses(
275+
original: Option<&syn::WhereClause>,
276+
additional: HashSet<WherePredicate>,
277+
) -> Option<syn::WhereClause> {
278+
if original.is_none() && additional.is_empty() {
279+
return None;
280+
}
281+
let mut predicates = if let Some(wc) = original {
282+
wc.predicates.clone()
283+
} else {
284+
syn::punctuated::Punctuated::new()
285+
};
286+
for p in additional {
287+
if !predicates.iter().any(|q| q == &p) {
288+
predicates.push(p);
289+
}
290+
}
291+
Some(syn::WhereClause {
292+
where_token: Default::default(),
293+
predicates,
294+
})
295+
}

src/dev/dir-structure/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Changed the image async pipeline to use the new traits `ReadImageFromAsync`, `WriteImageToAsync`, and `WriteImageToAsyncRef`.
66
See the documentation in the [`async_vcs`](src/traits/async_vfs.rs) module for more details.
77

8+
- Added bounds for derived implementations of `ReadFrom` and `WriteTo` via the `DirStructure` derive macro.
9+
810
# `0.2.0-rc.3`
911

1012
Released: 2025-09-19

src/dev/dir-structure/src/traits/asy.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::traits::vfs::VfsCore;
1212

1313
/// Trait for types / structures that can be read from disk asynchronously.
1414
///
15-
/// `async` version of [`ReadFrom`].
15+
/// `async` version of [`ReadFrom`](crate::traits::sync::ReadFrom).
1616
#[cfg(feature = "async")]
1717
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
1818
pub trait ReadFromAsync<'a, Vfs: VfsAsync + ?Sized + 'a>: Sized {
@@ -57,6 +57,8 @@ pub trait WriteToAsync<'a, Vfs: WriteSupportingVfsAsync + ?Sized + 'a> {
5757
///
5858
/// The difference between this and [`WriteToAsync`] is that this trait takes in
5959
/// a reference instead of owned data.
60+
///
61+
/// This is an async equivalent of [`WriteTo`](crate::traits::sync::WriteTo).
6062
#[cfg(feature = "async")]
6163
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
6264
pub trait WriteToAsyncRef<'r, Vfs: WriteSupportingVfsAsync + ?Sized + 'r> {
@@ -80,7 +82,7 @@ pub trait WriteToAsyncRef<'r, Vfs: WriteSupportingVfsAsync + ?Sized + 'r> {
8082
'r: 'a;
8183
}
8284

83-
/// Async equivalent of [`FromRefForWriter`](crate::FromRefForWriter).
85+
/// Async equivalent of [`FromRefForWriter`](crate::traits::sync::FromRefForWriter).
8486
#[cfg(feature = "async")]
8587
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
8688
pub trait FromRefForWriterAsync<'a, Vfs: WriteSupportingVfsAsync + ?Sized + 'a> {

src/dev/dir-structure/src/traits/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Traits for resolving paths in a directory structure.
22
//!
3-
//! [`HasField`] is automatically derived by the `#[derive(DirStructure)]` macro.
3+
//! [`HasField`] is automatically derived by the [`#[derive(HasField)]` macro](crate::HasField).
44
55
#[cfg(feature = "resolve-path")]
66
use crate::traits::vfs::OwnedPathType;

0 commit comments

Comments
 (0)