diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 7c9515fc0..71afe5825 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -39,6 +39,7 @@ dioxus = { workspace = true } pretty_assertions = "1.3.0" rand = "0.8.5" dioxus-ssr = { workspace = true } +trybuild = "1.0" [features] default = [] diff --git a/packages/core/compile_tests/props_safety.rs b/packages/core/compile_tests/props_safety.rs new file mode 100644 index 000000000..a9729cec9 --- /dev/null +++ b/packages/core/compile_tests/props_safety.rs @@ -0,0 +1,32 @@ +use dioxus::prelude::*; + +fn main() {} + +fn app(cx: Scope) -> Element { + let count: &RefCell> = cx.use_hook(|| RefCell::new(Vec::new())); + + render! { + unsafe_child_component { + borrowed: count + } + } +} + +#[derive(Props)] +struct Testing<'a> { + borrowed: &'a RefCell>>, +} + +fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { + let Testing { borrowed } = cx.props; + let borrowed_temporary_data = + cx.use_hook(|| String::from("This data is only valid for the lifetime of the child")); + + borrowed + .borrow_mut() + .push(render! {"{borrowed_temporary_data}"}); + + cx.render(rsx! { + div { "Hello, world!" } + }) +} diff --git a/packages/core/compile_tests/props_safety.stderr b/packages/core/compile_tests/props_safety.stderr new file mode 100644 index 000000000..565d7b6cc --- /dev/null +++ b/packages/core/compile_tests/props_safety.stderr @@ -0,0 +1,20 @@ +error[E0521]: borrowed data escapes outside of function + --> compile_tests/props_safety.rs:8:5 + | +5 | fn app(cx: Scope) -> Element { + | -- + | | + | `cx` is a reference that is only valid in the function body + | has type `&'1 Scoped<'1>` +... +8 | / render! { +9 | | unsafe_child_component { +10 | | borrowed: count +11 | | } +12 | | } + | | ^ + | | | + | |_____`cx` escapes the function body here + | argument requires that `'1` must outlive `'static` + | + = note: this error originates in the macro `render` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 834b0cabd..f754f8b68 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -73,3 +73,10 @@ impl EmptyBuilder { pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { T::builder() } + +#[cfg(not(miri))] +#[test] +fn unsafe_props_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("compile_tests/props_safety.rs"); +} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 9470c3d0d..4a21fcb78 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -481,19 +481,20 @@ impl<'src> ScopeState { /// fn(Scope) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn component

( + pub fn component<'child, P>( &'src self, - component: fn(Scope<'src, P>) -> Element<'src>, + component: fn(Scope<'child, P>) -> Element<'child>, props: P, fn_name: &'static str, ) -> DynamicNode<'src> where - P: Properties + 'src, + P: Properties + 'child, + 'src: 'child, { let vcomp = VProps::new(component, P::memoize, props); // cast off the lifetime of the render return - let as_dyn: Box + '_> = Box::new(vcomp); + let as_dyn: Box + '_> = Box::new(vcomp); let extended: Box + 'src> = unsafe { std::mem::transmute(as_dyn) }; DynamicNode::Component(VComponent {