Skip to content

Commit c3c6291

Browse files
authored
feat(stackable-versioned): Add conditional allow attr generation (#876)
* feat(stackable-versioned): Add conditional allow attr generation * feat: Add allow attr to enum, carry over deprecated attr
1 parent 7ba94b5 commit c3c6291

File tree

10 files changed

+188
-44
lines changed

10 files changed

+188
-44
lines changed

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,23 @@ pub(crate) trait Neighbors<K, V>
44
where
55
K: Ord + Eq,
66
{
7+
/// Returns the values of keys which are neighbors of `key`.
8+
///
9+
/// Given a map which contains the following keys: 1, 3, 5. Calling this
10+
/// function with these keys, results in the following return values:
11+
///
12+
/// - Key **0**: `(None, Some(1))`
13+
/// - Key **2**: `(Some(1), Some(3))`
14+
/// - Key **4**: `(Some(3), Some(5))`
15+
/// - Key **6**: `(Some(5), None)`
716
fn get_neighbors(&self, key: &K) -> (Option<&V>, Option<&V>);
817

18+
/// Returns whether the function `f` returns true if applied to the value
19+
/// identified by `key`.
20+
fn value_is<F>(&self, key: &K, f: F) -> bool
21+
where
22+
F: Fn(&V) -> bool;
23+
924
fn lo_bound(&self, bound: Bound<&K>) -> Option<(&K, &V)>;
1025
fn up_bound(&self, bound: Bound<&K>) -> Option<(&K, &V)>;
1126
}
@@ -14,15 +29,6 @@ impl<K, V> Neighbors<K, V> for BTreeMap<K, V>
1429
where
1530
K: Ord + Eq,
1631
{
17-
/// Returns the values of keys which are neighbors of `key`.
18-
///
19-
/// Given a map which contains the following keys: 1, 3, 5. Calling this
20-
/// function with these keys, results in the following return values:
21-
///
22-
/// - Key **0**: `(None, Some(1))`
23-
/// - Key **2**: `(Some(1), Some(3))`
24-
/// - Key **4**: `(Some(3), Some(5))`
25-
/// - Key **6**: `(Some(5), None)`
2632
fn get_neighbors(&self, key: &K) -> (Option<&V>, Option<&V>) {
2733
// NOTE (@Techassi): These functions might get added to the standard
2834
// library at some point. If that's the case, we can use the ones
@@ -51,6 +57,13 @@ where
5157
}
5258
}
5359

60+
fn value_is<F>(&self, key: &K, f: F) -> bool
61+
where
62+
F: Fn(&V) -> bool,
63+
{
64+
self.get(key).map_or(false, f)
65+
}
66+
5467
fn lo_bound(&self, bound: Bound<&K>) -> Option<(&K, &V)> {
5568
self.range((Bound::Unbounded, bound)).next_back()
5669
}

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

+32-10
Original file line numberDiff line numberDiff line change
@@ -303,58 +303,79 @@ where
303303
} => chain.insert(
304304
version.inner,
305305
ItemStatus::NoChange {
306+
previously_deprecated: false,
306307
ident: from_ident.clone(),
307308
ty: from_type.clone(),
308309
},
309310
),
310311
ItemStatus::Deprecation { previous_ident, .. } => chain.insert(
311312
version.inner,
312313
ItemStatus::NoChange {
314+
previously_deprecated: false,
313315
ident: previous_ident.clone(),
314316
ty: self.inner.ty(),
315317
},
316318
),
317-
ItemStatus::NoChange { ident, ty } => chain.insert(
319+
ItemStatus::NoChange {
320+
previously_deprecated,
321+
ident,
322+
ty,
323+
} => chain.insert(
318324
version.inner,
319325
ItemStatus::NoChange {
326+
previously_deprecated: *previously_deprecated,
320327
ident: ident.clone(),
321328
ty: ty.clone(),
322329
},
323330
),
324331
ItemStatus::NotPresent => unreachable!(),
325332
},
326333
(Some(status), None) => {
327-
let (ident, ty) = match status {
328-
ItemStatus::Addition { ident, ty, .. } => (ident, ty),
334+
let (ident, ty, previously_deprecated) = match status {
335+
ItemStatus::Addition { ident, ty, .. } => (ident, ty, false),
329336
ItemStatus::Change {
330337
to_ident, to_type, ..
331-
} => (to_ident, to_type),
332-
ItemStatus::Deprecation { ident, .. } => (ident, &self.inner.ty()),
333-
ItemStatus::NoChange { ident, ty } => (ident, ty),
338+
} => (to_ident, to_type, false),
339+
ItemStatus::Deprecation { ident, .. } => {
340+
(ident, &self.inner.ty(), true)
341+
}
342+
ItemStatus::NoChange {
343+
previously_deprecated,
344+
ident,
345+
ty,
346+
..
347+
} => (ident, ty, *previously_deprecated),
334348
ItemStatus::NotPresent => unreachable!(),
335349
};
336350

337351
chain.insert(
338352
version.inner,
339353
ItemStatus::NoChange {
354+
previously_deprecated,
340355
ident: ident.clone(),
341356
ty: ty.clone(),
342357
},
343358
)
344359
}
345360
(Some(status), Some(_)) => {
346-
let (ident, ty) = match status {
347-
ItemStatus::Addition { ident, ty, .. } => (ident, ty),
361+
let (ident, ty, previously_deprecated) = match status {
362+
ItemStatus::Addition { ident, ty, .. } => (ident, ty, false),
348363
ItemStatus::Change {
349364
to_ident, to_type, ..
350-
} => (to_ident, to_type),
351-
ItemStatus::NoChange { ident, ty, .. } => (ident, ty),
365+
} => (to_ident, to_type, false),
366+
ItemStatus::NoChange {
367+
previously_deprecated,
368+
ident,
369+
ty,
370+
..
371+
} => (ident, ty, *previously_deprecated),
352372
_ => unreachable!(),
353373
};
354374

355375
chain.insert(
356376
version.inner,
357377
ItemStatus::NoChange {
378+
previously_deprecated,
358379
ident: ident.clone(),
359380
ty: ty.clone(),
360381
},
@@ -398,6 +419,7 @@ pub(crate) enum ItemStatus {
398419
ident: Ident,
399420
},
400421
NoChange {
422+
previously_deprecated: bool,
401423
ident: Ident,
402424
ty: Type,
403425
},

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

+38-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use syn::{DataEnum, Error};
88
use crate::{
99
attrs::common::ContainerAttributes,
1010
codegen::{
11-
common::{Container, ContainerInput, ContainerVersion, Item, VersionedContainer},
11+
chain::Neighbors,
12+
common::{
13+
Container, ContainerInput, ContainerVersion, Item, ItemStatus, VersionedContainer,
14+
},
1215
venum::variant::VersionedVariant,
1316
},
1417
};
@@ -193,11 +196,18 @@ impl VersionedEnum {
193196
))
194197
}
195198

196-
// TODO (@Techassi): Be a little bit more clever about when to include
197-
// the #[allow(deprecated)] attribute.
199+
// Include allow(deprecated) only when this or the next version is
200+
// deprecated. Also include it, when a variant in this or the next
201+
// version is deprecated.
202+
let allow_attribute = (version.deprecated
203+
|| next_version.deprecated
204+
|| self.is_any_variant_deprecated(version)
205+
|| self.is_any_variant_deprecated(next_version))
206+
.then_some(quote! { #[allow(deprecated)] });
207+
198208
return quote! {
199209
#[automatically_derived]
200-
#[allow(deprecated)]
210+
#allow_attribute
201211
impl From<#module_name::#enum_ident> for #next_module_name::#enum_ident {
202212
fn from(#from_ident: #module_name::#enum_ident) -> Self {
203213
match #from_ident {
@@ -210,4 +220,28 @@ impl VersionedEnum {
210220

211221
quote! {}
212222
}
223+
224+
/// Returns whether any field is deprecated in the provided
225+
/// [`ContainerVersion`].
226+
fn is_any_variant_deprecated(&self, version: &ContainerVersion) -> bool {
227+
// First, iterate over all fields. Any will return true if any of the
228+
// function invocations return true. If a field doesn't have a chain,
229+
// we can safely default to false (unversioned fields cannot be
230+
// deprecated). Then we retrieve the status of the field and ensure it
231+
// is deprecated.
232+
self.items.iter().any(|f| {
233+
f.chain.as_ref().map_or(false, |c| {
234+
c.value_is(&version.inner, |a| {
235+
matches!(
236+
a,
237+
ItemStatus::Deprecation { .. }
238+
| ItemStatus::NoChange {
239+
previously_deprecated: true,
240+
..
241+
}
242+
)
243+
})
244+
})
245+
})
246+
}
213247
}

crates/stackable-versioned-macros/src/codegen/venum/variant.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,21 @@ impl VersionedVariant {
142142
#ident,
143143
})
144144
}
145-
ItemStatus::NoChange { ident, .. } => Some(quote! {
146-
#(#original_attributes)*
147-
#ident,
148-
}),
145+
ItemStatus::NoChange {
146+
previously_deprecated,
147+
ident,
148+
..
149+
} => {
150+
// TODO (@Techassi): Also carry along the deprecation
151+
// note.
152+
let deprecated_attr = previously_deprecated.then(|| quote! {#[deprecated]});
153+
154+
Some(quote! {
155+
#(#original_attributes)*
156+
#deprecated_attr
157+
#ident,
158+
})
159+
}
149160
ItemStatus::NotPresent => None,
150161
},
151162
None => {

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

+16-4
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,22 @@ impl VersionedField {
157157
})
158158
}
159159
ItemStatus::NotPresent => None,
160-
ItemStatus::NoChange { ident, ty } => Some(quote! {
161-
#(#original_attributes)*
162-
pub #ident: #ty,
163-
}),
160+
ItemStatus::NoChange {
161+
previously_deprecated,
162+
ident,
163+
ty,
164+
..
165+
} => {
166+
// TODO (@Techassi): Also carry along the deprecation
167+
// note.
168+
let deprecated_attr = previously_deprecated.then(|| quote! {#[deprecated]});
169+
170+
Some(quote! {
171+
#(#original_attributes)*
172+
#deprecated_attr
173+
pub #ident: #ty,
174+
})
175+
}
164176
}
165177
}
166178
None => {

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

+37-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ use syn::{parse_quote, DataStruct, Error, Ident};
88
use crate::{
99
attrs::common::ContainerAttributes,
1010
codegen::{
11+
chain::Neighbors,
1112
common::{
12-
Container, ContainerInput, ContainerVersion, Item, VersionExt, VersionedContainer,
13+
Container, ContainerInput, ContainerVersion, Item, ItemStatus, VersionExt,
14+
VersionedContainer,
1315
},
1416
vstruct::field::VersionedField,
1517
},
@@ -241,11 +243,18 @@ impl VersionedStruct {
241243

242244
let fields = self.generate_from_fields(version, next_version, from_ident);
243245

244-
// TODO (@Techassi): Be a little bit more clever about when to include
245-
// the #[allow(deprecated)] attribute.
246+
// Include allow(deprecated) only when this or the next version is
247+
// deprecated. Also include it, when a field in this or the next
248+
// version is deprecated.
249+
let allow_attribute = (version.deprecated
250+
|| next_version.deprecated
251+
|| self.is_any_field_deprecated(version)
252+
|| self.is_any_field_deprecated(next_version))
253+
.then_some(quote! { #[allow(deprecated)] });
254+
246255
return Some(quote! {
247256
#[automatically_derived]
248-
#[allow(deprecated)]
257+
#allow_attribute
249258
impl From<#module_name::#struct_ident> for #next_module_name::#struct_ident {
250259
fn from(#from_ident: #module_name::#struct_ident) -> Self {
251260
Self {
@@ -275,6 +284,30 @@ impl VersionedStruct {
275284

276285
token_stream
277286
}
287+
288+
/// Returns whether any field is deprecated in the provided
289+
/// [`ContainerVersion`].
290+
fn is_any_field_deprecated(&self, version: &ContainerVersion) -> bool {
291+
// First, iterate over all fields. Any will return true if any of the
292+
// function invocations return true. If a field doesn't have a chain,
293+
// we can safely default to false (unversioned fields cannot be
294+
// deprecated). Then we retrieve the status of the field and ensure it
295+
// is deprecated.
296+
self.items.iter().any(|f| {
297+
f.chain.as_ref().map_or(false, |c| {
298+
c.value_is(&version.inner, |a| {
299+
matches!(
300+
a,
301+
ItemStatus::Deprecation { .. }
302+
| ItemStatus::NoChange {
303+
previously_deprecated: true,
304+
..
305+
}
306+
)
307+
})
308+
})
309+
})
310+
}
278311
}
279312

280313
// Kubernetes specific code generation

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ pub struct FooSpec {
456456
}
457457
458458
# fn main() {
459-
let merged_crd = Foo::merged_crd("v1").unwrap();
459+
let merged_crd = Foo::merged_crd(Foo::V1).unwrap();
460460
println!("{}", serde_yaml::to_string(&merged_crd).unwrap());
461461
# }
462462
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use stackable_versioned_macros::versioned;
2+
3+
fn main() {
4+
#[versioned(
5+
version(name = "v1alpha1"),
6+
version(name = "v1beta1"),
7+
version(name = "v1"),
8+
version(name = "v2"),
9+
version(name = "v3")
10+
)]
11+
enum Foo {
12+
#[versioned(deprecated(since = "v1"))]
13+
DeprecatedBar,
14+
Baz,
15+
}
16+
}

crates/stackable-versioned-macros/tests/default/pass/deprecate.rs renamed to crates/stackable-versioned-macros/tests/default/pass/deprecate_struct.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ fn main() {
44
#[versioned(
55
version(name = "v1alpha1"),
66
version(name = "v1beta1"),
7-
version(name = "v1")
7+
version(name = "v1"),
8+
version(name = "v2"),
9+
version(name = "v3")
810
)]
911
struct Foo {
10-
#[versioned(deprecated(since = "v1beta1", note = "gone"))]
12+
#[versioned(deprecated(since = "v1", note = "gone"))]
1113
deprecated_bar: usize,
1214
baz: bool,
1315
}

crates/stackable-versioned-macros/tests/trybuild.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
#[allow(dead_code)]
1818
mod default {
1919
// mod pass {
20-
// mod attributes_enum;
21-
// mod attributes_struct;
22-
// mod basic;
20+
// // mod attributes_enum;
21+
// // mod attributes_struct;
22+
// // mod basic;
2323

24-
// mod deprecate;
25-
// mod rename;
26-
// mod skip_from_version;
24+
// // mod deprecate_enum;
25+
// // mod deprecate_struct;
26+
// // mod rename;
27+
// // mod skip_from_version;
2728
// }
2829

2930
// mod fail {

0 commit comments

Comments
 (0)