Skip to content

Commit 15faafd

Browse files
authored
feat(stackable-versioned): Add convert_with arg in changed action (#967)
* feat(stackable-versioned): Add convert_with arg in changed action * test(docs): Add ignore keyword to expanded example code
1 parent 260115b commit 15faafd

File tree

6 files changed

+154
-12
lines changed

6 files changed

+154
-12
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#[versioned(version(name = "v1alpha1"), version(name = "v1"), version(name = "v2"))]
2+
// ---
3+
struct Foo {
4+
#[versioned(
5+
// This tests two additional things:
6+
// - that both unquoted and quoted usage works
7+
// - that the renamed name does get picked up correctly by the conversion function
8+
changed(since = "v1", from_type = "u16", from_name = "bar", convert_with = u16_to_u32),
9+
changed(since = "v2", from_type = "u32", convert_with = "u32_to_u64")
10+
)]
11+
baz: u64,
12+
}

crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@convert_with.rs.snap

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stackable-versioned-macros/src/attrs/item/mod.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ impl CommonItemAttributes {
237237
);
238238
}
239239
}
240+
241+
// The convert_with argument only makes sense to use when the
242+
// type changed
243+
if let Some(convert_func) = change.convert_with.as_ref() {
244+
if change.from_type.is_none() {
245+
errors.push(
246+
Error::custom(
247+
"the `convert_with` argument must be used in combination with `from_type`",
248+
)
249+
.with_span(&convert_func.span()),
250+
);
251+
}
252+
}
240253
}
241254

242255
errors.finish()
@@ -307,9 +320,10 @@ impl CommonItemAttributes {
307320
actions.insert(
308321
*change.since,
309322
ItemStatus::Change {
323+
convert_with: change.convert_with.as_deref().cloned(),
310324
from_ident: from_ident.clone(),
311-
to_ident: ident,
312325
from_type: from_ty.clone(),
326+
to_ident: ident,
313327
to_type: ty,
314328
},
315329
);
@@ -356,9 +370,10 @@ impl CommonItemAttributes {
356370
actions.insert(
357371
*change.since,
358372
ItemStatus::Change {
373+
convert_with: change.convert_with.as_deref().cloned(),
359374
from_ident: from_ident.clone(),
360-
to_ident: ident,
361375
from_type: from_ty.clone(),
376+
to_ident: ident,
362377
to_type: ty,
363378
},
364379
);
@@ -431,12 +446,14 @@ fn default_default_fn() -> SpannedValue<Path> {
431446
///
432447
/// Example usage:
433448
/// - `changed(since = "...", from_name = "...")`
434-
/// - `changed(since = "...", from_name = "..." from_type="...")`
449+
/// - `changed(since = "...", from_name = "...", from_type="...")`
450+
/// - `changed(since = "...", from_name = "...", from_type="...", convert_with = "...")`
435451
#[derive(Clone, Debug, FromMeta)]
436-
pub(crate) struct ChangedAttributes {
437-
pub(crate) since: SpannedValue<Version>,
438-
pub(crate) from_name: Option<SpannedValue<String>>,
439-
pub(crate) from_type: Option<SpannedValue<Type>>,
452+
pub struct ChangedAttributes {
453+
pub since: SpannedValue<Version>,
454+
pub from_name: Option<SpannedValue<String>>,
455+
pub from_type: Option<SpannedValue<Type>>,
456+
pub convert_with: Option<SpannedValue<Path>>,
440457
}
441458

442459
/// For the deprecated() action

crates/stackable-versioned-macros/src/codegen/item/field.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,29 @@ impl VersionedField {
167167
_,
168168
ItemStatus::Change {
169169
from_ident: old_field_ident,
170+
convert_with,
170171
to_ident,
171172
..
172173
},
173-
) => {
174-
quote! {
174+
) => match convert_with {
175+
// The user specified a custom conversion function which
176+
// will be used here instead of the default .into() call
177+
// which utilizes From impls.
178+
Some(convert_fn) => quote! {
179+
#to_ident: #convert_fn(#from_struct_ident.#old_field_ident),
180+
},
181+
// Default .into() call using From impls.
182+
None => quote! {
175183
#to_ident: #from_struct_ident.#old_field_ident.into(),
176-
}
177-
}
184+
},
185+
},
178186
(old, next) => {
179187
let next_field_ident = next.get_ident();
180188
let old_field_ident = old.get_ident();
181189

190+
// NOTE (@Techassi): Do we really need .into() here. I'm
191+
// currently not sure why it is there and if it is needed
192+
// in some edge cases.
182193
quote! {
183194
#next_field_ident: #from_struct_ident.#old_field_ident.into(),
184195
}

crates/stackable-versioned-macros/src/codegen/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl From<&ModuleAttributes> for Vec<VersionDefinition> {
7272
}
7373

7474
#[derive(Debug, PartialEq)]
75-
pub(crate) enum ItemStatus {
75+
pub enum ItemStatus {
7676
Addition {
7777
ident: IdentString,
7878
default_fn: Path,
@@ -81,6 +81,7 @@ pub(crate) enum ItemStatus {
8181
ty: Type,
8282
},
8383
Change {
84+
convert_with: Option<Path>,
8485
from_ident: IdentString,
8586
to_ident: IdentString,
8687
from_type: Type,

crates/stackable-versioned-macros/src/lib.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,10 @@ mod utils;
400400
/// - `since` to indicate since which version the item is changed.
401401
/// - `from_name` to indicate from which previous name the field is renamed.
402402
/// - `from_type` to indicate from which previous type the field is changed.
403+
/// - `convert_with` to provide a custom conversion function instead of using
404+
/// a [`From`] implementation by default. This argument can only be used in
405+
/// combination with the `from_type` argument. The expected function signature
406+
/// is: `fn (OLD_TYPE) -> NEW_TYPE`. This function must not fail.
403407
///
404408
/// ```
405409
/// # use stackable_versioned_macros::versioned;
@@ -521,6 +525,61 @@ mod utils;
521525
/// This automatic generation can be skipped to enable a custom implementation
522526
/// for more complex conversions.
523527
///
528+
/// ### Custom conversion function at field level
529+
///
530+
/// As stated in the [`changed()`](#changed-action) section, a custom conversion
531+
/// function can be provided using the `convert_with` argument. A simple example
532+
/// looks like this:
533+
///
534+
/// ```
535+
/// # use stackable_versioned_macros::versioned;
536+
/// #[versioned(
537+
/// version(name = "v1alpha1"),
538+
/// version(name = "v1beta1")
539+
/// )]
540+
/// pub struct Foo {
541+
/// #[versioned(changed(
542+
/// since = "v1beta1",
543+
/// from_type = "u8",
544+
/// convert_with = "u8_to_u16"
545+
/// ))]
546+
/// bar: u16,
547+
/// }
548+
///
549+
/// fn u8_to_u16(old: u8) -> u16 {
550+
/// old as u16
551+
/// }
552+
/// ```
553+
///
554+
/// <details>
555+
/// <summary>Expand Generated Code</summary>
556+
///
557+
/// ```ignore
558+
/// pub mod v1alpha1 {
559+
/// use super::*;
560+
/// pub struct Foo {
561+
/// pub bar: u8,
562+
/// }
563+
/// }
564+
///
565+
/// impl ::std::convert::From<v1alpha1::Foo> for v1beta1::Foo {
566+
/// fn from(__sv_foo: v1alpha1::Foo) -> Self {
567+
/// Self {
568+
/// bar: u8_to_u16(__sv_foo.bar),
569+
/// }
570+
/// }
571+
/// }
572+
///
573+
/// pub mod v1beta1 {
574+
/// use super::*;
575+
/// pub struct Foo {
576+
/// pub bar: u16,
577+
/// }
578+
/// }
579+
/// ```
580+
///
581+
/// </details>
582+
///
524583
/// ### Skipping at the Container Level
525584
///
526585
/// Disabling this behavior at the container level results in no `From`

0 commit comments

Comments
 (0)