Compare commits

...

30 Commits

Author SHA1 Message Date
Greg Johnston
2faae43d5f v0.8.0-beta 2025-03-19 21:02:10 -04:00
Nicolas Cura
6760c87e83 Merge pull request #3727 from NCura/patch-1
Fix typo
2025-03-19 21:02:10 -04:00
autofix-ci[bot]
e19e42c650 [autofix.ci] apply automated fixes 2025-03-19 23:42:02 +00:00
zakstucke
fb4be49ebf feat: remove SendWrapper from the external interface of LocalResource (#3715) 2025-03-19 19:26:14 -04:00
Greg Johnston
0b4cbbc17d Merge remote-tracking branch 'origin' into leptos_0.8 2025-03-16 14:27:23 -04:00
Greg Johnston
dbbeb7c6ef fix: don't retrigger rendering when only param has changed (closes #3719( 2025-03-16 14:26:23 -04:00
Greg Johnston
36aef2565d chore: merge issues 2025-03-16 14:25:31 -04:00
Greg Johnston
a2a7eb8a2a Merge remote-tracking branch 'origin' into leptos_0.8 2025-03-16 14:21:21 -04:00
Zak Stucke
97e22e2506 Extra trait impls for MaybeSendWrapper 2025-03-16 14:20:35 -04:00
autofix-ci[bot]
8bedacb0c7 [autofix.ci] apply automated fixes 2025-03-16 14:20:35 -04:00
Zak Stucke
56b7b9a16a Remove SendWrapper from the external interface of LocalResource, by internalising a MaybeSendWrapper inside ArcAsyncDerived 2025-03-16 14:20:35 -04:00
Greg Johnston
d04d4c77f9 Merge pull request #3720 from metatoaster/regression_tests_3502
test: regression from #3502
2025-03-16 14:16:05 -04:00
Tommy Yu
5c75928b5b test: avoiding testdriver browser contention
- The number of tests have increased sufficiently that the browser test
  instances spawned by the driver are choked out on CI.
2025-03-16 22:43:21 +13:00
Tommy Yu
abc5631654 test: regression from #3502
- Also as reported in #3719, which has an actual minimum example.
- The "quicker" test had a reset but that runs into timing issue with
  the way this reset is done too soon after resource usage, so leaving
  this out and we will just trust the bigger counters.
2025-03-16 22:43:21 +13:00
Tommy Yu
40e5288ac1 fix instrumented use_context
- It shouldn't be in on_cleanup, move into it from the component to
  avoid BorrowMut error.
2025-03-16 22:43:21 +13:00
Greg Johnston
335934d40e Merge pull request #3716 from zakstucke/string-opt
view!{} macro optimisation: don't wrap string types in closures when passing to ToChildren
2025-03-15 10:56:40 -04:00
Greg Johnston
6ee72f42e2 Merge pull request #3687 from leptos-rs/3671
Various issues related to setting signals and context in cleanups
2025-03-15 10:34:53 -04:00
Thomas Versteeg
93af23a970 feat: allow closures for shell parameter in file_and_error_handler* (#3711) 2025-03-15 10:24:09 -04:00
Danik Vitek
95e8ae84af feat(reactive_stores): Replace AsRef bound of StoreFieldIterator blanket impl with Len bound (#3701) 2025-03-15 10:23:09 -04:00
Danik Vitek
5cfe7f6b5e fix: make tuple struct field locator in Patch impl Index instead of usize (#3700) 2025-03-15 10:19:41 -04:00
Greg Johnston
0404efd5c3 fix: ensure that store subfield mutations notify from the root down (closes #3704) (#3714) 2025-03-15 10:04:52 -04:00
Zak Stucke
93173c1400 view!} macro optimisation: don't wrap string types in closures when passing to ToChildren 2025-03-15 03:17:20 +00:00
Tommy Yu
cd2904f6a6 chore: prep common base to share example with 0.8 2025-03-15 14:35:55 +13:00
Greg Johnston
6b453845f9 fix: allow NodeRef to work with AttributeInterceptor (closes #3697) 2025-03-13 21:38:28 -04:00
zakstucke
111b84ce3b feat: allow LocalResource sync methods to be used outside Suspense (#3708) 2025-03-13 21:36:51 -04:00
zakstucke
5633148047 feat: allow LocalResource sync methods to be used outside Suspense (#3708) 2025-03-13 09:28:00 -04:00
autofix-ci[bot]
4edb012de3 [autofix.ci] apply automated fixes 2025-03-13 01:16:55 +00:00
Greg Johnston
330920eae2 chore: clippy 2025-03-10 10:14:46 -04:00
Greg Johnston
a94bc0a6da fix: only store a weak reference to an Owner in the current thread (see #3671) 2025-03-10 10:14:46 -04:00
Greg Johnston
f85e01f4d6 fix: do not panic unnecessarily in try_ methods on Arena (closes #3671) 2025-03-10 10:14:46 -04:00
38 changed files with 1045 additions and 291 deletions

42
Cargo.lock generated
View File

@@ -1776,7 +1776,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "leptos"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"any_spawner",
"base64",
@@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "leptos_actix"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"actix-files",
"actix-http",
@@ -1852,7 +1852,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"any_spawner",
"axum",
@@ -1876,7 +1876,7 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"config",
"regex",
@@ -1890,7 +1890,7 @@ dependencies = [
[[package]]
name = "leptos_dom"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"js-sys",
"leptos",
@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "leptos_hot_reload"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"anyhow",
"camino",
@@ -1923,7 +1923,7 @@ dependencies = [
[[package]]
name = "leptos_integration_utils"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"futures",
"hydration_context",
@@ -1936,7 +1936,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1955,7 +1955,7 @@ dependencies = [
"rstml",
"serde",
"server_fn",
"server_fn_macro 0.8.0-alpha",
"server_fn_macro 0.8.0-beta",
"syn 2.0.100",
"tracing",
"trybuild",
@@ -1965,7 +1965,7 @@ dependencies = [
[[package]]
name = "leptos_meta"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"futures",
"indexmap",
@@ -1980,7 +1980,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"any_spawner",
"either_of",
@@ -2004,7 +2004,7 @@ dependencies = [
[[package]]
name = "leptos_router_macro"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"leptos_macro",
"leptos_router",
@@ -2016,7 +2016,7 @@ dependencies = [
[[package]]
name = "leptos_server"
version = "0.8.0-alpha2"
version = "0.8.0-beta"
dependencies = [
"any_spawner",
"base64",
@@ -2732,7 +2732,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.2.0-alpha2"
version = "0.2.0-beta"
dependencies = [
"any_spawner",
"async-lock",
@@ -2754,7 +2754,7 @@ dependencies = [
[[package]]
name = "reactive_stores"
version = "0.2.0-alpha"
version = "0.2.0-beta"
dependencies = [
"any_spawner",
"guardian",
@@ -2771,7 +2771,7 @@ dependencies = [
[[package]]
name = "reactive_stores_macro"
version = "0.2.0-alpha"
version = "0.2.0-beta"
dependencies = [
"convert_case 0.7.1",
"proc-macro-error2",
@@ -3228,7 +3228,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"actix-web",
"actix-ws",
@@ -3289,7 +3289,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"const_format",
"convert_case 0.6.0",
@@ -3301,9 +3301,9 @@ dependencies = [
[[package]]
name = "server_fn_macro_default"
version = "0.8.0-alpha"
version = "0.8.0-beta"
dependencies = [
"server_fn_macro 0.8.0-alpha",
"server_fn_macro 0.8.0-beta",
"syn 2.0.100",
]
@@ -3497,7 +3497,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.2.0-alpha"
version = "0.2.0-beta"
dependencies = [
"any_spawner",
"async-trait",

View File

@@ -40,7 +40,7 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.8.0-alpha"
version = "0.8.0-beta"
edition = "2021"
rust-version = "1.76"
@@ -51,27 +51,27 @@ const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.5" }
hydration_context = { path = "./hydration_context", version = "0.3.0" }
itertools = "0.14.0"
leptos = { path = "./leptos", version = "0.8.0-alpha" }
leptos_config = { path = "./leptos_config", version = "0.8.0-alpha" }
leptos_dom = { path = "./leptos_dom", version = "0.8.0-alpha" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-alpha" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-alpha" }
leptos_macro = { path = "./leptos_macro", version = "0.8.0-alpha" }
leptos_router = { path = "./router", version = "0.8.0-alpha" }
leptos_router_macro = { path = "./router_macro", version = "0.8.0-alpha" }
leptos_server = { path = "./leptos_server", version = "0.8.0-alpha" }
leptos_meta = { path = "./meta", version = "0.8.0-alpha" }
leptos = { path = "./leptos", version = "0.8.0-beta" }
leptos_config = { path = "./leptos_config", version = "0.8.0-beta" }
leptos_dom = { path = "./leptos_dom", version = "0.8.0-beta" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-beta" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-beta" }
leptos_macro = { path = "./leptos_macro", version = "0.8.0-beta" }
leptos_router = { path = "./router", version = "0.8.0-beta" }
leptos_router_macro = { path = "./router_macro", version = "0.8.0-beta" }
leptos_server = { path = "./leptos_server", version = "0.8.0-beta" }
leptos_meta = { path = "./meta", version = "0.8.0-beta" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.0" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.0-alpha" }
reactive_stores = { path = "./reactive_stores", version = "0.2.0-alpha" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0-alpha" }
reactive_graph = { path = "./reactive_graph", version = "0.2.0-beta" }
reactive_stores = { path = "./reactive_stores", version = "0.2.0-beta" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0-beta" }
serde_json = "1.0.0"
server_fn = { path = "./server_fn", version = "0.8.0-alpha" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-alpha" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-alpha" }
tachys = { path = "./tachys", version = "0.2.0-alpha" }
server_fn = { path = "./server_fn", version = "0.8.0-beta" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-beta" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-beta" }
tachys = { path = "./tachys", version = "0.2.0-beta" }
wasm-bindgen = { version = "0.2.100" }
[profile.release]

View File

@@ -24,7 +24,7 @@ pub fn RouterExample() -> impl IntoView {
// contexts are passed down through the route tree
provide_context(ExampleContext(0));
// this signal will be ued to set whether we are allowed to access a protected route
// this signal will be used to set whether we are allowed to access a protected route
let (logged_in, set_logged_in) = signal(true);
let (is_routing, set_is_routing) = signal(false);

View File

@@ -0,0 +1,99 @@
@check_instrumented_issue_3719
Feature: Using instrumented counters to test regression from #3502.
Check that the suspend/suspense and the underlying resources are
called with the expected number of times. If this was already in
place by #3502 (5c43c18) it should have caught this regression.
For a better minimum demonstration see #3719.
Background:
Given I see the app
And I select the mode Instrumented
Scenario: follow all paths via CSR avoids #3502
Given I select the following links
| Item Listing |
| Item 1 |
| Inspect path2 |
| Inspect path2/field3 |
And I click on Reset CSR Counters
When I select the following links
| Inspect path2/field1 |
| Inspect path2/field2 |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 0 |
| item_inspect | 2 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 0 |
| inspect_item_root | 0 |
| inspect_item_field | 2 |
# To show that starting directly from within a param will simply
# cause the problem.
Scenario: Quicker way to demonstrate regression caused by #3502
Given I select the link Target 123
# And I click on Reset CSR Counters
When I select the following links
| Inspect path2/field1 |
| Inspect path2/field2 |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 0 |
| item_inspect | 3 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 1 |
| get_item | 1 |
| inspect_item_root | 0 |
| inspect_item_field | 4 |
Scenario: Follow paths ordinarily down to a target
Given I select the following links
| Item Listing |
| Item 1 |
And I click on Reset CSR Counters
When I select the following links
| Target 4## |
| Target 3## |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 2 |
| item_inspect | 0 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 2 |
| inspect_item_root | 0 |
| inspect_item_field | 0 |
Scenario: Same as above, but add a refresh to test hydration
Given I select the following links
| Item Listing |
| Item 1 |
And I refresh the page
And I click on Reset CSR Counters
When I select the following links
| Target 4## |
| Target 3## |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 2 |
| item_inspect | 0 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 2 |
| inspect_item_root | 0 |
| inspect_item_field | 0 |

View File

@@ -3,12 +3,28 @@ mod fixtures;
use anyhow::Result;
use cucumber::World;
use fixtures::world::AppWorld;
use std::{ffi::OsStr, fs::read_dir};
#[tokio::main]
async fn main() -> Result<()> {
AppWorld::cucumber()
.fail_on_skipped()
.run_and_exit("./features")
.await;
// Normally the below is done, but it's now gotten to the point of
// having a sufficient number of tests where the resource contention
// of the concurrently running browsers will cause failures on CI.
// AppWorld::cucumber()
// .fail_on_skipped()
// .run_and_exit("./features")
// .await;
// Mitigate the issue by manually stepping through each feature,
// rather than letting cucumber glob them and dispatch all at once.
for entry in read_dir("./features")? {
let path = entry?.path();
if path.extension() == Some(OsStr::new("feature")) {
AppWorld::cucumber()
.fail_on_skipped()
.run_and_exit(path)
.await;
}
}
Ok(())
}

View File

@@ -492,8 +492,9 @@ fn ItemInspect() -> impl IntoView {
// result
},
);
on_cleanup(|| {
if let Some(c) = use_context::<WriteSignal<Option<FieldNavCtx>>>() {
let ws = use_context::<WriteSignal<Option<FieldNavCtx>>>();
on_cleanup(move || {
if let Some(c) = ws {
c.set(None);
}
});

View File

@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Axum integrations for the Leptos web framework."
version = "0.8.0-alpha2"
version = "0.8.0-beta"
rust-version.workspace = true
edition.workspace = true

View File

@@ -2013,7 +2013,7 @@ where
#[cfg(feature = "default")]
pub fn file_and_error_handler_with_context<S, IV>(
additional_context: impl Fn() + 'static + Clone + Send,
shell: fn(LeptosOptions) -> IV,
shell: impl Fn(LeptosOptions) -> IV + 'static + Clone + Send,
) -> impl Fn(
Uri,
State<S>,
@@ -2030,6 +2030,7 @@ where
move |uri: Uri, State(state): State<S>, req: Request<Body>| {
Box::pin({
let additional_context = additional_context.clone();
let shell = shell.clone();
async move {
let options = LeptosOptions::from_ref(&state);
let res =
@@ -2073,7 +2074,7 @@ where
/// simply reuse the source code of this function in your own application.
#[cfg(feature = "default")]
pub fn file_and_error_handler<S, IV>(
shell: fn(LeptosOptions) -> IV,
shell: impl Fn(LeptosOptions) -> IV + 'static + Clone + Send,
) -> impl Fn(
Uri,
State<S>,

View File

@@ -91,6 +91,9 @@ pub trait ToChildren<F> {
fn to_children(f: F) -> Self;
}
/// Compiler optimisation, can be used with certain type to avoid unique closures in the view!{} macro.
pub struct ChildrenOptContainer<T>(pub T);
impl<F, C> ToChildren<F> for Children
where
F: FnOnce() -> C + Send + 'static,
@@ -102,6 +105,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for Children
where
T: IntoAny + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Box::new(move || t.0.into_any())
}
}
impl<F, C> ToChildren<F> for ChildrenFn
where
F: Fn() -> C + Send + Sync + 'static,
@@ -113,6 +126,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFn
where
T: IntoAny + Clone + Send + Sync + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Arc::new(move || t.0.clone().into_any())
}
}
impl<F, C> ToChildren<F> for ChildrenFnMut
where
F: Fn() -> C + Send + 'static,
@@ -124,6 +147,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFnMut
where
T: IntoAny + Clone + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Box::new(move || t.0.clone().into_any())
}
}
impl<F, C> ToChildren<F> for BoxedChildrenFn
where
F: Fn() -> C + Send + 'static,
@@ -135,6 +168,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for BoxedChildrenFn
where
T: IntoAny + Clone + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Box::new(move || t.0.clone().into_any())
}
}
impl<F, C> ToChildren<F> for ChildrenFragment
where
F: FnOnce() -> C + Send + 'static,
@@ -146,6 +189,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragment
where
T: IntoAny + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Box::new(move || Fragment::new(vec![t.0.into_any()]))
}
}
impl<F, C> ToChildren<F> for ChildrenFragmentFn
where
F: Fn() -> C + Send + 'static,
@@ -157,6 +210,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragmentFn
where
T: IntoAny + Clone + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Arc::new(move || Fragment::new(vec![t.0.clone().into_any()]))
}
}
impl<F, C> ToChildren<F> for ChildrenFragmentMut
where
F: FnMut() -> C + Send + 'static,
@@ -168,6 +231,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragmentMut
where
T: IntoAny + Clone + Send + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
Box::new(move || Fragment::new(vec![t.0.clone().into_any()]))
}
}
/// New-type wrapper for a function that returns a view with `From` and `Default` traits implemented
/// to enable optional props in for example `<Show>` and `<Suspense>`.
#[derive(Clone)]
@@ -246,6 +319,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildren<T>
where
T: IntoView + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
TypedChildren(Box::new(move || t.0.into_view()))
}
}
/// A typed equivalent to [`ChildrenFnMut`], which takes a generic but preserves type information to
/// allow the compiler to optimize the view more effectively.
pub struct TypedChildrenMut<T>(Box<dyn FnMut() -> View<T> + Send>);
@@ -275,6 +358,16 @@ where
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildrenMut<T>
where
T: IntoView + Clone + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
TypedChildrenMut(Box::new(move || t.0.clone().into_view()))
}
}
/// A typed equivalent to [`ChildrenFn`], which takes a generic but preserves type information to
/// allow the compiler to optimize the view more effectively.
pub struct TypedChildrenFn<T>(Arc<dyn Fn() -> View<T> + Send + Sync>);
@@ -310,3 +403,13 @@ where
TypedChildrenFn(Arc::new(move || f().into_view()))
}
}
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildrenFn<T>
where
T: IntoView + Clone + Sync + 'static,
{
#[inline]
fn to_children(t: ChildrenOptContainer<T>) -> Self {
TypedChildrenFn(Arc::new(move || t.0.clone().into_view()))
}
}

View File

@@ -1,15 +1,19 @@
use super::{
fragment_to_tokens, utils::is_nostrip_optional_and_update_key, TagType,
};
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
use crate::view::{
attribute_absolute, text_to_tokens, utils::filter_prefixed_attrs,
};
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use rstml::node::{
CustomNode, KeyedAttributeValue, NodeAttribute, NodeBlock, NodeElement,
NodeName,
CustomNode, KeyedAttributeValue, Node, NodeAttribute, NodeBlock,
NodeElement, NodeName,
};
use std::collections::HashMap;
use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt};
use syn::{
spanned::Spanned, Expr, ExprPath, ExprRange, Item, RangeLimits, Stmt,
};
pub(crate) fn component_to_tokens(
node: &mut NodeElement<impl CustomNode>,
@@ -197,6 +201,12 @@ pub(crate) fn component_to_tokens(
let mut slots = HashMap::new();
let children = if node.children.is_empty() {
quote! {}
} else if let Some(children) = maybe_optimised_component_children(
&node.children,
&items_to_bind,
&items_to_clone,
) {
children
} else {
let children = fragment_to_tokens(
&mut node.children,
@@ -225,10 +235,7 @@ pub(crate) fn component_to_tokens(
let bindables =
items_to_bind.iter().map(|ident| quote! { #ident, });
let clonables = items_to_clone.iter().map(|ident| {
let ident_ref = quote_spanned!(ident.span()=> &#ident);
quote! { let #ident = ::core::clone::Clone::clone(#ident_ref); }
});
let clonables = items_to_clone_to_tokens(&items_to_clone);
if bindables.len() > 0 {
quote_spanned! {children.span()=>
@@ -319,3 +326,111 @@ fn is_attr_let(key: &NodeName) -> bool {
false
}
}
pub fn items_to_clone_to_tokens<'a>(
items_to_clone: &'a [Ident],
) -> impl Iterator<Item = TokenStream> + 'a {
items_to_clone.iter().map(|ident| {
let ident_ref = quote_spanned!(ident.span()=> &#ident);
quote! { let #ident = ::core::clone::Clone::clone(#ident_ref); }
})
}
/// By default all children are placed in an outer closure || #children.
/// This is to work with all the variants of the leptos::children::ToChildren::to_children trait.
/// Strings are optimised to be passed without the wrapping closure, providing significant compile time and binary size improvements.
pub fn maybe_optimised_component_children(
children: &[Node<impl CustomNode>],
items_to_bind: &[TokenStream],
items_to_clone: &[Ident],
) -> Option<TokenStream> {
// If there are bindables will have to be in a closure:
if !items_to_bind.is_empty() {
return None;
}
// Filter out comments:
let mut children_iter = children
.iter()
.filter(|child| !matches!(child, Node::Comment(_)));
let children = if let Some(child) = children_iter.next() {
// If more than one child after filtering out comments, don't think we can optimise:
if children_iter.next().is_some() {
return None;
}
match child {
Node::Text(text) => text_to_tokens(&text.value),
Node::RawText(raw) => {
let text = raw.to_string_best();
let text = syn::LitStr::new(&text, raw.span());
text_to_tokens(&text)
}
// Specifically allow std macros that produce strings:
Node::Block(NodeBlock::ValidBlock(block)) => {
fn is_supported(mac: &syn::Macro) -> bool {
for string_macro in ["format", "include_str"] {
if mac.path.is_ident(string_macro) {
return true;
}
}
false
}
if block.stmts.len() > 1 {
return None;
} else if let Some(stmt) = block.stmts.first() {
match stmt {
Stmt::Macro(mac) => {
// eprintln!("Macro: {:?}", mac.mac.path);
if is_supported(&mac.mac) {
quote! { #block }
} else {
return None;
}
}
Stmt::Item(Item::Macro(mac)) => {
// eprintln!("Item Macro: {:?}", mac.mac.path);
if is_supported(&mac.mac) {
quote! { #block }
} else {
return None;
}
}
Stmt::Expr(Expr::Macro(mac), _) => {
// eprintln!("Expr Macro: {:?}", mac.mac.path);
if is_supported(&mac.mac) {
quote! { #block }
} else {
return None;
}
}
_ => return None,
}
} else {
return Some(quote! {});
}
}
_ => return None,
}
} else {
return None;
};
// // Debug check to see how many use this optimisation:
// static COUNT: std::sync::atomic::AtomicUsize =
// std::sync::atomic::AtomicUsize::new(0);
// COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
// eprintln!(
// "Optimised children: {}",
// COUNT.load(std::sync::atomic::Ordering::Relaxed)
// );
let clonables = items_to_clone_to_tokens(items_to_clone);
Some(quote_spanned! {children.span()=>
.children({
#(#clonables)*
::leptos::children::ToChildren::to_children(::leptos::children::ChildrenOptContainer(#children))
})
})
}

View File

@@ -1,4 +1,7 @@
use super::{convert_to_snake_case, ident_from_tag_name};
use super::{
component_builder::maybe_optimised_component_children,
convert_to_snake_case, ident_from_tag_name,
};
use crate::view::{fragment_to_tokens, utils::filter_prefixed_attrs, TagType};
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{quote, quote_spanned};
@@ -70,7 +73,10 @@ pub(crate) fn slot_to_tokens(
}
});
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:");
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:")
.into_iter()
.map(|ident| quote! { #ident })
.collect::<Vec<_>>();
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
@@ -96,6 +102,12 @@ pub(crate) fn slot_to_tokens(
let mut slots = HashMap::new();
let children = if node.children.is_empty() {
quote! {}
} else if let Some(children) = maybe_optimised_component_children(
&node.children,
&items_to_bind,
&items_to_clone,
) {
children
} else {
let children = fragment_to_tokens(
&mut node.children,

View File

@@ -2,7 +2,7 @@
name = "leptos_server"
# TODO revert to { workspace = true } before 0.8.0 release
# this is a hack because I missing bumping the hydration_context version number before publishing
version = "0.8.0-alpha2"
version = "0.8.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -8,15 +8,15 @@ use reactive_graph::{
ToAnySource, ToAnySubscriber,
},
owner::use_context,
send_wrapper_ext::MaybeSendWrapperOption,
signal::{
guards::{AsyncPlain, ReadGuard},
guards::{AsyncPlain, Mapped, ReadGuard},
ArcRwSignal, RwSignal,
},
traits::{
DefinedAt, IsDisposed, ReadUntracked, Track, Update, With, Write,
},
};
use send_wrapper::SendWrapper;
use std::{
future::{pending, Future, IntoFuture},
panic::Location,
@@ -24,7 +24,7 @@ use std::{
/// A reference-counted resource that only loads its data locally on the client.
pub struct ArcLocalResource<T> {
data: ArcAsyncDerived<SendWrapper<T>>,
data: ArcAsyncDerived<T>,
refetch: ArcRwSignal<usize>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
@@ -71,14 +71,12 @@ impl<T> ArcLocalResource<T> {
}
}
};
let fetcher = SendWrapper::new(fetcher);
let refetch = ArcRwSignal::new(0);
let data = {
let refetch = refetch.clone();
ArcAsyncDerived::new(move || {
ArcAsyncDerived::new_unsync(move || {
refetch.track();
let fut = fetcher();
SendWrapper::new(async move { SendWrapper::new(fut.await) })
fetcher()
})
};
Self {
@@ -97,7 +95,7 @@ impl<T> ArcLocalResource<T> {
/// Synchronously, reactively reads the current value of the resource and applies the function
/// `f` to its value if it is `Some(_)`.
#[track_caller]
pub fn map<U>(&self, f: impl FnOnce(&SendWrapper<T>) -> U) -> Option<U>
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
where
T: 'static,
{
@@ -128,14 +126,9 @@ where
T: Clone + 'static,
{
type Output = T;
type IntoFuture = futures::future::Map<
AsyncDerivedFuture<SendWrapper<T>>,
fn(SendWrapper<T>) -> T,
>;
type IntoFuture = AsyncDerivedFuture<T>;
fn into_future(self) -> Self::IntoFuture {
use futures::FutureExt;
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
notifier.notify();
} else if cfg!(feature = "ssr") {
@@ -145,7 +138,7 @@ where
always pending on the server."
);
}
self.data.into_future().map(|value| (*value).clone())
self.data.into_future()
}
}
@@ -166,18 +159,14 @@ impl<T> ReadUntracked for ArcLocalResource<T>
where
T: 'static,
{
type Value =
ReadGuard<Option<SendWrapper<T>>, AsyncPlain<Option<SendWrapper<T>>>>;
type Value = ReadGuard<
Option<T>,
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
>;
fn try_read_untracked(&self) -> Option<Self::Value> {
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
notifier.notify();
} else if cfg!(feature = "ssr") {
panic!(
"Reading from a LocalResource outside Suspense in `ssr` mode \
will cause the response to hang, because LocalResources are \
always pending on the server."
);
}
self.data.try_read_untracked()
}
@@ -246,7 +235,7 @@ impl<T> Subscriber for ArcLocalResource<T> {
/// A resource that only loads its data locally on the client.
pub struct LocalResource<T> {
data: AsyncDerived<SendWrapper<T>>,
data: AsyncDerived<T>,
refetch: RwSignal<usize>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
@@ -296,11 +285,9 @@ impl<T> LocalResource<T> {
data: if cfg!(feature = "ssr") {
AsyncDerived::new_mock(fetcher)
} else {
let fetcher = SendWrapper::new(fetcher);
AsyncDerived::new(move || {
AsyncDerived::new_unsync_threadsafe_storage(move || {
refetch.track();
let fut = fetcher();
SendWrapper::new(async move { SendWrapper::new(fut.await) })
fetcher()
})
},
refetch,
@@ -320,14 +307,9 @@ where
T: Clone + 'static,
{
type Output = T;
type IntoFuture = futures::future::Map<
AsyncDerivedFuture<SendWrapper<T>>,
fn(SendWrapper<T>) -> T,
>;
type IntoFuture = AsyncDerivedFuture<T>;
fn into_future(self) -> Self::IntoFuture {
use futures::FutureExt;
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
notifier.notify();
} else if cfg!(feature = "ssr") {
@@ -337,7 +319,7 @@ where
always pending on the server."
);
}
self.data.into_future().map(|value| (*value).clone())
self.data.into_future()
}
}
@@ -358,18 +340,14 @@ impl<T> ReadUntracked for LocalResource<T>
where
T: 'static,
{
type Value =
ReadGuard<Option<SendWrapper<T>>, AsyncPlain<Option<SendWrapper<T>>>>;
type Value = ReadGuard<
Option<T>,
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
>;
fn try_read_untracked(&self) -> Option<Self::Value> {
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
notifier.notify();
} else if cfg!(feature = "ssr") {
panic!(
"Reading from a LocalResource outside Suspense in `ssr` mode \
will cause the response to hang, because LocalResources are \
always pending on the server."
);
}
self.data.try_read_untracked()
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.8.0-alpha"
version = "0.8.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.2.0-alpha2"
version = "0.2.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -13,8 +13,9 @@ use crate::{
SubscriberSet, ToAnySource, ToAnySubscriber, WithObserver,
},
owner::{use_context, Owner},
send_wrapper_ext::MaybeSendWrapperOption,
signal::{
guards::{AsyncPlain, ReadGuard, WriteGuard},
guards::{AsyncPlain, Mapped, MappedMut, ReadGuard, WriteGuard},
ArcTrigger,
},
traits::{
@@ -28,11 +29,10 @@ use async_lock::RwLock as AsyncRwLock;
use core::fmt::Debug;
use futures::{channel::oneshot, FutureExt, StreamExt};
use or_poisoned::OrPoisoned;
use send_wrapper::SendWrapper;
use std::{
future::Future,
mem,
ops::DerefMut,
ops::{Deref, DerefMut},
panic::Location,
sync::{
atomic::{AtomicBool, Ordering},
@@ -110,7 +110,7 @@ pub struct ArcAsyncDerived<T> {
#[cfg(any(debug_assertions, leptos_debuginfo))]
pub(crate) defined_at: &'static Location<'static>,
// the current state of this signal
pub(crate) value: Arc<AsyncRwLock<Option<T>>>,
pub(crate) value: Arc<AsyncRwLock<MaybeSendWrapperOption<T>>>,
// holds wakers generated when you .await this
pub(crate) wakers: Arc<RwLock<Vec<Waker>>>,
pub(crate) inner: Arc<RwLock<ArcAsyncDerivedInner>>,
@@ -280,7 +280,7 @@ macro_rules! spawn_derived {
let mut guard = this.inner.write().or_poisoned();
guard.state = AsyncDerivedState::Clean;
*value.blocking_write() = Some(orig_value);
*value.blocking_write() = orig_value;
this.loading.store(false, Ordering::Relaxed);
(true, None)
}
@@ -405,14 +405,14 @@ macro_rules! spawn_derived {
impl<T: 'static> ArcAsyncDerived<T> {
async fn set_inner_value(
new_value: T,
value: Arc<AsyncRwLock<Option<T>>>,
new_value: MaybeSendWrapperOption<T>,
value: Arc<AsyncRwLock<MaybeSendWrapperOption<T>>>,
wakers: Arc<RwLock<Vec<Waker>>>,
inner: Arc<RwLock<ArcAsyncDerivedInner>>,
loading: Arc<AtomicBool>,
ready_tx: Option<oneshot::Sender<()>>,
) {
*value.write().await = Some(new_value);
*value.write().await.deref_mut() = new_value;
Self::notify_subs(&wakers, &inner, &loading, ready_tx);
}
@@ -479,6 +479,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
T: Send + Sync + 'static,
Fut: Future<Output = T> + Send + 'static,
{
let fun = move || {
let fut = fun();
async move { MaybeSendWrapperOption::new(Some(fut.await)) }
};
let initial_value = MaybeSendWrapperOption::new(initial_value);
let (this, _) = spawn_derived!(
Executor::spawn,
initial_value,
@@ -508,6 +513,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
Fut: Future<Output = T> + Send + 'static,
S: Track,
{
let fun = move || {
let fut = fun();
async move { MaybeSendWrapperOption::new(Some(fut.await)) }
};
let initial_value = MaybeSendWrapperOption::new(initial_value);
let (this, _) = spawn_derived!(
Executor::spawn,
initial_value,
@@ -545,6 +555,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
T: 'static,
Fut: Future<Output = T> + 'static,
{
let fun = move || {
let fut = fun();
async move { MaybeSendWrapperOption::new_local(Some(fut.await)) }
};
let initial_value = MaybeSendWrapperOption::new_local(initial_value);
let (this, _) = spawn_derived!(
Executor::spawn_local,
initial_value,
@@ -567,7 +582,7 @@ impl<T: 'static> ArcAsyncDerived<T> {
}
}
impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
impl<T: 'static> ArcAsyncDerived<T> {
#[doc(hidden)]
#[track_caller]
pub fn new_mock<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
@@ -575,13 +590,10 @@ impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
T: 'static,
Fut: Future<Output = T> + 'static,
{
let initial = None::<SendWrapper<T>>;
let initial = MaybeSendWrapperOption::new_local(None::<T>);
let fun = move || {
let fut = fun();
async move {
let value = fut.await;
SendWrapper::new(value)
}
async move { MaybeSendWrapperOption::new_local(Some(fut.await)) }
};
let (this, _) = spawn_derived!(
Executor::spawn_local,
@@ -597,7 +609,10 @@ impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
}
impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
type Value = ReadGuard<Option<T>, AsyncPlain<Option<T>>>;
type Value = ReadGuard<
Option<T>,
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
>;
fn try_read_untracked(&self) -> Option<Self::Value> {
if let Some(suspense_context) = use_context::<SuspenseContext>() {
@@ -613,7 +628,9 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
.suspenses
.push(suspense_context);
}
AsyncPlain::try_new(&self.value).map(ReadGuard::new)
AsyncPlain::try_new(&self.value).map(|plain| {
ReadGuard::new(Mapped::new_with_guard(plain, |v| v.deref()))
})
}
}
@@ -627,13 +644,21 @@ impl<T: 'static> Write for ArcAsyncDerived<T> {
type Value = Option<T>;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
Some(WriteGuard::new(self.clone(), self.value.blocking_write()))
Some(MappedMut::new(
WriteGuard::new(self.clone(), self.value.blocking_write()),
|v| v.deref(),
|v| v.deref_mut(),
))
}
fn try_write_untracked(
&self,
) -> Option<impl DerefMut<Target = Self::Value>> {
Some(self.value.blocking_write())
Some(MappedMut::new(
self.value.blocking_write(),
|v| v.deref(),
|v| v.deref_mut(),
))
}
}

View File

@@ -5,7 +5,8 @@ use crate::{
ToAnySource, ToAnySubscriber,
},
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
send_wrapper_ext::MaybeSendWrapperOption,
signal::guards::{AsyncPlain, Mapped, MappedMut, ReadGuard, WriteGuard},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
UntrackableGuard, Write,
@@ -13,8 +14,11 @@ use crate::{
unwrap_signal,
};
use core::fmt::Debug;
use send_wrapper::SendWrapper;
use std::{future::Future, ops::DerefMut, panic::Location};
use std::{
future::Future,
ops::{Deref, DerefMut},
panic::Location,
};
/// A reactive value that is derived by running an asynchronous computation in response to changes
/// in its sources.
@@ -94,9 +98,10 @@ impl<T, S> Dispose for AsyncDerived<T, S> {
}
}
impl<T> From<ArcAsyncDerived<T>> for AsyncDerived<T>
impl<T, S> From<ArcAsyncDerived<T>> for AsyncDerived<T, S>
where
T: Send + Sync + 'static,
T: 'static,
S: Storage<ArcAsyncDerived<T>>,
{
fn from(value: ArcAsyncDerived<T>) -> Self {
#[cfg(any(debug_assertions, leptos_debuginfo))]
@@ -109,12 +114,13 @@ where
}
}
impl<T> From<AsyncDerived<T>> for ArcAsyncDerived<T>
impl<T, S> From<AsyncDerived<T, S>> for ArcAsyncDerived<T>
where
T: Send + Sync + 'static,
T: 'static,
S: Storage<ArcAsyncDerived<T>>,
{
#[track_caller]
fn from(value: AsyncDerived<T>) -> Self {
fn from(value: AsyncDerived<T, S>) -> Self {
value
.inner
.try_get_value()
@@ -179,7 +185,7 @@ where
}
}
impl<T> AsyncDerived<SendWrapper<T>> {
impl<T> AsyncDerived<T> {
#[doc(hidden)]
pub fn new_mock<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
where
@@ -192,6 +198,24 @@ impl<T> AsyncDerived<SendWrapper<T>> {
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
}
}
/// Same as [`AsyncDerived::new_unsync`] except it produces AsyncDerived<T> instead of AsyncDerived<T, LocalStorage>.
/// The internal value will still be wrapped in a [`send_wrapper::SendWrapper`].
pub fn new_unsync_threadsafe_storage<Fut>(
fun: impl Fn() -> Fut + 'static,
) -> Self
where
T: 'static,
Fut: Future<Output = T> + 'static,
{
Self {
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
fun,
)),
}
}
}
impl<T> AsyncDerived<T, LocalStorage>
@@ -294,7 +318,10 @@ where
T: 'static,
S: Storage<ArcAsyncDerived<T>>,
{
type Value = ReadGuard<Option<T>, AsyncPlain<Option<T>>>;
type Value = ReadGuard<
Option<T>,
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
>;
fn try_read_untracked(&self) -> Option<Self::Value> {
self.inner
@@ -324,13 +351,21 @@ where
let guard = self
.inner
.try_with_value(|n| n.value.blocking_write_arc())?;
Some(WriteGuard::new(*self, guard))
Some(MappedMut::new(
WriteGuard::new(*self, guard),
|v| v.deref(),
|v| v.deref_mut(),
))
}
fn try_write_untracked(
&self,
) -> Option<impl DerefMut<Target = Self::Value>> {
self.inner.try_with_value(|n| n.value.blocking_write_arc())
self.inner
.try_with_value(|n| n.value.blocking_write_arc())
.map(|inner| {
MappedMut::new(inner, |v| v.deref(), |v| v.deref_mut())
})
}
}

View File

@@ -4,6 +4,7 @@ use crate::{
diagnostics::SpecialNonReactiveZone,
graph::{AnySource, ToAnySource},
owner::{use_context, Storage},
send_wrapper_ext::MaybeSendWrapperOption,
signal::guards::{AsyncPlain, Mapped, ReadGuard},
traits::{DefinedAt, Track},
unwrap_signal,
@@ -24,7 +25,8 @@ use std::{
///
/// Implements [`Deref`](std::ops::Deref) to access the inner value. This should not be held longer
/// than it is needed, as it prevents updates to the inner value.
pub type AsyncDerivedGuard<T> = ReadGuard<T, Mapped<AsyncPlain<Option<T>>, T>>;
pub type AsyncDerivedGuard<T> =
ReadGuard<T, Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, T>>;
/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading,
/// but does not contain its value.
@@ -106,7 +108,7 @@ where
/// and contains its value. `.await`ing this clones the value `T`.
pub struct AsyncDerivedFuture<T> {
source: AnySource,
value: Arc<async_lock::RwLock<Option<T>>>,
value: Arc<async_lock::RwLock<MaybeSendWrapperOption<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
inner: Arc<RwLock<ArcAsyncDerivedInner>>,
@@ -183,7 +185,7 @@ where
/// and yields an [`AsyncDerivedGuard`] that dereferences to its value.
pub struct AsyncDerivedRefFuture<T> {
source: AnySource,
value: Arc<async_lock::RwLock<Option<T>>>,
value: Arc<async_lock::RwLock<MaybeSendWrapperOption<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
}

View File

@@ -81,6 +81,7 @@ pub mod diagnostics;
pub mod effect;
pub mod graph;
pub mod owner;
pub mod send_wrapper_ext;
#[cfg(feature = "serde")]
mod serde;
pub mod signal;

View File

@@ -60,6 +60,38 @@ pub struct Owner {
pub(crate) shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
}
impl Owner {
fn downgrade(&self) -> WeakOwner {
WeakOwner {
inner: Arc::downgrade(&self.inner),
#[cfg(feature = "hydration")]
shared_context: self.shared_context.as_ref().map(Arc::downgrade),
}
}
}
#[derive(Clone)]
struct WeakOwner {
inner: Weak<RwLock<OwnerInner>>,
#[cfg(feature = "hydration")]
shared_context: Option<Weak<dyn SharedContext + Send + Sync>>,
}
impl WeakOwner {
fn upgrade(&self) -> Option<Owner> {
self.inner.upgrade().map(|inner| {
#[cfg(feature = "hydration")]
let shared_context =
self.shared_context.as_ref().and_then(|sc| sc.upgrade());
Owner {
inner,
#[cfg(feature = "hydration")]
shared_context,
}
})
}
}
impl PartialEq for Owner {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
@@ -67,7 +99,7 @@ impl PartialEq for Owner {
}
thread_local! {
static OWNER: RefCell<Option<Owner>> = Default::default();
static OWNER: RefCell<Option<WeakOwner>> = Default::default();
}
impl Owner {
@@ -107,12 +139,16 @@ impl Owner {
/// Creates a new `Owner` and registers it as a child of the current `Owner`, if there is one.
pub fn new() -> Self {
#[cfg(not(feature = "hydration"))]
let parent = OWNER
.with(|o| o.borrow().as_ref().map(|o| Arc::downgrade(&o.inner)));
let parent = OWNER.with(|o| {
o.borrow()
.as_ref()
.and_then(|o| o.upgrade())
.map(|o| Arc::downgrade(&o.inner))
});
#[cfg(feature = "hydration")]
let (parent, shared_context) = OWNER
.with(|o| {
o.borrow().as_ref().map(|o| {
o.borrow().as_ref().and_then(|o| o.upgrade()).map(|o| {
(Some(Arc::downgrade(&o.inner)), o.shared_context.clone())
})
})
@@ -200,7 +236,7 @@ impl Owner {
/// Sets this as the current `Owner`.
pub fn set(&self) {
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
OWNER.with_borrow_mut(|owner| *owner = Some(self.downgrade()));
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self.inner.read().or_poisoned().arena);
}
@@ -208,9 +244,10 @@ impl Owner {
/// Runs the given function with this as the current `Owner`.
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
// codegen optimisation:
fn inner_1(self_: &Owner) -> Option<Owner> {
let prev =
{ OWNER.with(|o| (*o.borrow_mut()).replace(self_.clone())) };
fn inner_1(self_: &Owner) -> Option<WeakOwner> {
let prev = {
OWNER.with(|o| (*o.borrow_mut()).replace(self_.downgrade()))
};
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self_.inner.read().or_poisoned().arena);
prev
@@ -220,7 +257,7 @@ impl Owner {
let val = fun();
// monomorphisation optimisation:
fn inner_2(prev: Option<Owner>) {
fn inner_2(prev: Option<WeakOwner>) {
OWNER.with(|o| {
*o.borrow_mut() = prev;
});
@@ -266,7 +303,7 @@ impl Owner {
/// Returns the current `Owner`, if any.
pub fn current() -> Option<Owner> {
OWNER.with(|o| o.borrow().clone())
OWNER.with(|o| o.borrow().as_ref().and_then(|n| n.upgrade()))
}
/// Returns the [`SharedContext`] associated with this owner, if any.
@@ -280,7 +317,7 @@ impl Owner {
/// Removes this from its state as the thread-local owner and drops it.
pub fn unset(self) {
OWNER.with_borrow_mut(|owner| {
if owner.as_ref() == Some(&self) {
if owner.as_ref().and_then(|n| n.upgrade()) == Some(self) {
mem::take(owner);
}
})
@@ -293,6 +330,7 @@ impl Owner {
OWNER.with(|o| {
o.borrow()
.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
})
}
@@ -306,6 +344,7 @@ impl Owner {
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
});
match sc {
@@ -331,6 +370,7 @@ impl Owner {
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
});
match sc {

View File

@@ -48,20 +48,30 @@ impl Arena {
fun(&MAP.get_or_init(Default::default).read().or_poisoned())
}
#[cfg(feature = "sandboxed-arenas")]
{
Arena::try_with(fun).unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, but no \
Arena is active",
std::panic::Location::caller()
)
})
}
}
#[track_caller]
pub fn try_with<U>(fun: impl FnOnce(&ArenaMap) -> U) -> Option<U> {
#[cfg(not(feature = "sandboxed-arenas"))]
{
Some(fun(&MAP.get_or_init(Default::default).read().or_poisoned()))
}
#[cfg(feature = "sandboxed-arenas")]
{
MAP.with_borrow(|arena| {
fun(&arena
arena
.as_ref()
.and_then(Weak::upgrade)
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
but no Arena is active",
std::panic::Location::caller()
)
})
.read()
.or_poisoned())
.map(|n| fun(&n.read().or_poisoned()))
})
}
}
@@ -74,20 +84,32 @@ impl Arena {
}
#[cfg(feature = "sandboxed-arenas")]
{
let caller = std::panic::Location::caller();
Arena::try_with_mut(fun).unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, but no \
Arena is active",
std::panic::Location::caller()
)
})
}
}
#[track_caller]
pub fn try_with_mut<U>(fun: impl FnOnce(&mut ArenaMap) -> U) -> Option<U> {
#[cfg(not(feature = "sandboxed-arenas"))]
{
Some(fun(&mut MAP
.get_or_init(Default::default)
.write()
.or_poisoned()))
}
#[cfg(feature = "sandboxed-arenas")]
{
MAP.with_borrow(|arena| {
fun(&mut arena
arena
.as_ref()
.and_then(Weak::upgrade)
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
but no Arena is active",
caller
)
})
.write()
.or_poisoned())
.map(|n| fun(&mut n.write().or_poisoned()))
})
}
}
@@ -126,6 +148,7 @@ pub mod sandboxed {
/// called.
///
/// [item]:[crate::owner::ArenaItem]
#[track_caller]
pub fn new(inner: T) -> Self {
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
Self { arena, inner }

View File

@@ -53,7 +53,7 @@ where
})
};
OWNER.with(|o| {
if let Some(owner) = &*o.borrow() {
if let Some(owner) = o.borrow().as_ref().and_then(|o| o.upgrade()) {
owner.register(node);
}
});

View File

@@ -76,24 +76,26 @@ where
}
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
Arena::with(|arena| {
Arena::try_with(|arena| {
let m = arena.get(node);
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
})
.flatten()
}
fn try_with_mut<U>(
node: NodeId,
fun: impl FnOnce(&mut T) -> U,
) -> Option<U> {
Arena::with_mut(|arena| {
Arena::try_with_mut(|arena| {
let m = arena.get_mut(node);
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
})
.flatten()
}
fn try_set(node: NodeId, value: T) -> Option<T> {
Arena::with_mut(|arena| {
Arena::try_with_mut(|arena| {
let m = arena.get_mut(node);
match m.and_then(|n| n.downcast_mut::<T>()) {
Some(inner) => {
@@ -103,6 +105,7 @@ where
None => Some(value),
}
})
.flatten()
}
fn take(node: NodeId) -> Option<T> {

View File

@@ -0,0 +1,145 @@
//! Additional wrapper utilities for [`send_wrapper::SendWrapper`].
use send_wrapper::SendWrapper;
use std::{
fmt::{Debug, Formatter},
ops::{Deref, DerefMut},
};
/// An optional value that might be wrapped in [`SendWrapper`].
///
/// This struct is useful because:
/// - Can be dereffed to &Option<T>, even when T is wrapped in a SendWrapper.
/// - Until [`DerefMut`] is called, the None case will not construct a SendWrapper, so no panics if initialised when None and dropped on a different thread. Any access other than [`DerefMut`] will not construct a SendWrapper.
pub struct MaybeSendWrapperOption<T> {
inner: Inner<T>,
}
// SAFETY: `MaybeSendWrapperOption` can *only* be given a T in four ways
// 1) via new(), which requires T: Send + Sync
// 2) via new_local(), which wraps T in a SendWrapper if given Some(T)
// 3) via deref_mut(), which creates a SendWrapper<Option<T>> as needed
// 4) via update(), which either dereferences an existing SendWrapper
// or creates a new SendWrapper as needed
unsafe impl<T> Send for MaybeSendWrapperOption<T> {}
unsafe impl<T> Sync for MaybeSendWrapperOption<T> {}
enum Inner<T> {
/// A threadsafe value.
Threadsafe(Option<T>),
/// A non-threadsafe value. If accessed/dropped from a different thread in the Some() variant, it will panic.
Local(Option<SendWrapper<Option<T>>>),
}
impl<T> MaybeSendWrapperOption<T>
where
T: Send + Sync,
{
/// Create a new threadsafe value.
pub fn new(value: Option<T>) -> Self {
Self {
inner: Inner::Threadsafe(value),
}
}
}
impl<T> MaybeSendWrapperOption<T> {
/// Create a new non-threadsafe value.
pub fn new_local(value: Option<T>) -> Self {
Self {
inner: if let Some(value) = value {
Inner::Local(Some(SendWrapper::new(Some(value))))
} else {
Inner::Local(None)
},
}
}
/// Update a value in place with a callback.
///
/// # Panics
/// If the value is [`Inner::Local`] and it is called from a different thread than the one the instance has been created with, it will panic.
pub fn update(&mut self, cb: impl FnOnce(&mut Option<T>)) {
match &mut self.inner {
Inner::Threadsafe(value) => cb(value),
Inner::Local(value) => match value {
Some(sw) => {
cb(sw.deref_mut());
if sw.is_none() {
*value = None;
}
}
None => {
let mut inner = None;
cb(&mut inner);
if let Some(inner) = inner {
*value = Some(SendWrapper::new(Some(inner)));
}
}
},
}
}
/// Consume the value.
///
/// # Panics
/// Panics if the [`Inner::Local`] variant and it is called from a different thread than the one the instance has been created with.
pub fn take(self) -> Option<T> {
match self.inner {
Inner::Threadsafe(value) => value,
Inner::Local(value) => value.and_then(|value| value.take()),
}
}
}
impl<T> Deref for MaybeSendWrapperOption<T> {
type Target = Option<T>;
fn deref(&self) -> &Self::Target {
match &self.inner {
Inner::Threadsafe(value) => value,
Inner::Local(value) => match value {
Some(value) => value.deref(),
None => &None,
},
}
}
}
impl<T> DerefMut for MaybeSendWrapperOption<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.inner {
Inner::Threadsafe(value) => value,
Inner::Local(value) => match value {
Some(value) => value.deref_mut(),
None => {
*value = Some(SendWrapper::new(None));
value.as_mut().unwrap().deref_mut()
}
},
}
}
}
impl<T: Debug> Debug for MaybeSendWrapperOption<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Inner::Threadsafe(value) => {
write!(f, "MaybeSendWrapperOption::Threadsafe({:?})", value)
}
Inner::Local(value) => {
write!(f, "MaybeSendWrapperOption::Local({:?})", value)
}
}
}
}
impl<T: Clone> Clone for MaybeSendWrapperOption<T> {
fn clone(&self) -> Self {
Self {
inner: match &self.inner {
Inner::Threadsafe(value) => Inner::Threadsafe(value.clone()),
Inner::Local(value) => Inner::Local(value.clone()),
},
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.2.0-alpha"
version = "0.2.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,4 +1,5 @@
use crate::{
len::Len,
path::{StorePath, StorePathSegment},
store_field::StoreField,
KeyMap, StoreFieldTrigger,
@@ -209,7 +210,7 @@ impl<Inner, Prev> StoreFieldIterator<Prev> for Inner
where
Inner: StoreField<Value = Prev> + Clone,
Prev::Output: Sized,
Prev: IndexMut<usize> + AsRef<[Prev::Output]>,
Prev: IndexMut<usize> + Len,
{
#[track_caller]
fn at_unkeyed(self, index: usize) -> AtIndex<Inner, Prev> {
@@ -224,7 +225,7 @@ where
trigger.children.track();
// get the current length of the field by accessing slice
let len = self.reader().map(|n| n.as_ref().len()).unwrap_or(0);
let len = self.reader().map(|n| n.len()).unwrap_or(0);
// return the iterator
StoreFieldIter {

217
reactive_stores/src/len.rs Normal file
View File

@@ -0,0 +1,217 @@
use std::{
borrow::Cow,
collections::{LinkedList, VecDeque},
};
/// A trait for getting the length of a collection.
pub trait Len {
/// Returns the length of the collection.
fn len(&self) -> usize;
/// Returns true if the collection is empty
#[inline(always)]
fn is_empty(&self) -> bool {
self.len() == 0
}
}
macro_rules! delegate_impl_len {
(<$($lt: lifetime,)*$($generics: ident,)*> $ty:ty) => {
impl<$($lt,)*$($generics,)*> Len for $ty {
#[inline(always)]
fn len(&self) -> usize {
<$ty>::len(self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
<$ty>::is_empty(self)
}
}
impl<$($lt,)*$($generics,)*> Len for &$ty {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<$($lt,)*$($generics,)*> Len for &mut $ty {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
};
($ty:ty) => {
delegate_impl_len!(<> $ty);
};
}
delegate_impl_len!(<T,> [T]);
delegate_impl_len!(<T,> Vec<T>);
delegate_impl_len!(str);
delegate_impl_len!(String);
impl<'a> Len for Cow<'a, str> {
#[inline(always)]
fn len(&self) -> usize {
<str>::len(self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
<str>::is_empty(self)
}
}
impl<'a> Len for &Cow<'a, str> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<'a> Len for &mut Cow<'a, str> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<'a, T> Len for Cow<'a, [T]>
where
[T]: ToOwned,
{
#[inline(always)]
fn len(&self) -> usize {
<[T]>::len(self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
<[T]>::is_empty(self)
}
}
impl<'a, T> Len for &Cow<'a, [T]>
where
[T]: ToOwned,
{
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<'a, T> Len for &mut Cow<'a, [T]>
where
[T]: ToOwned,
{
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<T> Len for VecDeque<T> {
#[inline(always)]
fn len(&self) -> usize {
<VecDeque<T>>::len(self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
<VecDeque<T>>::is_empty(self)
}
}
impl<T> Len for &VecDeque<T> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<T> Len for &mut VecDeque<T> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(&**self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<T> Len for LinkedList<T> {
#[inline(always)]
fn len(&self) -> usize {
<LinkedList<T>>::len(self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
<LinkedList<T>>::is_empty(self)
}
}
impl<T> Len for &LinkedList<T> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(*self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}
impl<T> Len for &mut LinkedList<T> {
#[inline(always)]
fn len(&self) -> usize {
Len::len(&**self)
}
#[inline(always)]
fn is_empty(&self) -> bool {
Len::is_empty(*self)
}
}

View File

@@ -269,6 +269,7 @@ mod deref;
mod field;
mod iter;
mod keyed;
mod len;
mod option;
mod patch;
mod path;
@@ -280,6 +281,7 @@ pub use deref::*;
pub use field::Field;
pub use iter::*;
pub use keyed::*;
pub use len::Len;
pub use option::*;
pub use patch::*;
pub use path::{StorePath, StorePathSegment};

View File

@@ -100,6 +100,9 @@ where
let mut full_path = self.path().into_iter().collect::<StorePath>();
full_path.pop();
// build a list of triggers, starting with the full path to this node and ending with the root
// this will mean that the root is the final item, and this path is first
let mut triggers = Vec::with_capacity(full_path.len());
triggers.push(trigger.this.clone());
loop {
@@ -110,6 +113,17 @@ where
}
full_path.pop();
}
// when the WriteGuard is dropped, each trigger will be notified, in order
// reversing the list will cause the triggers to be notified starting from the root,
// then to each child down to this one
//
// notifying from the root down is important for things like OptionStoreExt::map()/unwrap(),
// where it's really important that any effects that subscribe to .is_some() run before effects
// that subscribe to the inner value, so that the inner effect can be canceled if the outer switches to `None`
// (see https://github.com/leptos-rs/leptos/issues/3704)
triggers.reverse();
let guard = WriteGuard::new(triggers, parent);
Some(MappedMut::new(guard, self.read, self.write))

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.2.0-alpha"
version = "0.2.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -604,9 +604,9 @@ impl ToTokens for PatchModel {
let Field {
attrs, ident, ..
} = &field;
let field_name = match &ident {
Some(ident) => quote! { #ident },
None => quote! { #idx },
let locator = match &ident {
Some(ident) => Either::Left(ident),
None => Either::Right(Index::from(idx)),
};
let closure = attrs
.iter()
@@ -639,9 +639,9 @@ impl ToTokens for PatchModel {
let params = closure.inputs;
let body = closure.body;
quote! {
if new.#field_name != self.#field_name {
if new.#locator != self.#locator {
_ = {
let (#params) = (&mut self.#field_name, new.#field_name);
let (#params) = (&mut self.#locator, new.#locator);
#body
};
notify(&new_path);
@@ -651,8 +651,8 @@ impl ToTokens for PatchModel {
} else {
quote! {
#library_path::PatchField::patch_field(
&mut self.#field_name,
new.#field_name,
&mut self.#locator,
new.#locator,
&new_path,
notify
);
@@ -684,3 +684,17 @@ impl ToTokens for PatchModel {
});
}
}
enum Either<A, B> {
Left(A),
Right(B),
}
impl<A: ToTokens, B: ToTokens> ToTokens for Either<A, B> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Either::Left(a) => a.to_tokens(tokens),
Either::Right(b) => b.to_tokens(tokens),
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.8.0-alpha"
version = "0.8.0-beta"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -655,8 +655,10 @@ where
ScopedFuture::new(view.choose()),
);
let view = view.await;
let view =
MatchedRoute(matched.0.get(), view);
let view = MatchedRoute(
matched.0.get_untracked(),
view,
);
OwnedView::new(view).into_any()
})
as Pin<

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.8.0-alpha"
version = "0.8.0-beta"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.2.0-alpha"
version = "0.2.0-beta"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -8,7 +8,6 @@ pub mod custom;
pub mod global;
mod key;
pub(crate) mod maybe_next_attr_erasure_macros;
pub(crate) mod panic_on_clone_attribute;
mod value;
use crate::view::{Position, ToTemplate};

View File

@@ -1,88 +0,0 @@
use super::{Attribute, NextAttribute};
/// When type erasing with `AnyAttribute`, the underling attribute must be cloneable.
///
/// For most this is possible, but for some like `NodeRef` it is not.
///
/// This allows for a panic to be thrown if a non-cloneable attribute is cloned, whilst still seeming like it can be cloned.
pub struct PanicOnCloneAttr<T: Attribute + 'static> {
msg: &'static str,
attr: T,
}
impl<T: Attribute + 'static> PanicOnCloneAttr<T> {
pub(crate) fn new(attr: T, msg: &'static str) -> Self {
Self { msg, attr }
}
}
impl<T: Attribute + 'static> Clone for PanicOnCloneAttr<T> {
fn clone(&self) -> Self {
panic!("{}", self.msg)
}
}
impl<T: Attribute + 'static> NextAttribute for PanicOnCloneAttr<T> {
type Output<NewAttr: Attribute> = <T as NextAttribute>::Output<NewAttr>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
self.attr.add_any_attr(new_attr)
}
}
impl<T: Attribute + 'static> Attribute for PanicOnCloneAttr<T> {
const MIN_LENGTH: usize = T::MIN_LENGTH;
type State = T::State;
type AsyncOutput = T::AsyncOutput;
type Cloneable = Self;
type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.attr.html_len()
}
fn to_html(
self,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
) {
self.attr.to_html(buf, class, style, inner_html)
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
self.attr.hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.attr.build(el)
}
fn rebuild(self, state: &mut Self::State) {
self.attr.rebuild(state)
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
fn dry_resolve(&mut self) {
self.attr.dry_resolve()
}
async fn resolve(self) -> Self::AsyncOutput {
self.attr.resolve().await
}
}

View File

@@ -1,7 +1,7 @@
use super::{
attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type,
panic_on_clone_attribute::PanicOnCloneAttr, Attribute, NextAttribute,
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NextAttribute,
},
element::ElementType,
};
@@ -65,8 +65,8 @@ where
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
type State = crate::renderer::types::Element;
type Cloneable = PanicOnCloneAttr<Self>;
type CloneableOwned = PanicOnCloneAttr<Self>;
type Cloneable = Self;
type CloneableOwned = Self;
#[inline(always)]
fn html_len(&self) -> usize {
@@ -100,17 +100,11 @@ where
}
fn into_cloneable(self) -> Self::Cloneable {
PanicOnCloneAttr::new(
self,
"node_ref should not be spread across multiple elements.",
)
self
}
fn into_cloneable_owned(self) -> Self::Cloneable {
PanicOnCloneAttr::new(
self,
"node_ref should not be spread across multiple elements.",
)
self
}
fn dry_resolve(&mut self) {}