From 469a7ac0afc4d63768ab21fb692f0f4008bfe084 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Tue, 11 Jul 2023 21:41:52 +0200 Subject: [PATCH 01/20] better diagnostics for use_shared_state --- Cargo.toml | 3 +- packages/hooks/Cargo.toml | 1 + packages/hooks/src/use_shared_state.rs | 78 ++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d69a74828..4f6561e4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ futures-util = { version = "0.3", default-features = false } rustc-hash = "1.1.0" wasm-bindgen = "0.2.87" html_parser = "0.7.0" +thiserror = "1.0.30" # This is a "virtual package" # It is not meant to be published, but is used so "cargo run --example XYZ" works properly @@ -112,9 +113,9 @@ rand = { version = "0.8.4", features = ["small_rng"] } tokio = { version = "1.16.1", features = ["full"] } reqwest = { version = "0.11.9", features = ["json"] } fern = { version = "0.6.0", features = ["colored"] } -thiserror = "1.0.30" env_logger = "0.10.0" simple_logger = "4.0.0" +thiserror = { workspace = true } [profile.release] opt-level = 3 diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 6a3205c68..198cdb74e 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -14,6 +14,7 @@ keywords = ["dom", "ui", "gui", "react"] dioxus-core = { workspace = true } futures-channel = { workspace = true } log = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 3a3a4d639..c994e4817 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -121,30 +121,96 @@ pub struct UseSharedState { pub(crate) inner: Rc>>, } +#[derive(thiserror::Error, Debug)] +pub enum UseSharedStateError { + #[error("{type_name} is already borrowed, so it cannot be borrowed mutably.")] + AlreadyBorrowed { + source: core::cell::BorrowMutError, + type_name: &'static str, + }, + #[error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore.")] + AlreadyBorrowedMutably { + source: core::cell::BorrowError, + type_name: &'static str, + }, +} + +pub type UseSharedStateResult = Result; + impl UseSharedState { /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") pub fn notify_consumers(&self) { self.inner.borrow_mut().notify_consumers(); } + /// Try reading the shared state + pub fn try_read(&self) -> UseSharedStateResult> { + self.inner + .try_borrow() + .map_err(|source| UseSharedStateError::AlreadyBorrowedMutably { + source, + type_name: std::any::type_name::(), + }) + .map(|value| Ref::map(value, |inner| &inner.value)) + } /// Read the shared value pub fn read(&self) -> Ref<'_, T> { - Ref::map(self.inner.borrow(), |inner| &inner.value) + match self.try_read() { + Ok(value) => value, + Err(message) => panic!( + "Reading the shared state failed: {}\n({:?})", + message, message + ), + } } + /// Try writing the shared state + pub fn try_write(&self) -> UseSharedStateResult> { + self.inner + .try_borrow_mut() + .map_err(|source| UseSharedStateError::AlreadyBorrowed { + source, + type_name: std::any::type_name::(), + }) + .map(|mut value| { + value.notify_consumers(); + RefMut::map(value, |inner| &mut inner.value) + }) + } /// Calling "write" will force the component to re-render /// /// // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock pub fn write(&self) -> RefMut<'_, T> { - let mut value = self.inner.borrow_mut(); - value.notify_consumers(); - RefMut::map(value, |inner| &mut inner.value) + match self.try_write() { + Ok(value) => value, + Err(message) => panic!( + "Writing to shared state failed: {}\n({:?})", + message, message + ), + } } - /// Allows the ability to write the value without forcing a re-render + /// Tries writing the value without forcing a re-render + pub fn try_write_silent(&self) -> UseSharedStateResult> { + self.inner + .try_borrow_mut() + .map_err(|source| UseSharedStateError::AlreadyBorrowed { + source, + type_name: std::any::type_name::(), + }) + .map(|value| RefMut::map(value, |inner| &mut inner.value)) + } + + /// Writes the value without forcing a re-render pub fn write_silent(&self) -> RefMut<'_, T> { - RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value) + match self.try_write_silent() { + Ok(value) => value, + Err(message) => panic!( + "Writing to shared state silently failed: {}\n({:?})", + message, message + ), + } } } From f159779af4b33e9d686f610b76e7ae1b7a62c9eb Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Tue, 11 Jul 2023 22:54:24 +0200 Subject: [PATCH 02/20] make thiserror a workspace dependency everywhere else --- Cargo.toml | 2 +- packages/cli/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/dioxus/Cargo.toml | 2 +- packages/fullstack/Cargo.toml | 2 +- packages/hooks/src/use_shared_state.rs | 2 ++ packages/liveview/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/ssr/Cargo.toml | 2 +- 9 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f6561e4d..13e8ee2ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ futures-util = { version = "0.3", default-features = false } rustc-hash = "1.1.0" wasm-bindgen = "0.2.87" html_parser = "0.7.0" -thiserror = "1.0.30" +thiserror = "1.0.40" # This is a "virtual package" # It is not meant to be published, but is used so "cargo run --example XYZ" works properly diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 7e5e60eed..5fa8827b1 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT/Apache-2.0" [dependencies] # cli core clap = { version = "4.2", features = ["derive"] } -thiserror = "1.0.30" +thiserror = { workspace = true } wasm-bindgen-cli-support = "0.2" colored = "2.0.0" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 05c746a52..c537aaed8 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -17,7 +17,7 @@ dioxus-hot-reload = { workspace = true, optional = true } serde = "1.0.136" serde_json = "1.0.79" -thiserror = "1.0.30" +thiserror = { workspace = true } log = { workspace = true } wry = { version = "0.28.0" } futures-channel = { workspace = true } diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index b6bfe6536..fedf65894 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -33,7 +33,7 @@ futures-util = { workspace = true } log = { workspace = true } rand = { version = "0.8.4", features = ["small_rng"] } criterion = "0.3.5" -thiserror = "1.0.30" +thiserror = { workspace = true } env_logger = "0.10.0" tokio = { workspace = true, features = ["full"] } # dioxus-edit-stream = { workspace = true } diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index a6a1d29fe..ef3337c67 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -34,7 +34,7 @@ http = { version = "0.2.9", optional = true } log = { workspace = true } once_cell = "1.17.1" -thiserror = "1.0.40" +thiserror = { workspace = true } tokio = { workspace = true, features = ["full"], optional = true } object-pool = "0.5.4" anymap = "0.12.1" diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index c994e4817..a5762523e 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -153,6 +153,7 @@ impl UseSharedState { }) .map(|value| Ref::map(value, |inner| &inner.value)) } + /// Read the shared value pub fn read(&self) -> Ref<'_, T> { match self.try_read() { @@ -177,6 +178,7 @@ impl UseSharedState { RefMut::map(value, |inner| &mut inner.value) }) } + /// Calling "write" will force the component to re-render /// /// diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 28a4e4a06..210b72b53 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -9,7 +9,7 @@ description = "Build server-side apps with Dioxus" license = "MIT/Apache-2.0" [dependencies] -thiserror = "1.0.38" +thiserror = { workspace = true } log = { workspace = true } slab = { workspace = true } futures-util = { workspace = true, default-features = false, features = [ diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 7015c3243..2a0afbc20 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -30,7 +30,7 @@ wasm-bindgen = { workspace = true, optional = true } js-sys = { version = "0.3", optional = true } gloo-events = { version = "0.1.1", optional = true } log = { workspace = true } -thiserror = "1.0.30" +thiserror = { workspace = true } futures-util = { workspace = true } serde = { version = "1", optional = true } serde_urlencoded = { version = "0.7.1", optional = true } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 98e127be0..e91ee25eb 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -15,7 +15,7 @@ askama_escape = "0.10.3" [dev-dependencies] dioxus = { workspace = true } -thiserror = "1.0.23" +thiserror = { workspace = true } log = { workspace = true } fern = { version = "0.6.0", features = ["colored"] } anyhow = "1.0" From 43e3304eaf4c3bc640e30cde0ab0091595925963 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 01:19:20 +0200 Subject: [PATCH 03/20] track caller for easier debugging --- packages/hooks/src/use_shared_state.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index a5762523e..753f194fa 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -2,6 +2,7 @@ use dioxus_core::{ScopeId, ScopeState}; use std::{ cell::{Ref, RefCell, RefMut}, collections::HashSet, + panic::Location, rc::Rc, sync::Arc, }; @@ -123,15 +124,17 @@ pub struct UseSharedState { #[derive(thiserror::Error, Debug)] pub enum UseSharedStateError { - #[error("{type_name} is already borrowed, so it cannot be borrowed mutably.")] + #[error("[{caller}] {type_name} is already borrowed, so it cannot be borrowed mutably.")] AlreadyBorrowed { source: core::cell::BorrowMutError, type_name: &'static str, + caller: &'static Location<'static>, }, - #[error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore.")] + #[error("[caller] {type_name} is already borrowed mutably, so it cannot be borrowed anymore.")] AlreadyBorrowedMutably { source: core::cell::BorrowError, type_name: &'static str, + caller: &'static Location<'static>, }, } @@ -144,17 +147,20 @@ impl UseSharedState { } /// Try reading the shared state + #[track_caller] pub fn try_read(&self) -> UseSharedStateResult> { self.inner .try_borrow() .map_err(|source| UseSharedStateError::AlreadyBorrowedMutably { source, type_name: std::any::type_name::(), + caller: Location::caller(), }) .map(|value| Ref::map(value, |inner| &inner.value)) } /// Read the shared value + #[track_caller] pub fn read(&self) -> Ref<'_, T> { match self.try_read() { Ok(value) => value, @@ -166,12 +172,14 @@ impl UseSharedState { } /// Try writing the shared state + #[track_caller] pub fn try_write(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() .map_err(|source| UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), + caller: Location::caller(), }) .map(|mut value| { value.notify_consumers(); @@ -183,6 +191,7 @@ impl UseSharedState { /// /// // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock + #[track_caller] pub fn write(&self) -> RefMut<'_, T> { match self.try_write() { Ok(value) => value, @@ -194,17 +203,20 @@ impl UseSharedState { } /// Tries writing the value without forcing a re-render + #[track_caller] pub fn try_write_silent(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() .map_err(|source| UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), + caller: Location::caller(), }) .map(|value| RefMut::map(value, |inner| &mut inner.value)) } /// Writes the value without forcing a re-render + #[track_caller] pub fn write_silent(&self) -> RefMut<'_, T> { match self.try_write_silent() { Ok(value) => value, From b8c14ce35c1d7a7acab3a9f7e7936a3498c093ea Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 01:38:50 +0200 Subject: [PATCH 04/20] make location diagnostics optional (only available in debug mode) --- packages/hooks/src/use_shared_state.rs | 75 ++++++++++++++++++-------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 753f194fa..311f9a87f 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -1,12 +1,61 @@ +use self::error::{UseSharedStateError, UseSharedStateResult}; use dioxus_core::{ScopeId, ScopeState}; use std::{ cell::{Ref, RefCell, RefMut}, collections::HashSet, - panic::Location, rc::Rc, sync::Arc, }; +pub mod error { + use std::panic::Location; + + #[macro_export] + macro_rules! debug_location { + () => {{ + #[cfg(debug_assertions)] + { + Some(std::panic::Location::caller()) + } + #[cfg(not(debug_assertions))] + { + None + } + }}; + } + fn display_location(location: Option<&&Location<'_>>) -> String { + location + .map(|location| format!("[{location}] ")) + .unwrap_or_else(String::new) + } + + #[derive(thiserror::Error, Debug)] + pub enum UseSharedStateError { + #[error( + "{}{type_name} is already borrowed, so it cannot be borrowed mutably.", + display_location(.location.as_ref()) + )] + AlreadyBorrowed { + source: core::cell::BorrowMutError, + type_name: &'static str, + /// Only available in debug mode + location: Option<&'static Location<'static>>, + }, + #[error( + "{}{type_name} is already borrowed mutably, so it cannot be borrowed anymore.", + display_location(.location.as_ref()) + )] + AlreadyBorrowedMutably { + source: core::cell::BorrowError, + type_name: &'static str, + /// Only available in debug mode + location: Option<&'static Location<'static>>, + }, + } + + pub type UseSharedStateResult = Result; +} + type ProvidedState = Rc>>; // Tracks all the subscribers to a shared State @@ -122,24 +171,6 @@ pub struct UseSharedState { pub(crate) inner: Rc>>, } -#[derive(thiserror::Error, Debug)] -pub enum UseSharedStateError { - #[error("[{caller}] {type_name} is already borrowed, so it cannot be borrowed mutably.")] - AlreadyBorrowed { - source: core::cell::BorrowMutError, - type_name: &'static str, - caller: &'static Location<'static>, - }, - #[error("[caller] {type_name} is already borrowed mutably, so it cannot be borrowed anymore.")] - AlreadyBorrowedMutably { - source: core::cell::BorrowError, - type_name: &'static str, - caller: &'static Location<'static>, - }, -} - -pub type UseSharedStateResult = Result; - impl UseSharedState { /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") pub fn notify_consumers(&self) { @@ -154,7 +185,7 @@ impl UseSharedState { .map_err(|source| UseSharedStateError::AlreadyBorrowedMutably { source, type_name: std::any::type_name::(), - caller: Location::caller(), + location: crate::debug_location!(), }) .map(|value| Ref::map(value, |inner| &inner.value)) } @@ -179,7 +210,7 @@ impl UseSharedState { .map_err(|source| UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), - caller: Location::caller(), + location: crate::debug_location!(), }) .map(|mut value| { value.notify_consumers(); @@ -210,7 +241,7 @@ impl UseSharedState { .map_err(|source| UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), - caller: Location::caller(), + location: crate::debug_location!(), }) .map(|value| RefMut::map(value, |inner| &mut inner.value)) } From 9da32d54651c6e958a1bc283f8e8378a59dbaee2 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 01:53:00 +0200 Subject: [PATCH 05/20] oh yeah, tracking itself should also be debug-only --- packages/hooks/src/use_shared_state.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 311f9a87f..fbf16f790 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -178,7 +178,7 @@ impl UseSharedState { } /// Try reading the shared state - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn try_read(&self) -> UseSharedStateResult> { self.inner .try_borrow() @@ -191,7 +191,7 @@ impl UseSharedState { } /// Read the shared value - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn read(&self) -> Ref<'_, T> { match self.try_read() { Ok(value) => value, @@ -203,7 +203,7 @@ impl UseSharedState { } /// Try writing the shared state - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn try_write(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() @@ -222,7 +222,7 @@ impl UseSharedState { /// /// // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn write(&self) -> RefMut<'_, T> { match self.try_write() { Ok(value) => value, @@ -234,7 +234,7 @@ impl UseSharedState { } /// Tries writing the value without forcing a re-render - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn try_write_silent(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() @@ -247,7 +247,7 @@ impl UseSharedState { } /// Writes the value without forcing a re-render - #[track_caller] + #[cfg_attr(debug_assertions, track_caller)] pub fn write_silent(&self) -> RefMut<'_, T> { match self.try_write_silent() { Ok(value) => value, From 760d93716faad8240c92f1cf5333746b9fa9703c Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 02:53:48 +0200 Subject: [PATCH 06/20] also track previous caller in the in debug mode to show the conflicting borrow --- packages/hooks/Cargo.toml | 1 + packages/hooks/src/use_shared_state.rs | 157 ++++++++++++++++++++----- 2 files changed, 126 insertions(+), 32 deletions(-) diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 198cdb74e..b2dd4898f 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -21,3 +21,4 @@ thiserror = { workspace = true } futures-util = { workspace = true, default-features = false } dioxus-core = { workspace = true } dioxus = { workspace = true } +debug-cell = "0.1.1" diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index fbf16f790..8e9a05c29 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -7,49 +7,117 @@ use std::{ sync::Arc, }; -pub mod error { +#[cfg(not(debug_assertions))] +type Location = (); + +#[cfg(debug_assertions)] +type Location = &'static std::panic::Location<'static>; + +#[macro_export] +macro_rules! debug_location { + () => {{ + #[cfg(debug_assertions)] + { + std::panic::Location::caller() + } + #[cfg(not(debug_assertions))] + { + () + } + }}; +} + +pub mod diagnostics { use std::panic::Location; - #[macro_export] - macro_rules! debug_location { - () => {{ - #[cfg(debug_assertions)] - { - Some(std::panic::Location::caller()) - } - #[cfg(not(debug_assertions))] - { - None - } - }}; - } - fn display_location(location: Option<&&Location<'_>>) -> String { - location - .map(|location| format!("[{location}] ")) - .unwrap_or_else(String::new) + #[derive(Debug, Clone, Copy)] + pub enum BorrowKind { + Mutable, + Immutable, } + #[derive(Debug, Clone, Copy)] + pub struct PreviousBorrow { + pub location: Location<'static>, + pub kind: BorrowKind, + } + + impl super::UseSharedState { + #[cfg_attr(debug_assertions, track_caller)] + #[allow(unused_must_use)] + pub(super) fn debug_track_borrow(&self) { + #[cfg(debug_assertions)] + self.previous_borrow + .borrow_mut() + .insert(PreviousBorrow::borrowed()); + } + + #[cfg_attr(debug_assertions, track_caller)] + #[allow(unused_must_use)] + pub(super) fn debug_track_borrow_mut(&self) { + #[cfg(debug_assertions)] + self.previous_borrow + .borrow_mut() + .insert(PreviousBorrow::borrowed_mut()); + } + } + impl PreviousBorrow { + #[track_caller] + pub fn borrowed() -> Self { + Self { + location: *Location::caller(), + kind: BorrowKind::Immutable, + } + } + + #[track_caller] + pub fn borrowed_mut() -> Self { + Self { + location: *Location::caller(), + kind: BorrowKind::Mutable, + } + } + } +} + +pub mod error { #[derive(thiserror::Error, Debug)] pub enum UseSharedStateError { - #[error( - "{}{type_name} is already borrowed, so it cannot be borrowed mutably.", - display_location(.location.as_ref()) - )] + #[cfg_attr( + debug_assertions, + error( + "[{location}] {type_name} is already borrowed at [{previous_borrow:?}], so it cannot be borrowed mutably." + ) + )] + #[cfg_attr( + not(debug_assertions), + error("{type_name} is already borrowed, so it cannot be borrowed mutably.") + )] AlreadyBorrowed { source: core::cell::BorrowMutError, type_name: &'static str, /// Only available in debug mode - location: Option<&'static Location<'static>>, + location: super::Location, + #[cfg(debug_assertions)] + previous_borrow: Option, }, - #[error( - "{}{type_name} is already borrowed mutably, so it cannot be borrowed anymore.", - display_location(.location.as_ref()) - )] + #[cfg_attr( + debug_assertions, + error( + "[{location}] {type_name} is already borrowed mutably at [{previous_borrow:?}], so it cannot be borrowed anymore." + ) + )] + #[cfg_attr( + not(debug_assertions), + error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore.") + )] AlreadyBorrowedMutably { source: core::cell::BorrowError, type_name: &'static str, /// Only available in debug mode - location: Option<&'static Location<'static>>, + location: super::Location, + #[cfg(debug_assertions)] + previous_borrow: Option, }, } @@ -145,7 +213,7 @@ pub fn use_shared_state(cx: &ScopeState) -> Option<&UseSharedState Drop for UseSharedStateOwner { /// State that is shared between components through the context system pub struct UseSharedState { pub(crate) inner: Rc>>, + #[cfg(debug_assertions)] + previous_borrow: Rc>>, } impl UseSharedState { + fn new(inner: Rc>>) -> Self { + Self { + inner, + #[cfg(debug_assertions)] + previous_borrow: Default::default(), + } + } + /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") pub fn notify_consumers(&self) { self.inner.borrow_mut().notify_consumers(); @@ -185,9 +263,14 @@ impl UseSharedState { .map_err(|source| UseSharedStateError::AlreadyBorrowedMutably { source, type_name: std::any::type_name::(), - location: crate::debug_location!(), + location: debug_location!(), + #[cfg(debug_assertions)] + previous_borrow: *self.previous_borrow.borrow(), + }) + .map(|value| { + self.debug_track_borrow(); + Ref::map(value, |inner| &inner.value) }) - .map(|value| Ref::map(value, |inner| &inner.value)) } /// Read the shared value @@ -211,9 +294,12 @@ impl UseSharedState { source, type_name: std::any::type_name::(), location: crate::debug_location!(), + #[cfg(debug_assertions)] + previous_borrow: *self.previous_borrow.borrow(), }) .map(|mut value| { value.notify_consumers(); + self.debug_track_borrow_mut(); RefMut::map(value, |inner| &mut inner.value) }) } @@ -242,8 +328,13 @@ impl UseSharedState { source, type_name: std::any::type_name::(), location: crate::debug_location!(), + #[cfg(debug_assertions)] + previous_borrow: *self.previous_borrow.borrow(), + }) + .map(|value| { + self.debug_track_borrow_mut(); + RefMut::map(value, |inner| &mut inner.value) }) - .map(|value| RefMut::map(value, |inner| &mut inner.value)) } /// Writes the value without forcing a re-render @@ -263,6 +354,8 @@ impl Clone for UseSharedState { fn clone(&self) -> Self { Self { inner: self.inner.clone(), + #[cfg(debug_assertions)] + previous_borrow: Default::default(), } } } From 3fa519563e08f9fcd09af8304d59874d717932f3 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 12:27:17 +0200 Subject: [PATCH 07/20] prevent inlining --- packages/hooks/src/use_shared_state.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 8e9a05c29..cc1679b09 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -44,6 +44,7 @@ pub mod diagnostics { impl super::UseSharedState { #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] #[allow(unused_must_use)] pub(super) fn debug_track_borrow(&self) { #[cfg(debug_assertions)] @@ -53,6 +54,7 @@ pub mod diagnostics { } #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] #[allow(unused_must_use)] pub(super) fn debug_track_borrow_mut(&self) { #[cfg(debug_assertions)] @@ -63,6 +65,7 @@ pub mod diagnostics { } impl PreviousBorrow { #[track_caller] + #[inline(never)] pub fn borrowed() -> Self { Self { location: *Location::caller(), @@ -71,6 +74,7 @@ pub mod diagnostics { } #[track_caller] + #[inline(never)] pub fn borrowed_mut() -> Self { Self { location: *Location::caller(), @@ -257,6 +261,7 @@ impl UseSharedState { /// Try reading the shared state #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn try_read(&self) -> UseSharedStateResult> { self.inner .try_borrow() @@ -275,6 +280,7 @@ impl UseSharedState { /// Read the shared value #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn read(&self) -> Ref<'_, T> { match self.try_read() { Ok(value) => value, @@ -287,6 +293,7 @@ impl UseSharedState { /// Try writing the shared state #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn try_write(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() @@ -309,6 +316,7 @@ impl UseSharedState { /// // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn write(&self) -> RefMut<'_, T> { match self.try_write() { Ok(value) => value, @@ -321,6 +329,7 @@ impl UseSharedState { /// Tries writing the value without forcing a re-render #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn try_write_silent(&self) -> UseSharedStateResult> { self.inner .try_borrow_mut() @@ -339,6 +348,7 @@ impl UseSharedState { /// Writes the value without forcing a re-render #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn write_silent(&self) -> RefMut<'_, T> { match self.try_write_silent() { Ok(value) => value, From af091c5bf82b7edce0d1ad846011ffa5dea63189 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 16:20:19 +0200 Subject: [PATCH 08/20] turns out Result combinators are not marked with #[trace_caller], so it must be written without function nesting in order to work --- packages/hooks/src/use_shared_state.rs | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index cc1679b09..5ab88b7bd 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -263,19 +263,19 @@ impl UseSharedState { #[cfg_attr(debug_assertions, track_caller)] #[cfg_attr(debug_assertions, inline(never))] pub fn try_read(&self) -> UseSharedStateResult> { - self.inner - .try_borrow() - .map_err(|source| UseSharedStateError::AlreadyBorrowedMutably { + match self.inner.try_borrow() { + Ok(value) => { + self.debug_track_borrow(); + Ok(Ref::map(value, |inner| &inner.value)) + } + Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably { source, type_name: std::any::type_name::(), location: debug_location!(), #[cfg(debug_assertions)] previous_borrow: *self.previous_borrow.borrow(), - }) - .map(|value| { - self.debug_track_borrow(); - Ref::map(value, |inner| &inner.value) - }) + }), + } } /// Read the shared value @@ -295,20 +295,20 @@ impl UseSharedState { #[cfg_attr(debug_assertions, track_caller)] #[cfg_attr(debug_assertions, inline(never))] pub fn try_write(&self) -> UseSharedStateResult> { - self.inner - .try_borrow_mut() - .map_err(|source| UseSharedStateError::AlreadyBorrowed { + match self.inner.try_borrow_mut() { + Ok(mut value) => { + self.debug_track_borrow_mut(); + value.notify_consumers(); + Ok(RefMut::map(value, |inner| &mut inner.value)) + } + Err(source) => Err(UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), location: crate::debug_location!(), #[cfg(debug_assertions)] previous_borrow: *self.previous_borrow.borrow(), - }) - .map(|mut value| { - value.notify_consumers(); - self.debug_track_borrow_mut(); - RefMut::map(value, |inner| &mut inner.value) - }) + }), + } } /// Calling "write" will force the component to re-render @@ -331,19 +331,19 @@ impl UseSharedState { #[cfg_attr(debug_assertions, track_caller)] #[cfg_attr(debug_assertions, inline(never))] pub fn try_write_silent(&self) -> UseSharedStateResult> { - self.inner - .try_borrow_mut() - .map_err(|source| UseSharedStateError::AlreadyBorrowed { + match self.inner.try_borrow_mut() { + Ok(value) => { + self.debug_track_borrow_mut(); + Ok(RefMut::map(value, |inner| &mut inner.value)) + } + Err(source) => Err(UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), location: crate::debug_location!(), #[cfg(debug_assertions)] previous_borrow: *self.previous_borrow.borrow(), - }) - .map(|value| { - self.debug_track_borrow_mut(); - RefMut::map(value, |inner| &mut inner.value) - }) + }), + } } /// Writes the value without forcing a re-render From 9154b0f8ea57b2b46a167c6c508fbea11e0ae8f8 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 16:24:39 +0200 Subject: [PATCH 09/20] pass the information on previous caller on clone --- packages/hooks/src/use_shared_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 5ab88b7bd..c1791afed 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -365,7 +365,7 @@ impl Clone for UseSharedState { Self { inner: self.inner.clone(), #[cfg(debug_assertions)] - previous_borrow: Default::default(), + previous_borrow: self.previous_borrow.clone(), } } } From 60d6137f3901fab12999f4345ea99356a8be1ef6 Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Wed, 12 Jul 2023 16:36:17 +0200 Subject: [PATCH 10/20] better display for locations in panic messages --- packages/hooks/src/use_shared_state.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index c1791afed..cd5909d57 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -42,6 +42,22 @@ pub mod diagnostics { pub kind: BorrowKind, } + impl PreviousBorrow { + pub fn display_opt(value: &Option) -> String { + value + .as_ref() + .map(|value| value.to_string()) + .unwrap_or_default() + } + } + + impl std::fmt::Display for PreviousBorrow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { location, kind } = self; + write!(f, "{location} ({kind:?})") + } + } + impl super::UseSharedState { #[cfg_attr(debug_assertions, track_caller)] #[cfg_attr(debug_assertions, inline(never))] @@ -90,7 +106,8 @@ pub mod error { #[cfg_attr( debug_assertions, error( - "[{location}] {type_name} is already borrowed at [{previous_borrow:?}], so it cannot be borrowed mutably." + "[{location}] {type_name} is already borrowed at [{previous_borrow}], so it cannot be borrowed mutably.", + previous_borrow = super::diagnostics::PreviousBorrow::display_opt(.previous_borrow) ) )] #[cfg_attr( @@ -108,7 +125,8 @@ pub mod error { #[cfg_attr( debug_assertions, error( - "[{location}] {type_name} is already borrowed mutably at [{previous_borrow:?}], so it cannot be borrowed anymore." + "[{location}] {type_name} is already borrowed mutably at [{previous_borrow}], so it cannot be borrowed anymore.", + previous_borrow = super::diagnostics::PreviousBorrow::display_opt(.previous_borrow) ) )] #[cfg_attr( From 337697a8866c524df503b099ed060fb808615cdd Mon Sep 17 00:00:00 2001 From: niedzwiedzw Date: Thu, 13 Jul 2023 20:12:28 +0200 Subject: [PATCH 11/20] test with forked version of debug-cell --- packages/hooks/Cargo.toml | 6 +- packages/hooks/src/lib.rs | 2 + packages/hooks/src/use_shared_state.rs | 154 +++++-------------------- 3 files changed, 35 insertions(+), 127 deletions(-) diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index b2dd4898f..7bb40c5f3 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -9,16 +9,18 @@ repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] +[features] +default = [] +nightly-features = [] [dependencies] dioxus-core = { workspace = true } futures-channel = { workspace = true } log = { workspace = true } thiserror = { workspace = true } - +debug-cell = { git = "https://github.com/Niedzwiedzw/debug-cell", rev = "3352a1f8aff19f56f5e3b2018200a3338fd43d2e" } # waiting for the merge / official DioxusLabs fork [dev-dependencies] futures-util = { workspace = true, default-features = false } dioxus-core = { workspace = true } dioxus = { workspace = true } -debug-cell = "0.1.1" diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 21795a9d6..9589c5f72 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "nightly-features", feature(debug_refcell))] + #[macro_export] /// A helper macro for using hooks and properties in async environements. /// diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index cd5909d57..bde1fabb8 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -1,17 +1,15 @@ use self::error::{UseSharedStateError, UseSharedStateResult}; use dioxus_core::{ScopeId, ScopeState}; -use std::{ - cell::{Ref, RefCell, RefMut}, - collections::HashSet, - rc::Rc, - sync::Arc, +use std::{collections::HashSet, rc::Rc, sync::Arc}; + +#[cfg(debug_assertions)] +pub use debug_cell::{ + error::{BorrowError, BorrowMutError}, + Ref, RefCell, RefMut, }; #[cfg(not(debug_assertions))] -type Location = (); - -#[cfg(debug_assertions)] -type Location = &'static std::panic::Location<'static>; +pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; #[macro_export] macro_rules! debug_location { @@ -27,119 +25,49 @@ macro_rules! debug_location { }}; } -pub mod diagnostics { - use std::panic::Location; - - #[derive(Debug, Clone, Copy)] - pub enum BorrowKind { - Mutable, - Immutable, - } - - #[derive(Debug, Clone, Copy)] - pub struct PreviousBorrow { - pub location: Location<'static>, - pub kind: BorrowKind, - } - - impl PreviousBorrow { - pub fn display_opt(value: &Option) -> String { - value - .as_ref() - .map(|value| value.to_string()) - .unwrap_or_default() - } - } - - impl std::fmt::Display for PreviousBorrow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { location, kind } = self; - write!(f, "{location} ({kind:?})") - } - } - - impl super::UseSharedState { - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - #[allow(unused_must_use)] - pub(super) fn debug_track_borrow(&self) { - #[cfg(debug_assertions)] - self.previous_borrow - .borrow_mut() - .insert(PreviousBorrow::borrowed()); - } - - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - #[allow(unused_must_use)] - pub(super) fn debug_track_borrow_mut(&self) { - #[cfg(debug_assertions)] - self.previous_borrow - .borrow_mut() - .insert(PreviousBorrow::borrowed_mut()); - } - } - impl PreviousBorrow { - #[track_caller] - #[inline(never)] - pub fn borrowed() -> Self { - Self { - location: *Location::caller(), - kind: BorrowKind::Immutable, - } - } - - #[track_caller] - #[inline(never)] - pub fn borrowed_mut() -> Self { - Self { - location: *Location::caller(), - kind: BorrowKind::Mutable, - } - } - } -} - pub mod error { + fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String { + locations + .iter() + .map(|location| format!(" - {location}")) + .collect::>() + .join("\n") + } #[derive(thiserror::Error, Debug)] pub enum UseSharedStateError { #[cfg_attr( debug_assertions, error( - "[{location}] {type_name} is already borrowed at [{previous_borrow}], so it cannot be borrowed mutably.", - previous_borrow = super::diagnostics::PreviousBorrow::display_opt(.previous_borrow) + "[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n", + .source.attempted_at, + .type_name, + locations_display(&.source.already_borrowed_at) ) )] #[cfg_attr( not(debug_assertions), - error("{type_name} is already borrowed, so it cannot be borrowed mutably.") + error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)") )] AlreadyBorrowed { - source: core::cell::BorrowMutError, + source: super::BorrowMutError, type_name: &'static str, - /// Only available in debug mode - location: super::Location, - #[cfg(debug_assertions)] - previous_borrow: Option, }, #[cfg_attr( debug_assertions, error( - "[{location}] {type_name} is already borrowed mutably at [{previous_borrow}], so it cannot be borrowed anymore.", - previous_borrow = super::diagnostics::PreviousBorrow::display_opt(.previous_borrow) + "[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.", + .source.attempted_at, + .type_name, + locations_display(&.source.already_borrowed_at) ) )] #[cfg_attr( not(debug_assertions), - error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore.") + error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)") )] AlreadyBorrowedMutably { - source: core::cell::BorrowError, + source: super::BorrowError, type_name: &'static str, - /// Only available in debug mode - location: super::Location, - #[cfg(debug_assertions)] - previous_borrow: Option, }, } @@ -259,17 +187,11 @@ impl Drop for UseSharedStateOwner { /// State that is shared between components through the context system pub struct UseSharedState { pub(crate) inner: Rc>>, - #[cfg(debug_assertions)] - previous_borrow: Rc>>, } impl UseSharedState { fn new(inner: Rc>>) -> Self { - Self { - inner, - #[cfg(debug_assertions)] - previous_borrow: Default::default(), - } + Self { inner } } /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") @@ -282,16 +204,10 @@ impl UseSharedState { #[cfg_attr(debug_assertions, inline(never))] pub fn try_read(&self) -> UseSharedStateResult> { match self.inner.try_borrow() { - Ok(value) => { - self.debug_track_borrow(); - Ok(Ref::map(value, |inner| &inner.value)) - } + Ok(value) => Ok(Ref::map(value, |inner| &inner.value)), Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably { source, type_name: std::any::type_name::(), - location: debug_location!(), - #[cfg(debug_assertions)] - previous_borrow: *self.previous_borrow.borrow(), }), } } @@ -315,16 +231,12 @@ impl UseSharedState { pub fn try_write(&self) -> UseSharedStateResult> { match self.inner.try_borrow_mut() { Ok(mut value) => { - self.debug_track_borrow_mut(); value.notify_consumers(); Ok(RefMut::map(value, |inner| &mut inner.value)) } Err(source) => Err(UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), - location: crate::debug_location!(), - #[cfg(debug_assertions)] - previous_borrow: *self.previous_borrow.borrow(), }), } } @@ -350,16 +262,10 @@ impl UseSharedState { #[cfg_attr(debug_assertions, inline(never))] pub fn try_write_silent(&self) -> UseSharedStateResult> { match self.inner.try_borrow_mut() { - Ok(value) => { - self.debug_track_borrow_mut(); - Ok(RefMut::map(value, |inner| &mut inner.value)) - } + Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)), Err(source) => Err(UseSharedStateError::AlreadyBorrowed { source, type_name: std::any::type_name::(), - location: crate::debug_location!(), - #[cfg(debug_assertions)] - previous_borrow: *self.previous_borrow.borrow(), }), } } @@ -382,8 +288,6 @@ impl Clone for UseSharedState { fn clone(&self) -> Self { Self { inner: self.inner.clone(), - #[cfg(debug_assertions)] - previous_borrow: self.previous_borrow.clone(), } } } From 3d8d8a1b6f5b197205293a697a79a64e17b5bd00 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 10:19:23 -0700 Subject: [PATCH 12/20] half-build-times --- packages/autofmt/Cargo.toml | 4 +- packages/autofmt/src/collect_macros.rs | 21 +- packages/autofmt/src/component.rs | 2 +- packages/autofmt/src/lib.rs | 6 +- packages/autofmt/src/writer.rs | 2 +- packages/cli/Cargo.toml | 2 +- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/src/inlineprops.rs | 2 +- packages/core-macro/src/props/mod.rs | 64 ++-- packages/hot-reload/Cargo.toml | 18 +- packages/hot-reload/src/file_watcher.rs | 352 +++++++++++++++++ packages/hot-reload/src/lib.rs | 356 +----------------- packages/html/Cargo.toml | 2 +- packages/native-core-macro/Cargo.toml | 2 +- packages/native-core-macro/src/lib.rs | 2 +- packages/router-macro/Cargo.toml | 2 +- packages/router-macro/src/lib.rs | 14 +- packages/router-macro/src/route.rs | 6 +- packages/rsx-rosetta/Cargo.toml | 2 +- packages/rsx/Cargo.toml | 12 +- packages/rsx/src/error.rs | 13 +- .../rsx/src/hot_reload/hot_reload_diff.rs | 39 +- packages/rsx/src/lib.rs | 18 +- packages/rsx/src/node.rs | 2 +- 24 files changed, 489 insertions(+), 456 deletions(-) create mode 100644 packages/hot-reload/src/file_watcher.rs diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index 254b60093..c658a9f42 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -14,9 +14,9 @@ keywords = ["dom", "ui", "gui", "react"] dioxus-rsx = { workspace = true } proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" -syn = { version = "1.0.11", features = ["full", "extra-traits"] } +syn = { version = "2.0", features = ["full", "extra-traits"] } serde = { version = "1.0.136", features = ["derive"] } -prettyplease = { package = "prettier-please", version = "0.1.16", features = [ +prettyplease = { package = "prettier-please", version = "0.2", features = [ "verbatim", ] } diff --git a/packages/autofmt/src/collect_macros.rs b/packages/autofmt/src/collect_macros.rs index ebb6314e2..9c65fae39 100644 --- a/packages/autofmt/src/collect_macros.rs +++ b/packages/autofmt/src/collect_macros.rs @@ -5,6 +5,8 @@ use proc_macro2::LineColumn; use syn::{Block, Expr, File, Item, Macro, Stmt}; +use crate::expr; + type CollectedMacro<'a> = &'a Macro; pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec>) { @@ -42,7 +44,7 @@ pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec } // None of these we can really do anything with at the item level - Item::Macro2(_) + Item::Macro(_) | Item::Enum(_) | Item::ExternCrate(_) | Item::ForeignMod(_) @@ -61,11 +63,18 @@ pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec collect_from_item(item, macros), Stmt::Local(local) => { - if let Some((_eq, init)) = &local.init { - collect_from_expr(init, macros); + if let Some(init) = &local.init { + collect_from_expr(&init.expr, macros); + } + } + Stmt::Expr(expr, _) => collect_from_expr(expr, macros), + Stmt::Macro(mac) => { + if mac.mac.path.segments[0].ident == "rsx" + || mac.mac.path.segments[0].ident == "render" + { + macros.push(&mac.mac); } } - Stmt::Expr(exp) | Stmt::Semi(exp, _) => collect_from_expr(exp, macros), } } } @@ -147,10 +156,9 @@ pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec // don't both formatting these for now Expr::Array(_) - | Expr::AssignOp(_) + | Expr::Assign(_) | Expr::Await(_) | Expr::Binary(_) - | Expr::Box(_) | Expr::Break(_) | Expr::Cast(_) | Expr::Continue(_) @@ -167,7 +175,6 @@ pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec | Expr::Try(_) | Expr::TryBlock(_) | Expr::Tuple(_) - | Expr::Type(_) | Expr::Unary(_) | Expr::Verbatim(_) => {} diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index d5d92b79a..7b507e7bd 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -176,7 +176,7 @@ impl Writer<'_> { )?; } ContentField::OnHandlerRaw(exp) => { - let out = prettyplease::unparse_expr(exp); + let out = prettyplease::unparse(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); write!(self.out, "{name}: {first}")?; diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 0d2426a0b..f878a800a 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -88,9 +88,9 @@ pub fn fmt_file(contents: &str) -> Vec { } let span = match item.delimiter { - MacroDelimiter::Paren(b) => b.span, - MacroDelimiter::Brace(b) => b.span, - MacroDelimiter::Bracket(b) => b.span, + MacroDelimiter::Paren(b) => b.span.open(), + MacroDelimiter::Brace(b) => b.span.open(), + MacroDelimiter::Bracket(b) => b.span.open(), }; let mut formatted = String::new(); diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 59f135592..fae3313a8 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -197,7 +197,7 @@ impl<'a> Writer<'a> { self.out, "for {} in {} {{", forloop.pat.clone().into_token_stream(), - prettyplease::unparse_expr(&forloop.expr) + prettyplease::unparse_expr(&*forloop.expr) )?; if forloop.body.is_empty() { diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 3aa741615..a88b6df18 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -57,7 +57,7 @@ flate2 = "1.0.22" tar = "0.4.38" zip = "0.6.2" tower = "0.4.12" -syn = { version = "1.0", features = ["full", "extra-traits"] } +syn = { version = "2.0", features = ["full", "extra-traits"] } proc-macro2 = { version = "1.0", features = ["span-locations"] } lazy_static = "1.4.0" diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 92408725a..b99b1c359 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -15,7 +15,7 @@ proc-macro = true [dependencies] proc-macro2 = { version = "1.0" } quote = "1.0" -syn = { version = "1.0", features = ["full", "extra-traits"] } +syn = { version = "2.0", features = ["full", "extra-traits"] } dioxus-rsx = { workspace = true } # testing diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index 814724b56..ccc00f4f9 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -135,7 +135,7 @@ impl ToTokens for InlinePropsBody { quote! { <#struct_generics> }, ) } else { - let lifetime: LifetimeDef = parse_quote! { 'a }; + let lifetime: LifetimeParam = parse_quote! { 'a }; let mut fn_generics = generics.clone(); fn_generics diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 5f3de9dc9..523caf911 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -166,8 +166,9 @@ mod field_info { use crate::props::type_from_inside_option; use proc_macro2::TokenStream; use quote::quote; - use syn::parse::Error; use syn::spanned::Spanned; + use syn::Expr; + use syn::{parse::Error, punctuated::Punctuated}; use super::util::{ expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, @@ -260,31 +261,32 @@ mod field_info { pub fn with(mut self, attrs: &[syn::Attribute]) -> Result { let mut skip_tokens = None; for attr in attrs { - if path_to_single_string(&attr.path).as_deref() != Some("props") { + if path_to_single_string(attr.path()).as_deref() != Some("props") { continue; } - if attr.tokens.is_empty() { - continue; - } - - let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?; - match as_expr { - syn::Expr::Paren(body) => { - self.apply_meta(*body.expr)?; - } - syn::Expr::Tuple(body) => { - for expr in body.elems.into_iter() { - self.apply_meta(expr)?; + match &attr.meta { + syn::Meta::List(list) => { + if list.tokens.is_empty() { + continue; } } _ => { - return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)")); + continue; } } + + let as_expr = attr.parse_args_with( + Punctuated::::parse_separated_nonempty, + )?; + + for expr in as_expr.into_iter() { + self.apply_meta(expr)?; + } + // Stash its span for later (we don’t yet know if it’ll be an error) if self.skip && skip_tokens.is_none() { - skip_tokens = Some(attr.tokens.clone()); + skip_tokens = Some(attr.meta.clone()); } } @@ -461,6 +463,8 @@ mod struct_info { use proc_macro2::TokenStream; use quote::quote; use syn::parse::Error; + use syn::punctuated::Punctuated; + use syn::Expr; use super::field_info::{FieldBuilderAttr, FieldInfo}; use super::util::{ @@ -1082,28 +1086,28 @@ Finally, call `.build()` to create the instance of `{name}`. pub fn new(attrs: &[syn::Attribute]) -> Result { let mut result = TypeBuilderAttr::default(); for attr in attrs { - if path_to_single_string(&attr.path).as_deref() != Some("builder") { + if path_to_single_string(&attr.path()).as_deref() != Some("builder") { continue; } - if attr.tokens.is_empty() { - continue; - } - let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?; - - match as_expr { - syn::Expr::Paren(body) => { - result.apply_meta(*body.expr)?; - } - syn::Expr::Tuple(body) => { - for expr in body.elems.into_iter() { - result.apply_meta(expr)?; + match &attr.meta { + syn::Meta::List(list) => { + if list.tokens.is_empty() { + continue; } } _ => { - return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)")); + continue; } } + + let as_expr = attr.parse_args_with( + Punctuated::::parse_separated_nonempty, + )?; + + for expr in as_expr.into_iter() { + result.apply_meta(expr)?; + } } Ok(result) diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index c16dd5468..625fcae8a 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -5,19 +5,23 @@ edition = "2021" license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/hot_reload.html" -description = "Hot reloading utilites for Dioxus" +description = "Hot reloading utilities for Dioxus" keywords = ["dom", "ui", "gui", "react", "hot-reloading"] [dependencies] dioxus-rsx = { workspace = true } dioxus-core = { workspace = true, features = ["serialize"] } -dioxus-html = { workspace = true, features = ["hot-reload-context"] } +dioxus-html = { workspace = true } interprocess-docfix = { version = "1.2.2" } -notify = "5.0.0" -chrono = { version = "0.4.24", default-features = false, features = ["clock"] } +notify = { version = "5.0.0", optional = true } +chrono = { version = "0.4.24", default-features = false, features = ["clock"], optional = true } serde_json = "1.0.91" serde = { version = "1", features = ["derive"] } -execute = "0.2.11" -once_cell = "1.17.0" -ignore = "0.4.19" +execute = { version = "0.2.11", optional = true } +once_cell = { version = "1.17.0", optional = true } +ignore = { version = "0.4.19", optional = true } + +[features] +default = [] +file_watcher = ["ignore", "chrono", "notify", "execute", "once_cell", "ignore", "dioxus-html/hot-reload-context"] diff --git a/packages/hot-reload/src/file_watcher.rs b/packages/hot-reload/src/file_watcher.rs new file mode 100644 index 000000000..dd9b22f5f --- /dev/null +++ b/packages/hot-reload/src/file_watcher.rs @@ -0,0 +1,352 @@ +use std::{ + io::{BufRead, BufReader, Write}, + path::PathBuf, + str::FromStr, + sync::{Arc, Mutex}, +}; + +use dioxus_core::Template; +use dioxus_rsx::{ + hot_reload::{FileMap, FileMapBuildResult, UpdateResult}, + HotReloadingContext, +}; +use interprocess_docfix::local_socket::{LocalSocketListener, LocalSocketStream}; +use notify::{RecommendedWatcher, RecursiveMode, Watcher}; + +pub use dioxus_html::HtmlCtx; +use serde::{Deserialize, Serialize}; + +pub struct Config { + root_path: &'static str, + listening_paths: &'static [&'static str], + excluded_paths: &'static [&'static str], + log: bool, + rebuild_with: Option bool + Send + 'static>>, + phantom: std::marker::PhantomData, +} + +impl Default for Config { + fn default() -> Self { + Self { + root_path: "", + listening_paths: &[""], + excluded_paths: &["./target"], + log: true, + rebuild_with: None, + phantom: std::marker::PhantomData, + } + } +} + +impl Config { + pub const fn new() -> Self { + Self { + root_path: "", + listening_paths: &[""], + excluded_paths: &["./target"], + log: true, + rebuild_with: None, + phantom: std::marker::PhantomData, + } + } +} + +impl Config { + /// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro. + pub fn root(self, path: &'static str) -> Self { + Self { + root_path: path, + ..self + } + } + + /// Set whether to enable logs + pub fn with_logging(self, log: bool) -> Self { + Self { log, ..self } + } + + /// Set the command to run to rebuild the project + /// + /// For example to restart the application after a change is made, you could use `cargo run` + pub fn with_rebuild_command(self, rebuild_command: &'static str) -> Self { + self.with_rebuild_callback(move || { + execute::shell(rebuild_command) + .spawn() + .expect("Failed to spawn the rebuild command"); + true + }) + } + + /// Set a callback to run to when the project needs to be rebuilt and returns if the server should shut down + /// + /// For example a CLI application could rebuild the application when a change is made + pub fn with_rebuild_callback( + self, + rebuild_callback: impl FnMut() -> bool + Send + 'static, + ) -> Self { + Self { + rebuild_with: Some(Box::new(rebuild_callback)), + ..self + } + } + + /// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively. + pub fn with_paths(self, paths: &'static [&'static str]) -> Self { + Self { + listening_paths: paths, + ..self + } + } + + /// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts. + pub fn excluded_paths(self, paths: &'static [&'static str]) -> Self { + Self { + excluded_paths: paths, + ..self + } + } +} + +/// Initialize the hot reloading listener +pub fn init(cfg: Config) { + let Config { + root_path, + listening_paths, + log, + mut rebuild_with, + excluded_paths, + phantom: _, + } = cfg; + + if let Ok(crate_dir) = PathBuf::from_str(root_path) { + // try to find the gitingore file + let gitignore_file_path = crate_dir.join(".gitignore"); + let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); + + // convert the excluded paths to absolute paths + let excluded_paths = excluded_paths + .iter() + .map(|path| crate_dir.join(PathBuf::from(path))) + .collect::>(); + + let channels = Arc::new(Mutex::new(Vec::new())); + let FileMapBuildResult { + map: file_map, + errors, + } = FileMap::::create_with_filter(crate_dir.clone(), |path| { + // skip excluded paths + excluded_paths.iter().any(|p| path.starts_with(p)) || + // respect .gitignore + gitignore + .matched_path_or_any_parents(path, path.is_dir()) + .is_ignore() + }) + .unwrap(); + for err in errors { + if log { + println!("hot reloading failed to initialize:\n{err:?}"); + } + } + let file_map = Arc::new(Mutex::new(file_map)); + + #[cfg(target_os = "macos")] + { + // On unix, if you force quit the application, it can leave the file socket open + // This will cause the local socket listener to fail to open + // We check if the file socket is already open from an old session and then delete it + let paths = ["./dioxusin", "./@dioxusin"]; + for path in paths { + let path = PathBuf::from(path); + if path.exists() { + let _ = std::fs::remove_file(path); + } + } + } + + match LocalSocketListener::bind("@dioxusin") { + Ok(local_socket_stream) => { + let aborted = Arc::new(Mutex::new(false)); + + // listen for connections + std::thread::spawn({ + let file_map = file_map.clone(); + let channels = channels.clone(); + let aborted = aborted.clone(); + let _ = local_socket_stream.set_nonblocking(true); + move || { + loop { + if let Ok(mut connection) = local_socket_stream.accept() { + // send any templates than have changed before the socket connected + let templates: Vec<_> = { + file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if !send_msg( + HotReloadMsg::UpdateTemplate(template), + &mut connection, + ) { + continue; + } + } + channels.lock().unwrap().push(connection); + if log { + println!("Connected to hot reloading 🚀"); + } + } + if *aborted.lock().unwrap() { + break; + } + } + } + }); + + // watch for changes + std::thread::spawn(move || { + let mut last_update_time = chrono::Local::now().timestamp(); + + let (tx, rx) = std::sync::mpsc::channel(); + + let mut watcher = + RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); + + for path in listening_paths { + let full_path = crate_dir.join(path); + if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) { + if log { + println!( + "hot reloading failed to start watching {full_path:?}:\n{err:?}", + ); + } + } + } + + let mut rebuild = { + let aborted = aborted.clone(); + let channels = channels.clone(); + move || { + if let Some(rebuild_callback) = &mut rebuild_with { + if log { + println!("Rebuilding the application..."); + } + let shutdown = rebuild_callback(); + + if shutdown { + *aborted.lock().unwrap() = true; + } + + for channel in &mut *channels.lock().unwrap() { + send_msg(HotReloadMsg::Shutdown, channel); + } + + return shutdown; + } else if log { + println!( + "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." + ); + } + true + } + }; + + for evt in rx { + if chrono::Local::now().timestamp_millis() >= last_update_time { + if let Ok(evt) = evt { + let real_paths = evt + .paths + .iter() + .filter(|path| { + // skip non rust files + matches!( + path.extension().and_then(|p| p.to_str()), + Some("rs" | "toml" | "css" | "html" | "js") + ) && + // skip excluded paths + !excluded_paths.iter().any(|p| path.starts_with(p)) && + // respect .gitignore + !gitignore + .matched_path_or_any_parents(path, false) + .is_ignore() + }) + .collect::>(); + + // Give time for the change to take effect before reading the file + if !real_paths.is_empty() { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + let mut channels = channels.lock().unwrap(); + for path in real_paths { + // if this file type cannot be hot reloaded, rebuild the application + if path.extension().and_then(|p| p.to_str()) != Some("rs") + && rebuild() + { + return; + } + // find changes to the rsx in the file + match file_map + .lock() + .unwrap() + .update_rsx(path, crate_dir.as_path()) + { + Ok(UpdateResult::UpdatedRsx(msgs)) => { + for msg in msgs { + let mut i = 0; + while i < channels.len() { + let channel = &mut channels[i]; + if send_msg( + HotReloadMsg::UpdateTemplate(msg), + channel, + ) { + i += 1; + } else { + channels.remove(i); + } + } + } + } + Ok(UpdateResult::NeedsRebuild) => { + drop(channels); + if rebuild() { + return; + } + break; + } + Err(err) => { + if log { + println!( + "hot reloading failed to update rsx:\n{err:?}" + ); + } + } + } + } + } + last_update_time = chrono::Local::now().timestamp_millis(); + } + } + }); + } + Err(error) => println!("failed to connect to hot reloading\n{error}"), + } + } +} + +fn send_msg(msg: HotReloadMsg, channel: &mut impl Write) -> bool { + if let Ok(msg) = serde_json::to_string(&msg) { + if channel.write_all(msg.as_bytes()).is_err() { + return false; + } + if channel.write_all(&[b'\n']).is_err() { + return false; + } + true + } else { + false + } +} diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 48ee50a59..f70f61da9 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -1,21 +1,16 @@ -use std::{ - io::{BufRead, BufReader, Write}, - path::PathBuf, - str::FromStr, - sync::{Arc, Mutex}, -}; +use std::io::{BufRead, BufReader}; use dioxus_core::Template; -use dioxus_rsx::{ - hot_reload::{FileMap, FileMapBuildResult, UpdateResult}, - HotReloadingContext, -}; -use interprocess_docfix::local_socket::{LocalSocketListener, LocalSocketStream}; -use notify::{RecommendedWatcher, RecursiveMode, Watcher}; - +#[cfg(file_watcher)] pub use dioxus_html::HtmlCtx; +use interprocess_docfix::local_socket::LocalSocketStream; use serde::{Deserialize, Serialize}; +#[cfg(file_watcher)] +mod file_watcher; +#[cfg(file_watcher)] +use file_watcher::*; + /// A message the hot reloading server sends to the client #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum HotReloadMsg { @@ -26,341 +21,6 @@ pub enum HotReloadMsg { Shutdown, } -pub struct Config { - root_path: &'static str, - listening_paths: &'static [&'static str], - excluded_paths: &'static [&'static str], - log: bool, - rebuild_with: Option bool + Send + 'static>>, - phantom: std::marker::PhantomData, -} - -impl Default for Config { - fn default() -> Self { - Self { - root_path: "", - listening_paths: &[""], - excluded_paths: &["./target"], - log: true, - rebuild_with: None, - phantom: std::marker::PhantomData, - } - } -} - -impl Config { - pub const fn new() -> Self { - Self { - root_path: "", - listening_paths: &[""], - excluded_paths: &["./target"], - log: true, - rebuild_with: None, - phantom: std::marker::PhantomData, - } - } -} - -impl Config { - /// Set the root path of the project (where the Cargo.toml file is). This is automatically set by the [`hot_reload_init`] macro. - pub fn root(self, path: &'static str) -> Self { - Self { - root_path: path, - ..self - } - } - - /// Set whether to enable logs - pub fn with_logging(self, log: bool) -> Self { - Self { log, ..self } - } - - /// Set the command to run to rebuild the project - /// - /// For example to restart the application after a change is made, you could use `cargo run` - pub fn with_rebuild_command(self, rebuild_command: &'static str) -> Self { - self.with_rebuild_callback(move || { - execute::shell(rebuild_command) - .spawn() - .expect("Failed to spawn the rebuild command"); - true - }) - } - - /// Set a callback to run to when the project needs to be rebuilt and returns if the server should shut down - /// - /// For example a CLI application could rebuild the application when a change is made - pub fn with_rebuild_callback( - self, - rebuild_callback: impl FnMut() -> bool + Send + 'static, - ) -> Self { - Self { - rebuild_with: Some(Box::new(rebuild_callback)), - ..self - } - } - - /// Set the paths to listen for changes in to trigger hot reloading. If this is a directory it will listen for changes in all files in that directory recursively. - pub fn with_paths(self, paths: &'static [&'static str]) -> Self { - Self { - listening_paths: paths, - ..self - } - } - - /// Sets paths to ignore changes on. This will override any paths set in the [`Config::with_paths`] method in the case of conflicts. - pub fn excluded_paths(self, paths: &'static [&'static str]) -> Self { - Self { - excluded_paths: paths, - ..self - } - } -} - -/// Initialize the hot reloading listener -pub fn init(cfg: Config) { - let Config { - root_path, - listening_paths, - log, - mut rebuild_with, - excluded_paths, - phantom: _, - } = cfg; - - if let Ok(crate_dir) = PathBuf::from_str(root_path) { - // try to find the gitingore file - let gitignore_file_path = crate_dir.join(".gitignore"); - let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); - - // convert the excluded paths to absolute paths - let excluded_paths = excluded_paths - .iter() - .map(|path| crate_dir.join(PathBuf::from(path))) - .collect::>(); - - let channels = Arc::new(Mutex::new(Vec::new())); - let FileMapBuildResult { - map: file_map, - errors, - } = FileMap::::create_with_filter(crate_dir.clone(), |path| { - // skip excluded paths - excluded_paths.iter().any(|p| path.starts_with(p)) || - // respect .gitignore - gitignore - .matched_path_or_any_parents(path, path.is_dir()) - .is_ignore() - }) - .unwrap(); - for err in errors { - if log { - println!("hot reloading failed to initialize:\n{err:?}"); - } - } - let file_map = Arc::new(Mutex::new(file_map)); - - #[cfg(target_os = "macos")] - { - // On unix, if you force quit the application, it can leave the file socket open - // This will cause the local socket listener to fail to open - // We check if the file socket is already open from an old session and then delete it - let paths = ["./dioxusin", "./@dioxusin"]; - for path in paths { - let path = PathBuf::from(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } - } - } - - match LocalSocketListener::bind("@dioxusin") { - Ok(local_socket_stream) => { - let aborted = Arc::new(Mutex::new(false)); - - // listen for connections - std::thread::spawn({ - let file_map = file_map.clone(); - let channels = channels.clone(); - let aborted = aborted.clone(); - let _ = local_socket_stream.set_nonblocking(true); - move || { - loop { - if let Ok(mut connection) = local_socket_stream.accept() { - // send any templates than have changed before the socket connected - let templates: Vec<_> = { - file_map - .lock() - .unwrap() - .map - .values() - .filter_map(|(_, template_slot)| *template_slot) - .collect() - }; - for template in templates { - if !send_msg( - HotReloadMsg::UpdateTemplate(template), - &mut connection, - ) { - continue; - } - } - channels.lock().unwrap().push(connection); - if log { - println!("Connected to hot reloading 🚀"); - } - } - if *aborted.lock().unwrap() { - break; - } - } - } - }); - - // watch for changes - std::thread::spawn(move || { - let mut last_update_time = chrono::Local::now().timestamp(); - - let (tx, rx) = std::sync::mpsc::channel(); - - let mut watcher = - RecommendedWatcher::new(tx, notify::Config::default()).unwrap(); - - for path in listening_paths { - let full_path = crate_dir.join(path); - if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) { - if log { - println!( - "hot reloading failed to start watching {full_path:?}:\n{err:?}", - ); - } - } - } - - let mut rebuild = { - let aborted = aborted.clone(); - let channels = channels.clone(); - move || { - if let Some(rebuild_callback) = &mut rebuild_with { - if log { - println!("Rebuilding the application..."); - } - let shutdown = rebuild_callback(); - - if shutdown { - *aborted.lock().unwrap() = true; - } - - for channel in &mut *channels.lock().unwrap() { - send_msg(HotReloadMsg::Shutdown, channel); - } - - return shutdown; - } else if log { - println!( - "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes." - ); - } - true - } - }; - - for evt in rx { - if chrono::Local::now().timestamp_millis() >= last_update_time { - if let Ok(evt) = evt { - let real_paths = evt - .paths - .iter() - .filter(|path| { - // skip non rust files - matches!( - path.extension().and_then(|p| p.to_str()), - Some("rs" | "toml" | "css" | "html" | "js") - ) && - // skip excluded paths - !excluded_paths.iter().any(|p| path.starts_with(p)) && - // respect .gitignore - !gitignore - .matched_path_or_any_parents(path, false) - .is_ignore() - }) - .collect::>(); - - // Give time for the change to take effect before reading the file - if !real_paths.is_empty() { - std::thread::sleep(std::time::Duration::from_millis(10)); - } - - let mut channels = channels.lock().unwrap(); - for path in real_paths { - // if this file type cannot be hot reloaded, rebuild the application - if path.extension().and_then(|p| p.to_str()) != Some("rs") - && rebuild() - { - return; - } - // find changes to the rsx in the file - match file_map - .lock() - .unwrap() - .update_rsx(path, crate_dir.as_path()) - { - Ok(UpdateResult::UpdatedRsx(msgs)) => { - for msg in msgs { - let mut i = 0; - while i < channels.len() { - let channel = &mut channels[i]; - if send_msg( - HotReloadMsg::UpdateTemplate(msg), - channel, - ) { - i += 1; - } else { - channels.remove(i); - } - } - } - } - Ok(UpdateResult::NeedsRebuild) => { - drop(channels); - if rebuild() { - return; - } - break; - } - Err(err) => { - if log { - println!( - "hot reloading failed to update rsx:\n{err:?}" - ); - } - } - } - } - } - last_update_time = chrono::Local::now().timestamp_millis(); - } - } - }); - } - Err(error) => println!("failed to connect to hot reloading\n{error}"), - } - } -} - -fn send_msg(msg: HotReloadMsg, channel: &mut impl Write) -> bool { - if let Ok(msg) = serde_json::to_string(&msg) { - if channel.write_all(msg.as_bytes()).is_err() { - return false; - } - if channel.write_all(&[b'\n']).is_err() { - return false; - } - true - } else { - false - } -} - /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index a44b22a77..27c4a7cd6 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { workspace = true } -dioxus-rsx = { workspace = true, optional = true } +dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { workspace = true, optional = true } diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index 82b2dc4a6..ab53c20f2 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] proc-macro = true [dependencies] -syn = { version = "1.0.11", features = ["extra-traits", "full"] } +syn = { version = "2.0", features = ["extra-traits", "full"] } quote = "1.0" [dev-dependencies] diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index ff0e4629d..d122838bd 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -14,7 +14,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let has_create_fn = impl_block .items .iter() - .any(|item| matches!(item, syn::ImplItem::Method(method) if method.sig.ident == "create")); + .any(|item| matches!(item, syn::ImplItem::Fn(method) if method.sig.ident == "create")); let parent_dependencies = impl_block .items diff --git a/packages/router-macro/Cargo.toml b/packages/router-macro/Cargo.toml index 3b7a48aeb..9e23fdf86 100644 --- a/packages/router-macro/Cargo.toml +++ b/packages/router-macro/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "router"] proc-macro = true [dependencies] -syn = { version = "1.0.11", features = ["extra-traits", "full"] } +syn = { version = "2.0", features = ["extra-traits", "full"] } quote = "1.0" proc-macro2 = "1.0.56" slab = "0.4" diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index f8c9cff1d..708aa6891 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -286,7 +286,7 @@ impl RouteEnum { let mut excluded = Vec::new(); // Apply the any nesting attributes in order for attr in &variant.attrs { - if attr.path.is_ident("nest") { + if attr.path().is_ident("nest") { let mut children_routes = Vec::new(); { // add all of the variants of the enum to the children_routes until we hit an end_nest @@ -294,9 +294,9 @@ impl RouteEnum { 'o: for variant in &data.variants { children_routes.push(variant.fields.clone()); for attr in &variant.attrs { - if attr.path.is_ident("nest") { + if attr.path().is_ident("nest") { level += 1; - } else if attr.path.is_ident("end_nest") { + } else if attr.path().is_ident("end_nest") { level -= 1; if level < 0 { break 'o; @@ -341,7 +341,7 @@ impl RouteEnum { nests.push(nest); nest_stack.push(NestId(nest_index)); - } else if attr.path.is_ident("end_nest") { + } else if attr.path().is_ident("end_nest") { nest_stack.pop(); // pop the current nest segment off the stack and add it to the parent or the site map if let Some(segment) = site_map_stack.pop() { @@ -360,7 +360,7 @@ impl RouteEnum { children.push(current); } - } else if attr.path.is_ident("layout") { + } else if attr.path().is_ident("layout") { let parser = |input: ParseStream| { let bang: Option = input.parse().ok(); let exclude = bang.is_some(); @@ -382,9 +382,9 @@ impl RouteEnum { layouts.push(layout); layout_stack.push(LayoutId(layout_index)); } - } else if attr.path.is_ident("end_layout") { + } else if attr.path().is_ident("end_layout") { layout_stack.pop(); - } else if attr.path.is_ident("redirect") { + } else if attr.path().is_ident("redirect") { let parser = |input: ParseStream| { Redirect::parse(input, nest_stack.clone(), redirects.len()) }; diff --git a/packages/router-macro/src/route.rs b/packages/router-macro/src/route.rs index ad8c74857..af6d7136f 100644 --- a/packages/router-macro/src/route.rs +++ b/packages/router-macro/src/route.rs @@ -75,7 +75,7 @@ impl Route { let route_attr = variant .attrs .iter() - .find(|attr| attr.path.is_ident("route")); + .find(|attr| attr.path().is_ident("route")); let route; let ty; let route_name = variant.ident.clone(); @@ -106,7 +106,7 @@ impl Route { if let Some(route_attr) = variant .attrs .iter() - .find(|attr| attr.path.is_ident("child")) + .find(|attr| attr.path().is_ident("child")) { let args = route_attr.parse_args::()?; route = args.route.value(); @@ -116,7 +116,7 @@ impl Route { let child_field = fields.named.iter().find(|f| { f.attrs .iter() - .any(|attr| attr.path.is_ident("child")) + .any(|attr| attr.path().is_ident("child")) || *f.ident.as_ref().unwrap() == "child" }); match child_field{ diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index eef6b2499..4d443d433 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -18,7 +18,7 @@ dioxus-rsx = { workspace = true } html_parser.workspace = true proc-macro2 = "1.0.49" quote = "1.0.23" -syn = { version = "1.0.107", features = ["full"] } +syn = { version = "2.0", features = ["full"] } convert_case = "0.5.0" # [features] diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index d4b997187..996262e66 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -14,8 +14,12 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] proc-macro2 = { version = "1.0", features = ["span-locations"] } dioxus-core = { workspace = true} -syn = { version = "1.0", features = ["full", "extra-traits"] } +syn = { version = "2.0", features = ["full", "extra-traits"] } quote = { version = "1.0" } -serde = { version = "1.0", features = ["derive"] } -internment = "0.7.0" -krates = "0.12.6" \ No newline at end of file +serde = { version = "1.0", features = ["derive"], optional = true } +internment = { version = "0.7.0", optional = true } +krates = { version = "0.12.6", optional = true } + +[features] +hot_reload = ["krates", "internment"] +serde = ["dep:serde"] diff --git a/packages/rsx/src/error.rs b/packages/rsx/src/error.rs index e75331066..16651fe5a 100644 --- a/packages/rsx/src/error.rs +++ b/packages/rsx/src/error.rs @@ -1,15 +1,18 @@ use std::fmt::Display; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// An error produced when interperting the rsx -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { ParseError(ParseError), RecompileRequiredError(RecompileReason), } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum RecompileReason { Variable(String), Expression(String), @@ -18,14 +21,16 @@ pub enum RecompileReason { Attribute(String), } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CodeLocation { pub line: u32, pub column: u32, pub file_path: &'static str, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ParseError { pub message: String, pub location: CodeLocation, diff --git a/packages/rsx/src/hot_reload/hot_reload_diff.rs b/packages/rsx/src/hot_reload/hot_reload_diff.rs index ea9918f99..22a01875d 100644 --- a/packages/rsx/src/hot_reload/hot_reload_diff.rs +++ b/packages/rsx/src/hot_reload/hot_reload_diff.rs @@ -85,7 +85,7 @@ fn find_rsx_item( (syn::ImplItem::Const(new_item), syn::ImplItem::Const(old_item)) => { find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls) } - (syn::ImplItem::Method(new_item), syn::ImplItem::Method(old_item)) => { + (syn::ImplItem::Fn(new_item), syn::ImplItem::Fn(old_item)) => { find_rsx_block(&new_item.block, &old_item.block, rsx_calls) } (syn::ImplItem::Type(new_item), syn::ImplItem::Type(old_item)) => { @@ -114,7 +114,6 @@ fn find_rsx_item( || new_item.semi_token != old_item.semi_token || new_item.ident != old_item.ident } - (syn::Item::Macro2(new_item), syn::Item::Macro2(old_item)) => old_item != new_item, (syn::Item::Mod(new_item), syn::Item::Mod(old_item)) => { match (&new_item.content, &old_item.content) { (Some((_, new_items)), Some((_, old_items))) => { @@ -186,7 +185,7 @@ fn find_rsx_trait( true } } - (syn::TraitItem::Method(new_item), syn::TraitItem::Method(old_item)) => { + (syn::TraitItem::Fn(new_item), syn::TraitItem::Fn(old_item)) => { if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) { find_rsx_block(new_block, old_block, rsx_calls) } else { @@ -239,8 +238,9 @@ fn find_rsx_stmt( match (new_stmt, old_stmt) { (syn::Stmt::Local(new_local), syn::Stmt::Local(old_local)) => { (match (&new_local.init, &old_local.init) { - (Some((new_eq, new_expr)), Some((old_eq, old_expr))) => { - find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq + (Some(new_local), Some(old_local)) => { + find_rsx_expr(&new_local.expr, &old_local.expr, rsx_calls) + || new_local != old_local } (None, None) => false, _ => true, @@ -252,11 +252,13 @@ fn find_rsx_stmt( (syn::Stmt::Item(new_item), syn::Stmt::Item(old_item)) => { find_rsx_item(new_item, old_item, rsx_calls) } - (syn::Stmt::Expr(new_expr), syn::Stmt::Expr(old_expr)) => { + (syn::Stmt::Expr(new_expr, _), syn::Stmt::Expr(old_expr, _)) => { find_rsx_expr(new_expr, old_expr, rsx_calls) } - (syn::Stmt::Semi(new_expr, new_semi), syn::Stmt::Semi(old_expr, old_semi)) => { - find_rsx_expr(new_expr, old_expr, rsx_calls) || new_semi != old_semi + (syn::Stmt::Macro(new_macro), syn::Stmt::Macro(old_macro)) => { + find_rsx_macro(&new_macro.mac, &old_macro.mac, rsx_calls) + || new_macro.attrs != old_macro.attrs + || new_macro.semi_token != old_macro.semi_token } _ => true, } @@ -285,12 +287,6 @@ fn find_rsx_expr( || new_expr.attrs != old_expr.attrs || new_expr.eq_token != old_expr.eq_token } - (syn::Expr::AssignOp(new_expr), syn::Expr::AssignOp(old_expr)) => { - find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls) - || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls) - || new_expr.attrs != old_expr.attrs - || new_expr.op != old_expr.op - } (syn::Expr::Async(new_expr), syn::Expr::Async(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs @@ -314,11 +310,6 @@ fn find_rsx_expr( || new_expr.attrs != old_expr.attrs || new_expr.label != old_expr.label } - (syn::Expr::Box(new_expr), syn::Expr::Box(old_expr)) => { - find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) - || new_expr.attrs != old_expr.attrs - || new_expr.box_token != old_expr.box_token - } (syn::Expr::Break(new_expr), syn::Expr::Break(old_expr)) => { match (&new_expr.expr, &old_expr.expr) { (Some(new_inner), Some(old_inner)) => { @@ -478,7 +469,7 @@ fn find_rsx_expr( } (syn::Expr::Path(new_expr), syn::Expr::Path(old_expr)) => old_expr != new_expr, (syn::Expr::Range(new_expr), syn::Expr::Range(old_expr)) => { - match (&new_expr.from, &old_expr.from) { + match (&new_expr.start, &old_expr.start) { (Some(new_expr), Some(old_expr)) => { if find_rsx_expr(new_expr, old_expr, rsx_calls) { return true; @@ -487,7 +478,7 @@ fn find_rsx_expr( (None, None) => (), _ => return true, } - match (&new_expr.to, &old_expr.to) { + match (&new_expr.end, &old_expr.end) { (Some(new_inner), Some(old_inner)) => { find_rsx_expr(new_inner, old_inner, rsx_calls) || new_expr.attrs != old_expr.attrs @@ -568,12 +559,6 @@ fn find_rsx_expr( } new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token } - (syn::Expr::Type(new_expr), syn::Expr::Type(old_expr)) => { - find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) - || new_expr.attrs != old_expr.attrs - || new_expr.colon_token != old_expr.colon_token - || new_expr.ty != old_expr.ty - } (syn::Expr::Unary(new_expr), syn::Expr::Unary(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 41156e87e..1f01fcd72 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -15,18 +15,22 @@ mod errors; mod component; mod element; +#[cfg(feature = "hot_reload")] pub mod hot_reload; mod ifmt; mod node; -use std::{collections::HashMap, fmt::Debug, hash::Hash}; +use std::{fmt::Debug, hash::Hash}; // Re-export the namespaces into each other pub use component::*; +#[cfg(feature = "hot_reload")] use dioxus_core::{Template, TemplateAttribute, TemplateNode}; pub use element::*; +#[cfg(feature = "hot_reload")] pub use hot_reload::HotReloadingContext; pub use ifmt::*; +#[cfg(feature = "hot_reload")] use internment::Intern; pub use node::*; @@ -38,6 +42,7 @@ use syn::{ Result, Token, }; +#[cfg(feature = "hot_reload")] // interns a object into a static object, resusing the value if it already exists fn intern(s: impl Into>) -> &'static T { s.into().as_ref() @@ -50,6 +55,7 @@ pub struct CallBody { } impl CallBody { + #[cfg(feature = "hot_reload")] /// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed. /// This function intentionally leaks memory to create a static template. /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue. @@ -140,6 +146,7 @@ pub struct TemplateRenderer<'a> { } impl<'a> TemplateRenderer<'a> { + #[cfg(feature = "hot_reload")] fn update_template( &mut self, previous_call: Option, @@ -248,14 +255,16 @@ impl<'a> ToTokens for TemplateRenderer<'a> { } } +#[cfg(feature = "hot_reload")] #[derive(Default, Debug)] struct DynamicMapping { - attribute_to_idx: HashMap>, + attribute_to_idx: std::collections::HashMap>, last_attribute_idx: usize, - node_to_idx: HashMap>, + node_to_idx: std::collections::HashMap>, last_element_idx: usize, } +#[cfg(feature = "hot_reload")] impl DynamicMapping { fn from(nodes: Vec) -> Self { let mut new = Self::default(); @@ -349,6 +358,7 @@ pub struct DynamicContext<'a> { } impl<'a> DynamicContext<'a> { + #[cfg(feature = "hot_reload")] fn update_node( &mut self, root: &'a BodyNode, @@ -558,6 +568,7 @@ impl<'a> DynamicContext<'a> { } } +#[cfg(feature = "hot_reload")] #[test] fn create_template() { let input = quote! { @@ -643,6 +654,7 @@ fn create_template() { ) } +#[cfg(feature = "hot_reload")] #[test] fn diff_template() { use dioxus_core::Scope; diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 91651430d..1bda081d9 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -222,7 +222,7 @@ impl Parse for ForLoop { fn parse(input: ParseStream) -> Result { let for_token: Token![for] = input.parse()?; - let pat = input.parse()?; + let pat = Pat::parse_single(input)?; let in_token: Token![in] = input.parse()?; let expr: Expr = input.call(Expr::parse_without_eager_brace)?; From 0f3606ea53a3ea3bd7dd769a898ac5ab06dc13dc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 10:21:37 -0700 Subject: [PATCH 13/20] use git prettyplease temporarily --- packages/autofmt/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index c658a9f42..88a500432 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -16,7 +16,7 @@ proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "2.0", features = ["full", "extra-traits"] } serde = { version = "1.0.136", features = ["derive"] } -prettyplease = { package = "prettier-please", version = "0.2", features = [ +prettyplease = { package = "prettier-please", git = "https://github.com/Demonthos/prettier-please", features = [ "verbatim", ] } From 05a1ea403f174eb2ef31f7a833b866af6f077ee7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 10:33:23 -0700 Subject: [PATCH 14/20] use published prettier-please --- packages/autofmt/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index 88a500432..c658a9f42 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -16,7 +16,7 @@ proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "2.0", features = ["full", "extra-traits"] } serde = { version = "1.0.136", features = ["derive"] } -prettyplease = { package = "prettier-please", git = "https://github.com/Demonthos/prettier-please", features = [ +prettyplease = { package = "prettier-please", version = "0.2", features = [ "verbatim", ] } From c9d00908ec14db385c6b380b64ebc0a61ba26739 Mon Sep 17 00:00:00 2001 From: ealmloff Date: Wed, 19 Jul 2023 15:00:10 -0500 Subject: [PATCH 15/20] Update component.rs --- packages/autofmt/src/component.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 7b507e7bd..d5d92b79a 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -176,7 +176,7 @@ impl Writer<'_> { )?; } ContentField::OnHandlerRaw(exp) => { - let out = prettyplease::unparse(exp); + let out = prettyplease::unparse_expr(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); write!(self.out, "{name}: {first}")?; From 6639011a3eb15edbe11fa74c4d90668e36920038 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 13:43:04 -0700 Subject: [PATCH 16/20] fix autofmt --- packages/autofmt/src/collect_macros.rs | 6 +----- packages/autofmt/src/lib.rs | 9 +++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/autofmt/src/collect_macros.rs b/packages/autofmt/src/collect_macros.rs index 9c65fae39..6494b4bfe 100644 --- a/packages/autofmt/src/collect_macros.rs +++ b/packages/autofmt/src/collect_macros.rs @@ -5,8 +5,6 @@ use proc_macro2::LineColumn; use syn::{Block, Expr, File, Item, Macro, Stmt}; -use crate::expr; - type CollectedMacro<'a> = &'a Macro; pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec>) { @@ -44,8 +42,7 @@ pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec } // None of these we can really do anything with at the item level - Item::Macro(_) - | Item::Enum(_) + Item::Enum(_) | Item::ExternCrate(_) | Item::ForeignMod(_) | Item::TraitAlias(_) @@ -156,7 +153,6 @@ pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec // don't both formatting these for now Expr::Array(_) - | Expr::Assign(_) | Expr::Await(_) | Expr::Binary(_) | Expr::Break(_) diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index f878a800a..7d98fb222 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -88,10 +88,11 @@ pub fn fmt_file(contents: &str) -> Vec { } let span = match item.delimiter { - MacroDelimiter::Paren(b) => b.span.open(), - MacroDelimiter::Brace(b) => b.span.open(), - MacroDelimiter::Bracket(b) => b.span.open(), - }; + MacroDelimiter::Paren(b) => b.span, + MacroDelimiter::Brace(b) => b.span, + MacroDelimiter::Bracket(b) => b.span, + } + .join(); let mut formatted = String::new(); From 1f19ac9334b0c87720584fed1b7ff9746022693a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 13:45:47 -0700 Subject: [PATCH 17/20] bump sledgehammer bindgen --- packages/interpreter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index dd310ce41..7c6796829 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -15,7 +15,7 @@ wasm-bindgen = { workspace = true, optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } sledgehammer_bindgen = { version = "0.2.1", optional = true } -sledgehammer_utils = { version = "0.1.1", optional = true } +sledgehammer_utils = { version = "0.2", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] From 212022d0c24492382c34788ea36060792f96da7a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 13:54:10 -0700 Subject: [PATCH 18/20] more clippy fixes --- packages/autofmt/src/writer.rs | 2 +- packages/core-macro/src/props/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index fae3313a8..59f135592 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -197,7 +197,7 @@ impl<'a> Writer<'a> { self.out, "for {} in {} {{", forloop.pat.clone().into_token_stream(), - prettyplease::unparse_expr(&*forloop.expr) + prettyplease::unparse_expr(&forloop.expr) )?; if forloop.body.is_empty() { diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 523caf911..6a0d24a95 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -1086,7 +1086,7 @@ Finally, call `.build()` to create the instance of `{name}`. pub fn new(attrs: &[syn::Attribute]) -> Result { let mut result = TypeBuilderAttr::default(); for attr in attrs { - if path_to_single_string(&attr.path()).as_deref() != Some("builder") { + if path_to_single_string(attr.path()).as_deref() != Some("builder") { continue; } From dccad58f1d28cd7eb58d664218b9bedff8019a66 Mon Sep 17 00:00:00 2001 From: Andrea Frigido Date: Thu, 20 Jul 2023 18:00:07 +0100 Subject: [PATCH 19/20] Update license field following SPDX 2.1 license expression standard --- docs/guide/Cargo.toml | 2 +- docs/router/Cargo.toml | 2 +- packages/autofmt/Cargo.toml | 2 +- packages/cli/Cargo.toml | 2 +- packages/core-macro/Cargo.toml | 2 +- packages/core/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/dioxus-tui/Cargo.toml | 2 +- packages/fermi/Cargo.toml | 2 +- packages/fullstack/Cargo.toml | 2 +- packages/hooks/Cargo.toml | 2 +- packages/hot-reload/Cargo.toml | 2 +- packages/html/Cargo.toml | 2 +- packages/liveview/Cargo.toml | 2 +- packages/mobile/Cargo.toml | 2 +- packages/native-core-macro/Cargo.toml | 2 +- packages/native-core/Cargo.toml | 2 +- packages/rink/Cargo.toml | 2 +- packages/router-macro/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/rsx-rosetta/Cargo.toml | 2 +- packages/rsx/Cargo.toml | 2 +- packages/ssr/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- playwright-tests/liveview/Cargo.toml | 2 +- playwright-tests/web/Cargo.toml | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/guide/Cargo.toml b/docs/guide/Cargo.toml index 06b17b0e9..c2527b8b4 100644 --- a/docs/guide/Cargo.toml +++ b/docs/guide/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-guide" version = "0.0.1" edition = "2021" description = "Dioxus guide, including testable examples" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" publish = false [dev-dependencies] diff --git a/docs/router/Cargo.toml b/docs/router/Cargo.toml index eb1e29253..59e8861ad 100644 --- a/docs/router/Cargo.toml +++ b/docs/router/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-router-guide" version = "0.0.1" edition = "2021" description = "Dioxus router guide, including testable examples" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" publish = false [dev-dependencies] diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index a76b0fcd4..653716b28 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" edition = "2021" authors = ["Jonathan Kelley"] description = "Autofomatter for Dioxus RSX" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index b9fab5e9e..6360ffe01 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Jonathan Kelley"] edition = "2021" description = "CLI tool for developing, testing, and publishing Dioxus apps" repository = "https://github.com/DioxusLabs/dioxus/" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" [dependencies] # cli core diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 92408725a..a89ef1494 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Jonathan Kelley"] edition = "2021" description = "Core macro for Dioxus Virtual DOM" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 3b85a8d2d..beb65c9ef 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.3" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 05c746a52..29e867b4c 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Jonathan Kelley"] edition = "2018" description = "WebView renderer for Dioxus" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/desktop.html" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/dioxus-tui/Cargo.toml b/packages/dioxus-tui/Cargo.toml index ec8e29d32..914d22e07 100644 --- a/packages/dioxus-tui/Cargo.toml +++ b/packages/dioxus-tui/Cargo.toml @@ -7,7 +7,7 @@ description = "TUI-based renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/tui.html" keywords = ["dom", "ui", "gui", "react", "terminal"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" [dependencies] dioxus = { workspace = true } diff --git a/packages/fermi/Cargo.toml b/packages/fermi/Cargo.toml index aede371cc..54eb2f706 100644 --- a/packages/fermi/Cargo.toml +++ b/packages/fermi/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Jonathan Kelley"] edition = "2018" description = "Global state management for Dioxus" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "state-management"] diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 6a39918db..b73b8adc2 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-fullstack" version = "0.1.0" edition = "2021" description = "Fullstack Dioxus Utilities" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"] diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 598ffedab..758db4f64 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Basic useful hooks for Dioxus." -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index c16dd5468..14aae194e 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-hot-reload" version = "0.1.1" edition = "2021" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/hot_reload.html" description = "Hot reloading utilites for Dioxus" diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index a44b22a77..e8587c2b6 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 28a4e4a06..33964753a 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/liveview.html" keywords = ["dom", "ui", "gui", "react", "liveview"] description = "Build server-side apps with Dioxus" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" [dependencies] thiserror = "1.0.38" diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index c5a8f8239..bbaf4168c 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -7,7 +7,7 @@ description = "Mobile-compatible renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/mobile.html" keywords = ["dom", "ui", "gui", "react"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" [dependencies] dioxus-desktop = { workspace = true } diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index 82b2dc4a6..73cfa5f32 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-native-core-macro" version = "0.3.0" edition = "2021" description = "Build natively rendered apps with Dioxus" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 5ead1740d..44fe594f6 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-native-core" version = "0.2.0" edition = "2021" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" description = "Build natively rendered apps with Dioxus" diff --git a/packages/rink/Cargo.toml b/packages/rink/Cargo.toml index a51659dc7..1f5fa13b1 100644 --- a/packages/rink/Cargo.toml +++ b/packages/rink/Cargo.toml @@ -7,7 +7,7 @@ description = "TUI-based renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "terminal"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" [dependencies] dioxus-html = { workspace = true } diff --git a/packages/router-macro/Cargo.toml b/packages/router-macro/Cargo.toml index 3b7a48aeb..edead5319 100644 --- a/packages/router-macro/Cargo.toml +++ b/packages/router-macro/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Evan Almloff"] edition = "2021" description = "Macro for Dioxus Router" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index fd6d2ddf7..dcdcd7b4b 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-router" version = "0.3.0" edition = "2018" description = "Cross-platform router for Dioxus apps" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index eef6b2499..89cff682b 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" edition = "2021" authors = ["Jonathan Kelley"] description = "Autofomatter for Dioxus RSX" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index d4b997187..9594d3b28 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-rsx" version = "0.0.3" edition = "2018" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index c1a08d175..77cc49534 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus render-to-string" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/ssr.html" keywords = ["dom", "ui", "gui", "react", "ssr"] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 098ddfe65..74143537f 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.2" authors = ["Jonathan Kelley"] edition = "2018" description = "Web renderer for Dioxus using websys" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/web.html" keywords = ["dom", "ui", "gui", "react", "wasm"] diff --git a/playwright-tests/liveview/Cargo.toml b/playwright-tests/liveview/Cargo.toml index 173ae1d81..ea9f3eb24 100644 --- a/playwright-tests/liveview/Cargo.toml +++ b/playwright-tests/liveview/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-playwright-liveview-test" version = "0.0.1" edition = "2021" description = "Playwright test for Dioxus Liveview" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" publish = false [dependencies] diff --git a/playwright-tests/web/Cargo.toml b/playwright-tests/web/Cargo.toml index 22f4f4096..0cad7b207 100644 --- a/playwright-tests/web/Cargo.toml +++ b/playwright-tests/web/Cargo.toml @@ -3,7 +3,7 @@ name = "dioxus-playwright-web-test" version = "0.0.1" edition = "2021" description = "Playwright test for Dioxus Web" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" publish = false [dependencies] From a49fd81523eaa78ea353d70ba495c869939e6433 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 20 Jul 2023 10:55:52 -0700 Subject: [PATCH 20/20] fix merge conflict --- packages/router/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index a23e87d2c..ccd14dd28 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -18,10 +18,11 @@ thiserror = { workspace = true } futures-util = { workspace = true } serde_urlencoded = { version = "0.7.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } -thiserror = "1.0.37" url = "2.3.1" wasm-bindgen = { workspace = true, optional = true } -web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"] } +web-sys = { version = "0.3.60", optional = true, features = [ + "ScrollRestoration", +] } js-sys = { version = "0.3.63", optional = true } gloo-utils = { version = "0.1.6", optional = true } dioxus-ssr = { workspace = true, optional = true }