Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 47 additions & 31 deletions crates/bevy_scene/macros/src/bsn/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ impl BsnEntry {
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
"__value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
Expand All @@ -218,7 +218,7 @@ impl BsnEntry {
}
} else {
quote! {
let value = _scene.get_or_insert_template::<#path>(_context);
let __value = _scene.get_or_insert_template::<#path>(_context);
#(#assigns)*
}
})
Expand All @@ -227,7 +227,7 @@ impl BsnEntry {
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
"__value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
Expand All @@ -240,7 +240,7 @@ impl BsnEntry {
}
} else {
quote! {
let value = _scene.get_or_insert_template::<<#path as #bevy_ecs::template::FromTemplate>::Template>(_context);
let __value = _scene.get_or_insert_template::<<#path as #bevy_ecs::template::FromTemplate>::Template>(_context);
#(#assigns)*
}
})
Expand All @@ -249,8 +249,8 @@ impl BsnEntry {
type_path,
const_ident,
} => EntryResult::CombinedSceneFunction(quote! {
let value = _scene.get_or_insert_template::<#type_path>(_context);
*value = #type_path::#const_ident;
let __value = _scene.get_or_insert_template::<#type_path>(_context);
*__value = #type_path::#const_ident;
}),
BsnEntry::TemplateConstructor(BsnConstructor {
type_path,
Expand All @@ -259,8 +259,8 @@ impl BsnEntry {
}) => EntryResult::CombinedSceneFunction({
let args = args.to_tokens(ctx);
quote! {
let value = _scene.get_or_insert_template::<#type_path>(_context);
*value = #type_path::#function #args;
let __value = _scene.get_or_insert_template::<#type_path>(_context);
*__value = #type_path::#function #args;
}
}),
BsnEntry::FromTemplateConstructor(BsnConstructor {
Expand All @@ -270,8 +270,8 @@ impl BsnEntry {
}) => EntryResult::CombinedSceneFunction({
let args = args.to_tokens(ctx);
quote! {
let value = _scene.get_or_insert_template::<<#type_path as #bevy_ecs::template::FromTemplate>::Template>(_context);
*value = <#type_path as #bevy_ecs::template::FromTemplate>::Template::#function #args;
let __value = _scene.get_or_insert_template::<<#type_path as #bevy_ecs::template::FromTemplate>::Template>(_context);
*__value = <#type_path as #bevy_ecs::template::FromTemplate>::Template::#function #args;
}
}),
BsnEntry::RelatedSceneList(BsnRelatedSceneList {
Expand Down Expand Up @@ -310,8 +310,8 @@ impl BsnScene {
// which imposes constraints like requiring the type to impl FromTemplate, and requiring
// enums to have VariantDefault.
let mut assignments = Vec::new();
let props = format_ident!("props");
let props_ref = format_ident!("props_ref");
let props = format_ident!("__props");
let props_ref = format_ident!("__props_ref");
let target = PatchTarget {
path: &[Member::Named(props_ref.clone())],
is_ref: true,
Expand All @@ -320,7 +320,7 @@ impl BsnScene {
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
"__value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
Expand All @@ -329,7 +329,7 @@ impl BsnScene {
let path = &bsn_type.path;
let bevy_scene = ctx.bevy_scene;
let from_template_patch = quote! {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |__value, _context| {
#(#assigns)*
})
};
Expand Down Expand Up @@ -489,15 +489,15 @@ impl BsnType {
continue;
}

if field.value.is_none() {
if field.value.is_none() && !field.is_name_shorthand {
ctx.errors.push(syn::Error::new_spanned(
field_name,
format!("Field `{}` is missing a value.", field_name),
));
}

let path = if field.is_prop {
&[Member::Named(format_ident!("props"))]
&[Member::Named(format_ident!("__props"))]
} else {
target.path
};
Expand All @@ -508,6 +508,7 @@ impl BsnType {
path,
Member::Named(field_name.clone()),
field.value.as_ref(),
field.is_name_shorthand,
)?;
}
}
Expand All @@ -523,6 +524,7 @@ impl BsnType {
target.path,
Member::Unnamed(Index::from(i)),
Some(&field.value),
false,
) {
ctx.errors.push(err);
}
Expand All @@ -539,25 +541,31 @@ impl BsnType {
base_path: &[Member],
member: Member,
value: Option<&BsnValue>,
is_name_shorthand: bool,
) -> syn::Result<()> {
match value {
// NOTE: It is very important to still produce outputs for None field values. This is what
// enables field autocomplete in Rust Analyzer
None => {
if is_name_shorthand {
assignments.push(quote! {
#(#base_path.)*#member = #member.into();
});
} else {
assignments.push(quote! {
#(#base_path.)*#member;
});
}
}
// Enables field autocomplete in Rust Analyzer
Some(
BsnValue::Ident(_) | BsnValue::Expr(_) | BsnValue::Closure(_) | BsnValue::Tuple(_),
)
| None => {
// NOTE: It is very important to still produce outputs for None field values. This is what
// enables field autocomplete in Rust Analyzer
assignments.push(
value
.map(|v| {
let ident = ctx.hoisted_expressions.hoist(v);
quote! { #(#base_path.)*#member = #ident; }
})
.unwrap_or(quote! {
#(#base_path.)*#member;
}),
);
value @ (BsnValue::Ident(_)
| BsnValue::Expr(_)
| BsnValue::Closure(_)
| BsnValue::Tuple(_)),
) => {
let ident = ctx.hoisted_expressions.hoist(value);
assignments.push(quote! { #(#base_path.)*#member = #ident; });
}
Some(BsnValue::Lit(_)) => {
// value is Some
Expand Down Expand Up @@ -781,11 +789,13 @@ mod tests {
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!({}))),
is_prop: false,
is_name_shorthand: false,
},
BsnNamedField {
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!({}))),
is_prop: false,
is_name_shorthand: false,
},
]),
};
Expand Down Expand Up @@ -820,6 +830,7 @@ mod tests {
enum_variant: None,
fields: BsnFields::Named(vec![BsnNamedField {
is_prop: false,
is_name_shorthand: false,
name: parse_quote!(child_field),
value: Some(BsnValue::Type(BsnType {
path: parse_quote!(Child),
Expand All @@ -829,11 +840,13 @@ mod tests {
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!({}))),
is_prop: false,
is_name_shorthand: false,
},
BsnNamedField {
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!({}))),
is_prop: false,
is_name_shorthand: false,
},
]),
})),
Expand Down Expand Up @@ -871,6 +884,7 @@ mod tests {
enum_variant: None,
fields: BsnFields::Named(vec![BsnNamedField {
is_prop: false,
is_name_shorthand: false,
name: parse_quote!(x),
value: None,
}]),
Expand Down Expand Up @@ -908,11 +922,13 @@ mod tests {
fields: BsnFields::Named(vec![
BsnNamedField {
is_prop: false,
is_name_shorthand: false,
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!(1))),
},
BsnNamedField {
is_prop: false,
is_name_shorthand: false,
name: parse_quote!(x),
value: Some(BsnValue::Expr(quote!(2))),
},
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_scene/macros/src/bsn/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ impl Parse for BsnNamedField {
false
};
let name = input.parse::<Ident>()?;
let mut is_name_shorthand = false;
let value = if input.peek(Colon) {
input.parse::<Colon>()?;

Expand All @@ -367,12 +368,14 @@ impl Parse for BsnNamedField {
Some(input.parse::<BsnValue>()?)
}
} else {
is_name_shorthand = true;
None
};
Ok(BsnNamedField {
name,
value,
is_prop,
is_name_shorthand,
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_scene/macros/src/bsn/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub struct BsnTuple(pub Vec<BsnValue>);
#[derive(Debug)]
pub struct BsnNamedField {
pub is_prop: bool,
/// This is a `Struct { field }` shorthand for `Struct { field: field }`
pub is_name_shorthand: bool,
pub name: Ident,
/// This is an Option to enable autocomplete when the field name is being typed
/// To improve autocomplete further we'll need to forgo a lot of the syn parsing
Expand Down
63 changes: 61 additions & 2 deletions crates/bevy_scene/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ use bevy_ecs::prelude::*;
/// | `CompA(val)`<br>`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) |
/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) |
/// | `mymodule::CompA { name: val }` | Same as above, but referring to the component by module path |
/// | `CompA { name }` | Component with Rust's "field assignment shorthand". Evaluates to `CompA { name: name.into() }` |
/// | `MyEnum::Variant` | Enum Component `MyEnum` with the `Variant` variant |
/// | `template_value(component)` | Insert the component value from a variable `component` |
/// | `template_value(CompA::from_str("foo"))` | Insert the component value by immediately calling the constructor |
Expand Down Expand Up @@ -2839,11 +2840,69 @@ mod tests {
let entity = world
.spawn_scene(bsn! {
Foo {
value: vec! [ 10usize ],
value: vec! [ 10 ],
}
})
.unwrap();
assert_eq!(entity.get::<Foo>().unwrap().value, vec![10usize]);
assert_eq!(entity.get::<Foo>().unwrap().value, vec![10]);
}

#[test]
fn field_name_shorthand() {
let mut app = test_app();
let world = app.world_mut();

#[derive(Component, Default, Clone)]
struct Foo {
value: usize,
}
let value = 10usize;
let entity = world.spawn_scene(bsn! { Foo { value } }).unwrap();
assert_eq!(entity.get::<Foo>().unwrap().value, 10);

#[derive(SceneComponent, Default, Clone)]
#[scene(BarProps)]
struct Bar {
value: usize,
}

#[derive(Default)]
struct BarProps {
value: usize,
}

impl Bar {
fn scene(props: BarProps) -> impl Scene {
bsn! {Bar {
value: {props.value}
}}
}
}

let value = 10usize;
let entity = world.spawn_scene(bsn! { @Bar { @value } }).unwrap();
assert_eq!(entity.get::<Bar>().unwrap().value, 10);

#[derive(Component, Default, Clone)]
struct Baz {
value: X,
}

#[derive(Default, Clone)]
struct X;

#[derive(Default, Clone)]
struct Y;

impl From<Y> for X {
fn from(_: Y) -> Self {
X
}
}

let value = Y;
// ensure implicit Into works
let _ = world.spawn_scene(bsn! { Baz { value } }).unwrap();
}

#[test]
Expand Down
Loading