Repository: bevyengine/bevy
File: crates/bevy_ecs/macros/src/component.rs (Rust)

Code before:
pub const ON_INSERT: &str = "on_insert";
pub const ON_REPLACE: &str = "on_replace";
pub const ON_REMOVE: &str = "on_remove";
pub const ON_DESPAWN: &str = "on_despawn";
pub const MAP_ENTITIES: &str = "map_entities";

pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";

/// All allowed attribute value expression kinds for component hooks.
/// This doesn't simply use general expressions because of conflicting needs:
/// - we want to be able to use `Self` & generic parameters in paths
/// - call expressions producing a closure need to be wrapped in a function
///   to turn them into function pointers, which prevents access to the outer generic params
#[derive(Debug)]
enum HookAttributeKind {
    /// expressions like function or struct names
    ///
    /// structs will throw compile errors on the code generation so this is safe
    Path(ExprPath),
    /// function call like expressions
    Call(ExprCall),
}

impl HookAttributeKind {
    fn parse(input: syn::parse::ParseStream, default_hook_path: ExprPath) -> Result<Self> {
        if input.peek(Token![=]) {
            input.parse::<Token![=]>()?;
            input.parse::<Expr>().and_then(Self::from_expr)
        } else {
            Ok(Self::Path(default_hook_path))
        }
    }

    fn from_expr(value: Expr) -> Result<Self> {
        match value {
            Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
            Expr::Call(call) => Ok(HookAttributeKind::Call(call)),
            // throw meaningful error on all other expressions
            _ => Err(syn::Error::new(
                value.span(),
                [
                    "Not supported in this position, please use one of the following:",
                    "- path to function",
                    "- call to function yielding closure",
                ]
                .join("\n"),
            )),
        }
    }


Review comment (suggestion):
Small optimization: I think this should accept an `impl FnOnce() -> ExprPath` so we can avoid the cost of constructing the path if it isn't needed.  

Code after:
pub const ON_INSERT: &str = "on_insert";
pub const ON_REPLACE: &str = "on_replace";
pub const ON_REMOVE: &str = "on_remove";
pub const ON_DESPAWN: &str = "on_despawn";
pub const MAP_ENTITIES: &str = "map_entities";

pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";

/// All allowed attribute value expression kinds for component hooks.
/// This doesn't simply use general expressions because of conflicting needs:
/// - we want to be able to use `Self` & generic parameters in paths
/// - call expressions producing a closure need to be wrapped in a function
///   to turn them into function pointers, which prevents access to the outer generic params
#[derive(Debug)]
enum HookAttributeKind {
    /// expressions like function or struct names
    ///
    /// structs will throw compile errors on the code generation so this is safe
    Path(ExprPath),
    /// function call like expressions
    Call(ExprCall),
}

impl HookAttributeKind {
    fn parse(
        input: syn::parse::ParseStream,
        default_hook_path: impl FnOnce() -> ExprPath,
    ) -> Result<Self> {
        if input.peek(Token![=]) {
            input.parse::<Token![=]>()?;
            input.parse::<Expr>().and_then(Self::from_expr)
        } else {
            Ok(Self::Path(default_hook_path()))
        }
    }

    fn from_expr(value: Expr) -> Result<Self> {
        match value {
            Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
            Expr::Call(call) => Ok(HookAttributeKind::Call(call)),
            // throw meaningful error on all other expressions
            _ => Err(syn::Error::new(
                value.span(),
                [
                    "Not supported in this position, please use one of the following:",
                    "- path to function",
                    "- call to function yielding closure",
                ]
                .join("\n"),
            )),