Repository: zng-ui/zng (language: rust)

Issue:
When expression "borrow of moved value"
<!--
Please, make sure:

- The issue happens in the latest crate release or newer (master branch).
- The issue happens after `cargo update`.
-->

I tried this code:

```rust
let can_move = var(true);

Container! {
        when *#{can_move.clone()} {
            mouse::cursor = mouse::CursorIcon::Move;
        }
        mouse::on_mouse_down = hn!( |_| {
            let _use = &can_move;
        });

}
```

I saw this happen:

error[E0382]: borrow of moved value: `can_move`

I expected this to happen:

It should compile because the when expression is declared before the handler, so`#{can_move.clone()}` should eval before the `hn!` capture move.

Error log or stack trace:

<details>
  <summary>stderr</summary>

```
Running: do "run" ["window"]
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on build directory
   Compiling examples v0.0.0 (C:\code\zng\examples)
error[E0382]: borrow of moved value: `can_move`
   --> examples\window.rs:435:17
    |
427 |       let can_move = vars.state().map(|s| matches!(s, WindowState::Normal | WindowState:... 
    |           -------- move occurs because `can_move` has type `ReadOnlyVar<bool, ArcVar<bool>>`, which does not implement the `Copy` trait
...
435 |           when *#{can_move.clone()} {
    |                   ^^^^^^^^ value borrowed here after move
...
438 |           mouse::on_mouse_down = hn!(|args: &mouse::MouseInputArgs| {
    |  ________________________________-
439 | |             if args.is_primary() && can_move.get() {
    | |                                     -------- variable moved due to use in closure
440 | |                 window::cmd::DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify();
441 | |             }
442 | |         });
    | |__________- value moved into closure here
    |
help: consider cloning the value if the performance cost is acceptable
   --> C:\code\zng\crates\zng-clone-move\src\lib.rs:140:17
    |
140 |             move.clone() | $($rest)+
    |                 ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `examples` (example "window") due to 1 previous error
error: task "run" failed with exit code: 101
```

</details>


Discussion / hints:
When expressions are expanded after all assigns to support later assigns back when the WidgetBuilder did not exist:

```rust
Container! {
    when #widget::background_color.0.red == 0.0 {
        mouse::cursor = mouse::CursorIcon::Move;
    }
    widget::background_color = colors::BLUE;
}
```

Current WidgetBuilder can handle delayed assigns, even from a later dynamic source like a Style. Time to go mess with the proc-macros again...

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecec97283..797b852ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,34 @@
 * Add `OPEN_TITLE_BAR_CONTEXT_MENU_CMD` to window API.
 * Fix `WIDGET.border().offsets()` not including the innermost border offset.
 * Add `WindowVars::enabled_buttons` to window API.
+* **Breaking** Fix when/property assign expansion order.
+    - When blocks now expand in the same declaration order, before they always expanded after all property assigns.
+```rust
+// code like this incorrectly builds in v0.4:
+fn version_0_4() -> impl UiNode {
+    let can_move = var(true);
+    Container! {
+        when *#{can_move} {
+            mouse::cursor = mouse::CursorIcon::Move;
+        }
+        mouse::on_mouse_down = hn!(can_move, |_| {
+            let _use = &can_move;
+        });
+    }
+}
+// now in v0.5 the value must be cloned before the last move:
+fn version_0_5() -> impl UiNode {
+    let can_move = var(true);
+    Container! {
+        when *#{can_move.clone()} {
+            mouse::cursor = mouse::CursorIcon::Move;
+        }
+        mouse::on_mouse_down = hn!(|_| {
+            let _use = &can_move;
+        });
+    }
+}
+```
 
 # 0.4.0
 
diff --git a/crates/zng-app-proc-macros/src/widget.rs b/crates/zng-app-proc-macros/src/widget.rs
index 73236684f..648164534 100644
--- a/crates/zng-app-proc-macros/src/widget.rs
+++ b/crates/zng-app-proc-macros/src/widget.rs
@@ -4,7 +4,7 @@ use syn::{ext::IdentExt, parse::Parse, spanned::Spanned, *};
 
 use crate::{
     util::{self, parse_outer_attrs, ErrorRecoverable, Errors},
-    widget_util::{self, WgtProperty, WgtWhen},
+    widget_util::{self, WgtItem, WgtProperty, WgtWhen},
 };
 
 lazy_static! {
@@ -373,14 +373,12 @@ impl Parse for Args {
 
 struct Properties {
     errors: Errors,
-    properties: Vec<WgtProperty>,
-    whens: Vec<WgtWhen>,
+    items: Vec<WgtItem>,
 }
 impl Parse for Properties {
     fn parse(input: parse::ParseStream) -> Result<Self> {
         let mut errors = Errors::default();
-        let mut properties = vec![];
-        let mut whens = vec![];
+        let mut items = vec![];
 
         while !input.is_empty() {
             let attrs = parse_outer_attrs(input, &mut errors);
@@ -388,7 +386,7 @@ impl Parse for Properties {
             if input.peek(widget_util::keyword::when) {
                 if let Some(mut when) = WgtWhen::parse(input, &mut errors) {
                     when.attrs = util::Attributes::new(attrs);
-                    whens.push(when);
+                    items.push(WgtItem::When(when));
                 }
             } else if input.peek(Token![pub])
                 || input.peek(Ident::peek_any)
@@ -413,7 +411,7 @@ impl Parse for Properties {
                                 let _ = input.parse::<TokenTree>();
                             }
                         }
-                        properties.push(p);
+                        items.push(WgtItem::Property(p));
                     }
                     Err(e) => {
                         let (recoverable, e) = e.recoverable();
@@ -432,7 +430,7 @@ impl Parse for Properties {
             }
         }
 
-        Ok(Properties { errors, properties, whens })
+        Ok(Properties { errors, items })
     }
 }
 
@@ -457,114 +455,117 @@ pub fn expand_new(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
 
     let core = util::crate_core();
 
-    let mut set_props = quote!();
-    for prop in &p.properties {
-        set_props.extend(prop_assign(prop, &mut p.errors, false));
-    }
+    let mut items = quote!();
 
-    let mut set_whens = quote!();
-    for when in &p.whens {
-        let when_expr = match syn::parse2::<widget_util::WhenExpr>(when.condition_expr.clone()) {
-            Ok(w) => w,
-            Err(e) => {
-                p.errors.push_syn(e);
-                continue;
+    for item in &p.items {
+        match item {
+            WgtItem::Property(prop) => {
+                items.extend(prop_assign(prop, &mut p.errors, false));
             }
-        };
-
-        let mut when_expr_vars = quote!();
-        let mut inputs = quote!();
-        for ((property, member), var) in when_expr.inputs {
-            let (property, generics) = widget_util::split_path_generics(property).unwrap();
-            let p_ident = &property.segments.last().unwrap().ident;
-            let p_meta = ident_spanned!(p_ident.span()=> "{p_ident}_");
-            let var_input = ident!("{var}_in__");
-            let member_ident = ident_spanned!(property.span()=> "__w_{member}__");
-
-            let member = match member {
-                widget_util::WhenInputMember::Named(ident) => {
-                    let ident_str = ident.to_string();
-                    quote! {
-                        Named(#ident_str)
+            WgtItem::When(when) => {
+                let when_expr = match syn::parse2::<widget_util::WhenExpr>(when.condition_expr.clone()) {
+                    Ok(w) => w,
+                    Err(e) => {
+                        p.errors.push_syn(e);
+                        continue;
                     }
-                }
-                widget_util::WhenInputMember::Index(i) => quote! {
-                    Index(#i)
-                },
-            };
+                };
 
-            macro_rules! quote_call {
-                (#$mtd:ident ( $($args:tt)* )) => {
-                    if property.get_ident().is_some() {
-                        quote! {
-                            wgt__.#$mtd #generics($($args)*);
+                let mut when_expr_vars = quote!();
+                let mut inputs = quote!();
+                for ((property, member), var) in when_expr.inputs {
+                    let (property, generics) = widget_util::split_path_generics(property).unwrap();
+                    let p_ident = &property.segments.last().unwrap().ident;
+                    let p_meta = ident_spanned!(p_ident.span()=> "{p_ident}_");
+                    let var_input = ident!("{var}_in__");
+                    let member_ident = ident_spanned!(property.span()=> "__w_{member}__");
+
+                    let member = match member {
+                        widget_util::WhenInputMember::Named(ident) => {
+                            let ident_str = ident.to_string();
+                            quote! {
+                                Named(#ident_str)
+                            }
                         }
-                    } else {
-                        quote! {
-                            #property::#$mtd #generics(#core::widget::base::WidgetImpl::base(&mut *wgt__), $($args)*);
+                        widget_util::WhenInputMember::Index(i) => quote! {
+                            Index(#i)
+                        },
+                    };
+
+                    macro_rules! quote_call {
+                        (#$mtd:ident ( $($args:tt)* )) => {
+                            if property.get_ident().is_some() {
+                                quote! {
+                                    wgt__.#$mtd #generics($($args)*);
+                                }
+                            } else {
+                                quote! {
+                                    #property::#$mtd #generics(#core::widget::base::WidgetImpl::base(&mut *wgt__), $($args)*);
+                                }
+                            }
                         }
                     }
+
+                    let get_meta = quote_call!(#p_meta());
+
+                    when_expr_vars.extend(quote! {
+                        let (#var_input, #var) = {
+                            let meta__ = #get_meta
+                            meta__.allowed_in_when_expr();
+                            meta__.inputs #generics().#member_ident()
+                        };
+                    });
+
+                    inputs.extend(quote! {
+                        {
+                            let meta__ = #get_meta
+                            #core::widget::builder::WhenInput {
+                                property: meta__.id(),
+                                member: #core::widget::builder::WhenInputMember::#member,
+                                var: #var_input,
+                                property_default: meta__ .default_fn #generics(),
+                            }
+                        },
+                    });
                 }
-            }
 
-            let get_meta = quote_call!(#p_meta());
+                let mut assigns = quote!();
+                for prop in &when.assigns {
+                    assigns.extend(prop_assign(prop, &mut p.errors, true));
+                }
 
-            when_expr_vars.extend(quote! {
-                let (#var_input, #var) = {
-                    let meta__ = #get_meta
-                    meta__.allowed_in_when_expr();
-                    meta__.inputs #generics().#member_ident()
-                };
-            });
+                let attrs = when.attrs.cfg_and_lints();
+                let expr = when_expr.expr;
+                let expr_str = &when.condition_expr_str;
 
-            inputs.extend(quote! {
-                {
-                    let meta__ = #get_meta
-                    #core::widget::builder::WhenInput {
-                        property: meta__.id(),
-                        member: #core::widget::builder::WhenInputMember::#member,
-                        var: #var_input,
-                        property_default: meta__ .default_fn #generics(),
+                let box_expr = quote_spanned! {expr.span()=>
+                    {
+                        let expr_var = #core::var::expr_var!{#expr};
+                        #core::widget::builder::when_condition_expr_var(expr_var)
                     }
-                },
-            });
-        }
-
-        let mut assigns = quote!();
-        for prop in &when.assigns {
-            assigns.extend(prop_assign(prop, &mut p.errors, true));
-        }
-
-        let attrs = when.attrs.cfg_and_lints();
-        let expr = when_expr.expr;
-        let expr_str = &when.condition_expr_str;
-
-        let box_expr = quote_spanned! {expr.span()=>
-            {
-                let expr_var = #core::var::expr_var!{#expr};
-                #core::widget::builder::when_condition_expr_var(expr_var)
-            }
-        };
+                };
 
-        let source_location = widget_util::source_location(&core, Span::call_site());
-        set_whens.extend(quote! {
-            #attrs {
-                #when_expr_vars
-                let inputs__ = std::boxed::Box::new([
-                    #inputs
-                ]);
-                #core::widget::base::WidgetImpl::base(&mut *wgt__).start_when_block(
-                    inputs__,
-                    #box_expr,
-                    #expr_str,
-                    #source_location,
-                );
-
-                #assigns
-
-                #core::widget::base::WidgetImpl::base(&mut *wgt__).end_when_block();
+                let source_location = widget_util::source_location(&core, Span::call_site());
+                items.extend(quote! {
+                    #attrs {
+                        #when_expr_vars
+                        let inputs__ = std::boxed::Box::new([
+                            #inputs
+                        ]);
+                        #core::widget::base::WidgetImpl::base(&mut *wgt__).start_when_block(
+                            inputs__,
+                            #box_expr,
+                            #expr_str,
+                            #source_location,
+                        );
+
+                        #assigns
+
+                        #core::widget::base::WidgetImpl::base(&mut *wgt__).end_when_block();
+                    }
+                });
             }
-        });
+        }
     }
 
     let errors = p.errors;
@@ -574,8 +575,7 @@ pub fn expand_new(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
             #errors
 
             #start
-            #set_props
-            #set_whens
+            #items
             #end
         }
     };
diff --git a/crates/zng-app-proc-macros/src/widget_util.rs b/crates/zng-app-proc-macros/src/widget_util.rs
index 9eab0216e..0c5d08293 100644
--- a/crates/zng-app-proc-macros/src/widget_util.rs
+++ b/crates/zng-app-proc-macros/src/widget_util.rs
@@ -15,6 +15,12 @@ use crate::{
     wgt_property_attrs::PropertyAttrData,
 };
 
+/// Represents a property assign or when block.
+pub enum WgtItem {
+    Property(WgtProperty),
+    When(WgtWhen),
+}
+
 /// Represents a property assign.
 pub struct WgtProperty {
     /// Attributes.
diff --git a/crates/zng-wgt-markdown/src/resolvers.rs b/crates/zng-wgt-markdown/src/resolvers.rs
index fa4ab92c8..cf1f10365 100644
--- a/crates/zng-wgt-markdown/src/resolvers.rs
+++ b/crates/zng-wgt-markdown/src/resolvers.rs
@@ -313,7 +313,7 @@ pub fn try_open_link(args: &LinkArgs) -> bool {
             opacity = 100.pct();
             offset = (0, 0);
         }
-        when *#{status} == Status::Err {
+        when *#{status.clone()} == Status::Err {
             background_color = color_scheme_map(web_colors::DARK_RED.with_alpha(90.pct()), web_colors::PINK.with_alpha(90.pct()));
         }
 
diff --git a/crates/zng/src/lib.rs b/crates/zng/src/lib.rs
index 4fef9cbb3..a16fbe963 100644
--- a/crates/zng/src/lib.rs
+++ b/crates/zng/src/lib.rs
@@ -118,7 +118,7 @@
 //! feature of widgets, they declare conditional property values. The easing attribute can be set in any property with transitionable
 //! values to smoothly animate between changes.
 //!
-//! The [`widget`] module documentation provides an in-depth explanation of how widgets and properties work.
+//! The [`widget`](mod@widget) module documentation provides an in-depth explanation of how widgets and properties work.
 //!
 //! [`Button!`]: struct@button::Button
 //! [`Window!`]: struct@window::Window
@@ -457,7 +457,7 @@
 //!
 //! This crate level documentation only gives an overview required to start making apps using existing widgets and properties.
 //! All top-level modules in this crate contains in-depth documentation about their subject, of particular importance the
-//! [`app`], [`widget`], [`layout`] and [`render`] modules should give you a solid understanding of how everything works.
+//! [`app`], [`widget`](mod@widget), [`layout`] and [`render`] modules should give you a solid understanding of how everything works.
 //!
 //! ## Cargo Features
 //!
diff --git a/examples/button.rs b/examples/button.rs
index d90c8bb94..7dc0a73db 100644
--- a/examples/button.rs
+++ b/examples/button.rs
@@ -344,7 +344,7 @@ fn dyn_button(content: char, remove: impl Fn() + Send + Sync + 'static) -> impl
             margin = 0;
         }
 
-        when *#{removing} {
+        when *#{removing.clone()} {
             widget::interactive = false;
             opacity = 0.pct();
             margin = (0, 0, -30, 0);
diff --git a/examples/window.rs b/examples/window.rs
index b1bb8a91a..22efe179a 100644
--- a/examples/window.rs
+++ b/examples/window.rs
@@ -432,14 +432,14 @@ fn custom_chrome(title: impl Var<Txt>) -> impl UiNode {
         padding = 4;
         corner_radius = (0, 0, 5, 5);
 
-        mouse::on_mouse_down = hn!(can_move, |args: &mouse::MouseInputArgs| {
+        when *#{can_move.clone()} {
+            mouse::cursor = mouse::CursorIcon::Move;
+        }
+        mouse::on_mouse_down = hn!(|args: &mouse::MouseInputArgs| {
             if args.is_primary() && can_move.get() {
                 window::cmd::DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify();
             }
         });
-        when *#{can_move} {
-            mouse::cursor = mouse::CursorIcon::Move;
-        }
 
         gesture::on_context_click = hn!(|args: &gesture::ClickArgs| {
             if matches!(WINDOW.vars().state().get(), WindowState::Normal | WindowState::Maximized) {


Test patch: