Skip to content

Commit 8a16647

Browse files
authored
Merge pull request #1011 from schungx/master
Handle Option fields in CustomType derive
2 parents 7138710 + 8c07d41 commit 8a16647

File tree

6 files changed

+95
-11
lines changed

6 files changed

+95
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ Enhancements
1515
-----------
1616

1717
* `CustomType` derive macro now supports generic types (thanks [`@ProphetOSpam`](https://github.com/ProphetOSpam) [#999](https://github.com/rhaiscript/rhai/pull/999)). The `rhai_codegen` crate dependency is bumped to `3.0.0` or later.
18+
* `CustomType` derive macro now handles `Option` fields (thanks [`@agersant`](https://github.com/agersant) [#1005](https://github.com/rhaiscript/rhai/pull/1005)).
1819
* `Engine::eval_binary_op` is added to quickly compare two `Dynamic` values.
19-
* Better handling for 32-bit architectures.
20+
* Better handling for 32-bit architectures and enhanced safety by replacing casts with `try_from` (thanks [`@therealprof`](https://github.com/therealprof) [#1009](https://github.com/rhaiscript/rhai/pull/1009)).
2021

2122

2223
Version 1.22.2

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ num-traits = { version = "0.2.14", default-features = false }
2525
once_cell = { version = "1.20.1", default-features = false, features = ["race", "portable-atomic", "alloc"] }
2626
bitflags = { version = "2.3.3", default-features = false }
2727
smartstring = { version = "1.0.0", default-features = false }
28-
rhai_codegen = { version = "3.0.0", path = "codegen" }
28+
rhai_codegen = { version = "3.1.0", path = "codegen" }
2929

3030
no-std-compat = { git = "https://gitlab.com/jD91mZM2/no-std-compat.git", version = "0.4.1", default-features = false, features = ["alloc"], optional = true }
3131
libm = { version = "0.2.0", default-features = false, optional = true }

codegen/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rhai_codegen"
3-
version = "3.0.0"
3+
version = "3.1.0"
44
edition = "2018"
55
resolver = "2"
66
authors = ["jhwgh1968", "Stephen Chung"]

codegen/src/custom_type.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,51 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
183183
}
184184
}
185185

186+
// Code lifted from: https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn
187+
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
188+
use syn::{GenericArgument, Path, PathArguments, PathSegment};
189+
190+
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
191+
match *ty {
192+
syn::Type::Path(ref type_path) if type_path.qself.is_none() => Some(&type_path.path),
193+
_ => None,
194+
}
195+
}
196+
197+
// TODO store (with lazy static) the vec of string
198+
// TODO maybe optimization, reverse the order of segments
199+
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
200+
let idents_of_path = path
201+
.segments
202+
.iter()
203+
.into_iter()
204+
.fold(String::new(), |mut acc, v| {
205+
acc.push_str(&v.ident.to_string());
206+
acc.push('|');
207+
acc
208+
});
209+
vec!["Option|", "std|option|Option|", "core|option|Option|"]
210+
.into_iter()
211+
.find(|s| &idents_of_path == *s)
212+
.and_then(|_| path.segments.last())
213+
}
214+
215+
extract_type_path(ty)
216+
.and_then(|path| extract_option_segment(path))
217+
.and_then(|path_seg| {
218+
let type_params = &path_seg.arguments;
219+
// It should have only on angle-bracketed param ("<String>"):
220+
match *type_params {
221+
PathArguments::AngleBracketed(ref params) => params.args.first(),
222+
_ => None,
223+
}
224+
})
225+
.and_then(|generic_arg| match *generic_arg {
226+
GenericArgument::Type(ref ty) => Some(ty),
227+
_ => None,
228+
})
229+
}
230+
186231
fn scan_fields(fields: &[&Field], accessors: &mut Vec<TokenStream>, errors: &mut Vec<TokenStream>) {
187232
for (i, &field) in fields.iter().enumerate() {
188233
let mut map_name = None;
@@ -314,21 +359,54 @@ fn scan_fields(fields: &[&Field], accessors: &mut Vec<TokenStream>, errors: &mut
314359
quote! { #index }
315360
};
316361

362+
// Handle `Option` fields
363+
let option_type = extract_type_from_option(&field.ty);
364+
317365
// Override functions
318-
let get = match (get_mut_fn, get_fn) {
366+
367+
let get_impl = match (get_mut_fn, get_fn) {
319368
(Some(func), _) => func,
320369
(None, Some(func)) => quote! { |obj: &mut Self| #func(&*obj) },
321-
(None, None) => quote! { |obj: &mut Self| obj.#field_name.clone() },
370+
(None, None) => {
371+
if let Some(_) = option_type {
372+
quote! { |obj: &mut Self| obj.#field_name.clone().map_or(Dynamic::UNIT, Dynamic::from) }
373+
} else {
374+
quote! { |obj: &mut Self| obj.#field_name.clone() }
375+
}
376+
}
322377
};
323378

324-
let set = set_fn.unwrap_or_else(|| quote! { |obj: &mut Self, val| obj.#field_name = val });
379+
let set_impl = set_fn.unwrap_or_else(|| {
380+
if let Some(typ) = option_type {
381+
quote! {
382+
|obj: &mut Self, val: Dynamic| {
383+
if val.is_unit() {
384+
obj.#field_name = None;
385+
Ok(())
386+
} else if let Some(x) = val.read_lock::<#typ>() {
387+
obj.#field_name = Some(x.clone());
388+
Ok(())
389+
} else {
390+
Err(Box::new(EvalAltResult::ErrorMismatchDataType(
391+
stringify!(#typ).to_string(),
392+
val.type_name().to_string(),
393+
Position::NONE
394+
)))
395+
}
396+
}
397+
}
398+
} else {
399+
quote! { |obj: &mut Self, val| obj.#field_name = val }
400+
}
401+
});
402+
325403
let name = map_name.unwrap_or_else(|| quote! { stringify!(#field_name) });
326404

327405
accessors.push({
328406
let method = if readonly {
329-
quote! { builder.with_get(#name, #get) }
407+
quote! { builder.with_get(#name, #get_impl) }
330408
} else {
331-
quote! { builder.with_get_set(#name, #get, #set) }
409+
quote! { builder.with_get_set(#name, #get_impl, #set_impl) }
332410
};
333411

334412
#[cfg(feature = "metadata")]

codegen/tests/test_custom_type.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rhai::{CustomType, Engine, TypeBuilder, INT};
1+
use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Position, TypeBuilder, INT};
22

33
// Sanity check to make sure everything compiles
44

@@ -21,6 +21,7 @@ pub struct Foo {
2121
pub(crate) baz: String,
2222
#[rhai_type(set = Self::set_qux)]
2323
pub qux: Vec<INT>,
24+
pub maybe: Option<INT>,
2425
}
2526

2627
impl Foo {

tests/build_type.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![cfg(not(feature = "no_object"))]
2-
use rhai::{CustomType, Engine, EvalAltResult, Position, TypeBuilder, INT};
2+
use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Position, TypeBuilder, INT};
33
use std::cmp::Ordering;
44

55
#[test]
@@ -170,6 +170,7 @@ fn test_build_type_macro() {
170170
baz: bool,
171171
#[rhai_type(set = Self::set_hello)]
172172
hello: String,
173+
maybe: Option<bool>,
173174
}
174175

175176
impl Foo {
@@ -191,6 +192,7 @@ fn test_build_type_macro() {
191192
bar: 5,
192193
baz: false,
193194
hello: "hey".to_string(),
195+
maybe: None,
194196
});
195197
}
196198
}
@@ -207,6 +209,7 @@ fn test_build_type_macro() {
207209
foo.hello = "world!";
208210
foo.emphasize = true;
209211
foo.hello = "yo";
212+
foo.maybe = true;
210213
foo
211214
"#
212215
)
@@ -215,7 +218,8 @@ fn test_build_type_macro() {
215218
dummy: 0,
216219
bar: 5,
217220
baz: true,
218-
hello: "world!yo!!!!!".into()
221+
hello: "world!yo!!!!!".into(),
222+
maybe: Some(true),
219223
}
220224
);
221225
}

0 commit comments

Comments
 (0)