Skip to content

Commit 25872f4

Browse files
committed
Require that #[export(range = ...)] uses correct literals
Previously, all literals were expected to be floating-point, which meant: * Even integers needed float literals. #[export(range = (1.0, 5.0))] field: i8 * Literals were unchecked. #[export(range = (1.0, 500.0))] field: i8 Both now cause a compile error.
1 parent 1e429c3 commit 25872f4

File tree

4 files changed

+56
-23
lines changed

4 files changed

+56
-23
lines changed

godot-core/src/registry/property/mod.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ use crate::classes;
1616
use crate::global::PropertyHint;
1717
use crate::meta::{ClassName, FromGodot, GodotConvert, GodotType, PropertyHintInfo, ToGodot};
1818
use crate::obj::{EngineEnum, GodotClass};
19+
20+
// ----------------------------------------------------------------------------------------------------------------------------------------------
21+
// Child modules
22+
23+
mod phantom_var;
24+
25+
pub use phantom_var::PhantomVar;
26+
1927
// ----------------------------------------------------------------------------------------------------------------------------------------------
2028
// Trait definitions
2129

@@ -224,6 +232,7 @@ where
224232
/// Each function is named the same as the equivalent Godot annotation.
225233
/// For instance, `@export_range` in Godot is `fn export_range` here.
226234
pub mod export_info_functions {
235+
use std::fmt;
227236
use std::fmt::Write;
228237

229238
use godot_ffi::VariantType;
@@ -233,7 +242,7 @@ pub mod export_info_functions {
233242
use crate::meta::{GodotType, PropertyHintInfo, PropertyInfo};
234243
use crate::obj::EngineEnum;
235244
use crate::registry::property::Export;
236-
use crate::sys;
245+
use crate::{godot_str, sys};
237246

238247
/// Turn a list of variables into a comma separated string containing only the identifiers corresponding
239248
/// to a true boolean variable.
@@ -267,17 +276,18 @@ pub mod export_info_functions {
267276
/// #[derive(GodotClass)]
268277
/// #[class(init, base=Node)]
269278
/// struct MyClassWithRangedValues {
270-
/// #[export(range=(0.0, 400.0, 1.0, or_greater, suffix="px"))]
279+
/// #[export(range=(0, 400, 1, or_greater, suffix="px"))]
271280
/// icon_width: i32,
281+
///
272282
/// #[export(range=(-180.0, 180.0, degrees))]
273283
/// angle: f32,
274284
/// }
275285
/// ```
276286
#[allow(clippy::too_many_arguments)]
277-
pub fn export_range(
278-
min: f64,
279-
max: f64,
280-
step: Option<f64>,
287+
pub fn export_range<T: Export + fmt::Display>(
288+
min: T,
289+
max: T,
290+
step: Option<T>,
281291
or_greater: bool,
282292
or_less: bool,
283293
exp: bool,
@@ -314,7 +324,7 @@ pub mod export_info_functions {
314324

315325
PropertyHintInfo {
316326
hint: PropertyHint::RANGE,
317-
hint_string: GString::from(hint_string),
327+
hint_string: GString::from(&hint_string),
318328
}
319329
}
320330

@@ -377,7 +387,7 @@ pub mod export_info_functions {
377387

378388
PropertyHintInfo {
379389
hint: PropertyHint::ENUM,
380-
hint_string: GString::from(hint_string),
390+
hint_string: GString::from(&hint_string),
381391
}
382392
}
383393

@@ -386,7 +396,7 @@ pub mod export_info_functions {
386396

387397
PropertyHintInfo {
388398
hint: PropertyHint::EXP_EASING,
389-
hint_string: GString::from(hint_string),
399+
hint_string: GString::from(&hint_string),
390400
}
391401
}
392402

@@ -410,7 +420,7 @@ pub mod export_info_functions {
410420

411421
PropertyHintInfo {
412422
hint: PropertyHint::FLAGS,
413-
hint_string: GString::from(hint_string),
423+
hint_string: GString::from(&hint_string),
414424
}
415425
}
416426

@@ -489,14 +499,14 @@ pub mod export_info_functions {
489499

490500
PropertyHintInfo {
491501
hint: PropertyHint::TYPE_STRING,
492-
hint_string: GString::from(format!("{hint_string}:{filter}")),
502+
hint_string: godot_str!("{hint_string}:{filter}"),
493503
}
494504
}
495505

496-
pub fn export_placeholder<S: AsRef<str>>(placeholder: S) -> PropertyHintInfo {
506+
pub fn export_placeholder(placeholder: &str) -> PropertyHintInfo {
497507
PropertyHintInfo {
498508
hint: PropertyHint::PLACEHOLDER_TEXT,
499-
hint_string: GString::from(placeholder.as_ref()),
509+
hint_string: GString::from(placeholder),
500510
}
501511
}
502512

godot-macros/src/class/data_models/field_export.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ use quote::quote;
1212

1313
use crate::util::{ident, KvParser, ListParser};
1414
use crate::ParseResult;
15-
1615
pub struct FieldExport {
1716
pub export_type: ExportType,
1817
pub span: Span,
1918
}
2019

2120
impl FieldExport {
22-
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> {
21+
pub(crate) fn new_from_kv(
22+
parser: &mut KvParser,
23+
field_ty: venial::TypeExpr,
24+
) -> ParseResult<Self> {
2325
let span = parser.span();
24-
let export_type = ExportType::new_from_kv(parser)?;
26+
let export_type = ExportType::new_from_kv(parser, field_ty)?;
2527
Ok(Self { export_type, span })
2628
}
2729

@@ -65,6 +67,7 @@ pub enum ExportType {
6567
/// ### Property hints
6668
/// - `RANGE`
6769
Range {
70+
field_ty: venial::TypeExpr,
6871
min: TokenStream,
6972
max: TokenStream,
7073
step: TokenStream,
@@ -168,13 +171,15 @@ impl ExportType {
168171
/// - `@export_{flags/enum}("elem1", "elem2:key2", ...)`
169172
/// becomes
170173
/// `#[export(flags/enum = (elem1, elem2 = key2, ...))]`
171-
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> {
174+
pub(crate) fn new_from_kv(
175+
parser: &mut KvParser,
176+
field_ty: venial::TypeExpr,
177+
) -> ParseResult<Self> {
172178
if parser.handle_alone("storage")? {
173179
return Self::new_storage();
174180
}
175-
176181
if let Some(list_parser) = parser.handle_list("range")? {
177-
return Self::new_range_list(list_parser);
182+
return Self::new_range_list(list_parser, field_ty);
178183
}
179184

180185
if let Some(list_parser) = parser.handle_list("enum")? {
@@ -300,7 +305,7 @@ impl ExportType {
300305
Ok(Self::Storage)
301306
}
302307

303-
fn new_range_list(mut parser: ListParser) -> ParseResult<Self> {
308+
fn new_range_list(mut parser: ListParser, field_ty: venial::TypeExpr) -> ParseResult<Self> {
304309
const FLAG_OPTIONS: [&str; 7] = [
305310
"or_greater",
306311
"or_less",
@@ -314,6 +319,7 @@ impl ExportType {
314319

315320
let min = parser.next_expr()?;
316321
let max = parser.next_expr()?;
322+
317323
// If there is a next element, and it is a literal, we take its tokens directly.
318324
let step = if parser.peek().is_some_and(|kv| kv.as_literal().is_ok()) {
319325
let value = parser
@@ -330,6 +336,7 @@ impl ExportType {
330336
loop {
331337
let key_maybe_value =
332338
parser.next_allowed_key_optional_value(&FLAG_OPTIONS, &KV_OPTIONS)?;
339+
333340
match key_maybe_value {
334341
Some((option, None)) => {
335342
flags.insert(option.to_string());
@@ -344,6 +351,7 @@ impl ExportType {
344351
parser.finish()?;
345352

346353
Ok(Self::Range {
354+
field_ty,
347355
min,
348356
max,
349357
step,
@@ -411,12 +419,19 @@ impl ExportType {
411419
}
412420

413421
macro_rules! quote_export_func {
414-
($function_name:ident($($tt:tt)*)) => {
422+
($function_name:ident ($($tt:tt)*) ) => {
415423
Some(quote! {
416424
::godot::register::property::export_info_functions::$function_name($($tt)*)
417425
})
418426
};
419427

428+
// Use [ ] for generic args due to parsing ambiguity with ::< > turbofish.
429+
($function_name:ident [ $($generic_args:tt)* ] ($($tt:tt)*) ) => {
430+
Some(quote! {
431+
::godot::register::property::export_info_functions::$function_name::< $($generic_args)* >($($tt)*)
432+
})
433+
};
434+
420435
// Passes in a previously declared local `type FieldType = ...` as first generic argument.
421436
// Doesn't work if function takes other generic arguments -- in that case it could be converted to a Type<...> parameter.
422437
($function_name:ident < T > ($($tt:tt)*)) => {
@@ -434,6 +449,7 @@ impl ExportType {
434449
Self::Storage => quote_export_func! { export_storage() },
435450

436451
Self::Range {
452+
field_ty,
437453
min,
438454
max,
439455
step,
@@ -451,9 +467,11 @@ impl ExportType {
451467
} else {
452468
quote! { None }
453469
};
470+
454471
let export_func = quote_export_func! {
455-
export_range(#min, #max, #step, #or_greater, #or_less, #exp, #radians_as_degrees || #radians, #degrees, #hide_slider, #suffix)
472+
export_range [ #field_ty ] (#min, #max, #step, #or_greater, #or_less, #exp, #radians_as_degrees || #radians, #degrees, #hide_slider, #suffix)
456473
}?;
474+
457475
let deprecation_warning = if *radians {
458476
// For some reason, rustfmt formatting like this. Probably a bug.
459477
// See https://github.com/godot-rust/gdext/pull/783#discussion_r1669105958 and
@@ -465,6 +483,7 @@ impl ExportType {
465483
} else {
466484
quote! { #export_func }
467485
};
486+
468487
Some(quote! {
469488
#deprecation_warning
470489
})

godot-macros/src/class/derive_godot_class.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ fn parse_fields(
678678

679679
// #[export]
680680
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export")? {
681-
let export = FieldExport::new_from_kv(&mut parser)?;
681+
let export = FieldExport::new_from_kv(&mut parser, named_field.ty.clone())?;
682682
field.export = Some(export);
683683
parser.finish()?;
684684
}

itest/rust/src/object_tests/property_test.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ struct CheckAllExports {
267267
#[export(range = (0.0, 10.0, 0.2, or_greater, or_less, exp, radians_as_degrees, hide_slider))]
268268
range_exported_with_step: f64,
269269

270+
// Fails to compile if literal is floating-point or outside i8 range.
271+
#[export(range = (-5, 127))]
272+
int_exported: i8,
273+
270274
#[export(enum = (A = 10, B, C, D = 20))]
271275
enum_exported: i64,
272276

0 commit comments

Comments
 (0)