Skip to content

Commit bcd4c57

Browse files
committed
Revamp type descriptors
Allows deriving types for user-defined structs and enums
1 parent c61b3c9 commit bcd4c57

36 files changed

+2511
-1992
lines changed

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ jobs:
131131
uses: dtolnay/rust-toolchain@stable
132132
with: {toolchain: '${{matrix.rust}}'}
133133
- name: Build and test
134-
run: cargo test -vv --features static --workspace
134+
run: cargo test -vv --features static --workspace --exclude netcdf-derive
135135

136136
addr_san:
137137
name: Address sanitizer
@@ -147,4 +147,4 @@ jobs:
147147
env:
148148
RUSTFLAGS: "-Z sanitizer=address"
149149
RUSTDOCFLAGS: "-Z sanitizer=address"
150-
run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace
150+
run: cargo test --features netcdf-sys/static --target x86_64-unknown-linux-gnu --workspace --exclude netcdf-derive

.github/workflows/release.yml

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- "netcdf-v*"
88
- "netcdf-sys-v*"
99
- "netcdf-src-v*"
10+
- "netcdf-derive-v*"
1011

1112
env:
1213
CARGO_TERM_COLOR: always
@@ -32,6 +33,9 @@ jobs:
3233
- name: Publish netcdf-sys
3334
if: "${{ startsWith(github.ref_name, 'netcdf-sys-v') }}"
3435
run: cargo publish --package netcdf-sys --token "${{ secrets.CRATES_IO_TOKEN }}"
36+
- name: Publish netcdf-derive
37+
if: "${{ startsWith(github.ref_name, 'netcdf-derive-v') }}"
38+
run: cargo publish --package netcdf --token "${{ secrets.CRATES_IO_TOKEN }}"
3539
- name: Publish netcdf
3640
if: "${{ startsWith(github.ref_name, 'netcdf-v') }}"
3741
run: cargo publish --package netcdf --token "${{ secrets.CRATES_IO_TOKEN }}"

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ members = [
33
"netcdf",
44
"netcdf-sys",
55
"netcdf-src",
6+
"netcdf-derive",
67
]
78
default-members = ["netcdf", "netcdf-sys"]
89
resolver = "2"
@@ -11,4 +12,5 @@ resolver = "2"
1112
netcdf = { path = "netcdf", version = "0.9.0" }
1213
netcdf-sys = { path = "netcdf-sys", version = "0.6.0" }
1314
netcdf-src = { path = "netcdf-src", version = "0.3.0" }
15+
netcdf-derive = { path = "netcdf-derive", version = "0.1.0" }
1416
hdf5-sys = { version = "0.8.0" }

README.md

+2-5
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@ Supported:
2222
* Open/Append/Create modes
2323
* Reading from memory
2424
* Unlimited dimensions
25-
* String variables
26-
* User defined types (variable length, enum, compound, opaque)
25+
* User defined types (enum, compound, other types requires additional work)
2726

28-
Not (yet) supported:
29-
30-
* Nested user defined types
27+
Not (yet) supported (PRs welcome):
3128
* Writing using memory-mapped file
3229

3330
All variable data is read into a contiguous buffer, or into an [ndarray](https://github.com/rust-ndarray/rust-ndarray) if the `ndarray` feature is activated.

netcdf-derive/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.nc

netcdf-derive/Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "netcdf-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = "2.0.48"
11+
quote = "1.0.35"
12+
proc-macro2 = "1.0.78"
13+
proc-macro-error = "1.0.4"
14+
15+
[dev-dependencies]
16+
netcdf = { workspace = true }
17+
trybuild = "1.0.89"

netcdf-derive/src/lib.rs

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use proc_macro2::{Ident, TokenStream};
2+
use proc_macro_error::{abort, proc_macro_error};
3+
use quote::quote;
4+
use syn::{
5+
parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed, LitStr, Type,
6+
Variant,
7+
};
8+
9+
#[proc_macro_derive(NcType, attributes(netcdf))]
10+
/// Derives `NcTypeDescriptor` for user defined types.
11+
///
12+
/// See the documentation under `netcdf::TypeDescriptor` for examples
13+
///
14+
/// Use `#[netcdf(rename = "name")]` to
15+
/// rename field names or enum member names, or the name
16+
/// of the compound/enum.
17+
///
18+
/// Types one derives `NcType` for must have some properties to
19+
/// ensure correctness:
20+
/// * Structs must have `repr(C)` to ensure layout compatibility
21+
/// * Structs must be packed (no padding allowed)
22+
/// * Enums must have `repr(T)` where `T` is an int type (`{i/u}{8/16/32/64}`)
23+
#[proc_macro_error]
24+
pub fn derive(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
25+
let input = parse_macro_input!(stream as DeriveInput);
26+
let name = &input.ident;
27+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
28+
29+
let mut renamed = None;
30+
let mut repr_c = false;
31+
for attr in &input.attrs {
32+
if attr.path().is_ident("netcdf") {
33+
attr.parse_nested_meta(|meta| {
34+
if meta.path.is_ident("rename") {
35+
renamed = Some(meta.value()?.parse::<LitStr>()?.value());
36+
} else {
37+
abort!(meta.path, "NcType encountered an unknown attribute");
38+
}
39+
Ok(())
40+
})
41+
.unwrap();
42+
} else if attr.path().is_ident("repr") {
43+
attr.parse_nested_meta(|meta| {
44+
if meta.path.is_ident("C") {
45+
repr_c = true;
46+
}
47+
Ok(())
48+
})
49+
.unwrap();
50+
}
51+
}
52+
let ncname = renamed.unwrap_or_else(|| name.to_string());
53+
54+
let body = match input.data {
55+
Data::Struct(DataStruct {
56+
struct_token: _,
57+
ref fields,
58+
semi_token: _,
59+
}) => {
60+
if !repr_c {
61+
abort!(
62+
input,
63+
"Can not derive NcType for struct without fixed layout";
64+
help = "struct must have attribute #[repr(C)]"
65+
);
66+
}
67+
match fields {
68+
Fields::Named(fields) => impl_compound(name, &ncname, fields.clone()),
69+
Fields::Unnamed(f) => {
70+
abort!(f, "Can not derive NcType for struct with unnamed field"; note="#[derive(NcType)]")
71+
}
72+
Fields::Unit => abort!(input, "Can not derive NcType for unit struct"),
73+
}
74+
}
75+
Data::Enum(DataEnum {
76+
enum_token: _,
77+
brace_token: _,
78+
ref variants,
79+
}) => {
80+
let mut basetyp = None;
81+
for attr in &input.attrs {
82+
if attr.path().is_ident("repr") {
83+
attr.parse_nested_meta(|meta| {
84+
for item in ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"] {
85+
if meta.path.is_ident(item) {
86+
basetyp = Some(meta.path.get_ident().unwrap().clone());
87+
}
88+
}
89+
Ok(())
90+
})
91+
.unwrap();
92+
}
93+
}
94+
let Some(basetyp) = basetyp else {
95+
abort!(
96+
input,
97+
"Can not derive NcType for enum without suitable repr";
98+
help="Add #[repr(i32)] (or another integer type) as an attribute to the enum"
99+
);
100+
};
101+
impl_enum(/*&name,*/ &ncname, &basetyp, variants.iter())
102+
}
103+
Data::Union(_) => abort!(
104+
input,
105+
"Can not derive NcType for union";
106+
note = "netCDF has no concept of Union type"
107+
),
108+
};
109+
110+
let expanded = quote! {
111+
const _: () = {
112+
use netcdf::types::*;
113+
114+
#[automatically_derived]
115+
unsafe impl #impl_generics NcTypeDescriptor for #name #ty_generics #where_clause {
116+
fn type_descriptor() -> NcVariableType {
117+
#body
118+
}
119+
}
120+
};
121+
};
122+
proc_macro::TokenStream::from(expanded)
123+
}
124+
125+
fn impl_compound(ty: &Ident, ncname: &str, fields: FieldsNamed) -> TokenStream {
126+
struct FieldInfo {
127+
name: String,
128+
typ: Type,
129+
}
130+
let mut items: Vec<FieldInfo> = vec![];
131+
132+
for field in fields.named {
133+
let ident = field.ident.expect("Field must have a name").clone();
134+
let mut rename = None;
135+
for attr in field.attrs {
136+
if attr.path().is_ident("netcdf") {
137+
attr.parse_nested_meta(|meta| {
138+
if meta.path.is_ident("rename") {
139+
rename = Some(meta.value()?.parse::<LitStr>()?.value());
140+
} else {
141+
abort!(meta.path, "NcType encountered an unknown attribute")
142+
}
143+
Ok(())
144+
})
145+
.unwrap();
146+
}
147+
}
148+
let name = rename.unwrap_or_else(|| ident.to_string());
149+
items.push(FieldInfo {
150+
name,
151+
typ: field.ty,
152+
});
153+
}
154+
155+
let fieldnames = items
156+
.iter()
157+
.map(|item| item.name.clone())
158+
.collect::<Vec<_>>();
159+
let typeids = items
160+
.iter()
161+
.map(|item| item.typ.clone())
162+
.collect::<Vec<_>>();
163+
let fieldinfo = quote!(vec![#(
164+
(
165+
(#fieldnames).to_owned(),
166+
<#typeids as NcTypeDescriptor>::type_descriptor(),
167+
(<#typeids as NcTypeDescriptor>::ARRAY_ELEMENTS).as_dims().map(Vec::from),
168+
)
169+
),*]);
170+
171+
quote! {
172+
let mut fields = vec![];
173+
let mut offset = 0;
174+
for (name, basetype, arraydims) in #fieldinfo {
175+
let nelems = arraydims.as_ref().map_or(1, |x| x.iter().copied().product());
176+
let thissize = basetype.size() * nelems;
177+
fields.push(CompoundTypeField {
178+
name,
179+
offset,
180+
basetype,
181+
arraydims,
182+
});
183+
offset += thissize;
184+
}
185+
let compound = NcVariableType::Compound(CompoundType {
186+
name: (#ncname).to_owned(),
187+
size: offset,
188+
fields,
189+
});
190+
assert_eq!(compound.size(), std::mem::size_of::<#ty>(), "Compound must be packed");
191+
compound
192+
}
193+
}
194+
195+
fn impl_enum<'a>(
196+
// ty: &Ident,
197+
ncname: &str,
198+
basetyp: &Ident,
199+
fields: impl Iterator<Item = &'a Variant>,
200+
) -> TokenStream {
201+
let mut fieldnames = vec![];
202+
let mut fieldvalues = vec![];
203+
204+
for field in fields {
205+
let ident = field.ident.clone();
206+
let mut rename = None;
207+
for attr in &field.attrs {
208+
if attr.path().is_ident("netcdf") {
209+
attr.parse_nested_meta(|meta| {
210+
if meta.path.is_ident("rename") {
211+
rename = Some(meta.value()?.parse::<LitStr>()?.value());
212+
} else {
213+
abort!(meta.path, "NcType encountered an unknown attribute")
214+
}
215+
Ok(())
216+
})
217+
.unwrap();
218+
}
219+
}
220+
let name = rename.unwrap_or_else(|| ident.to_string());
221+
fieldnames.push(name);
222+
223+
let variant = match field.discriminant.clone() {
224+
Some((_, x)) => quote!(#x),
225+
None => fieldvalues
226+
.last()
227+
.map(|e| quote!(#e + 1))
228+
.unwrap_or(quote!(0)),
229+
};
230+
231+
fieldvalues.push(variant);
232+
}
233+
234+
let fieldnames = quote!(vec![#(#fieldnames),*]);
235+
let fieldvalues = quote!(vec![#(#fieldvalues),*]);
236+
237+
quote! {
238+
NcVariableType::Enum(EnumType {
239+
name: (#ncname).to_owned(),
240+
fieldnames: (#fieldnames).iter().map(|x| x.to_string()).collect(),
241+
fieldvalues: ((#fieldvalues).into_iter().collect::<Vec::<#basetyp>>()).into(),
242+
})
243+
}
244+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[derive(netcdf_derive::NcType)]
2+
enum NoRepr {
3+
A,
4+
B,
5+
}
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: Can not derive NcType for enum without suitable repr
2+
--> tests/failing/enumnorepr.rs:2:1
3+
|
4+
2 | / enum NoRepr {
5+
3 | | A,
6+
4 | | B,
7+
5 | | }
8+
| |_^
9+
|
10+
= help: Add #[repr(i32)] (or another integer type) as an attribute to the enum
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#[derive(netcdf_derive::NcType)]
2+
struct NoReprC {
3+
a: u8,
4+
}
5+
6+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error: Can not derive NcType for struct without fixed layout
2+
--> tests/failing/noreprc.rs:2:1
3+
|
4+
2 | / struct NoReprC {
5+
3 | | a: u8,
6+
4 | | }
7+
| |_^
8+
|
9+
= help: struct must have attribute #[repr(C)]

netcdf-derive/tests/failing/union.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[derive(netcdf_derive::NcType)]
2+
union NoSupportedUnion {
3+
A: i32,
4+
B: u32,
5+
}
6+
7+
fn main() {}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: Can not derive NcType for union
2+
--> tests/failing/union.rs:2:1
3+
|
4+
2 | / union NoSupportedUnion {
5+
3 | | A: i32,
6+
4 | | B: u32,
7+
5 | | }
8+
| |_^
9+
|
10+
= note: netCDF has no concept of Union type
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[derive(netcdf_derive::NcType)]
2+
#[repr(C)]
3+
struct B;
4+
5+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: Can not derive NcType for unit struct
2+
--> tests/failing/unittype.rs:2:1
3+
|
4+
2 | / #[repr(C)]
5+
3 | | struct B;
6+
| |_________^

0 commit comments

Comments
 (0)