Skip to content

Commit

Permalink
Updated the check in attributes generation
Browse files Browse the repository at this point in the history
* Use the new is_dyn check for attribute values
* Allow literal method calls to be non-dynamic
* Update attributes-passthrough example
  • Loading branch information
Kromgart committed Oct 3, 2024
1 parent 4a5bfed commit 5a869d0
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 47 deletions.
2 changes: 1 addition & 1 deletion examples/attributes-passthrough/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn CustomButton(props: CustomButtonProps) -> View {
let children = props.children.call();
view! {
// TODO: Remove the .clone() here.
button(id=props.id.clone(), ..props.attributes) {
button(id=props.id, ..props.attributes) {
(children)
}
}
Expand Down
115 changes: 69 additions & 46 deletions packages/sycamore-macro/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl Codegen {

pub fn attribute(&self, attr: &Prop) -> TokenStream {
let value = &attr.value;
let is_dynamic = !matches!(value, Expr::Lit(_) | Expr::Closure(_) | Expr::Path(_));
let is_dynamic = is_dyn(value);
let dyn_value = if is_dynamic {
quote! { move || #value }
} else {
Expand Down Expand Up @@ -198,12 +198,62 @@ fn is_component(ident: &TagIdent) -> bool {
}

fn is_dyn(ex: &Expr) -> bool {
fn is_dyn_macro(m: &syn::Macro) -> bool {
// Bodies of nested inner view! macros will be checked for dynamic
// parts when their own codegen is run.
!m.path
.get_ident()
.is_some_and(|ident| "view" == &ident.to_string())
}

fn is_dyn_block(block: &syn::Block) -> bool {
block.stmts.iter().any(|s: &syn::Stmt| match s {
syn::Stmt::Expr(ex, _) => is_dyn(ex),
syn::Stmt::Macro(m) => is_dyn_macro(&m.mac),

Check warning on line 212 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L212

Added line #L212 was not covered by tests
syn::Stmt::Local(loc) => {
is_dyn_pattern(&loc.pat)
|| loc.init.as_ref().is_some_and(|i| {
is_dyn(&i.expr) || i.diverge.as_ref().is_some_and(|(_, ex)| is_dyn(ex))
})
}
syn::Stmt::Item(_) => false,

Check warning on line 219 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L219

Added line #L219 was not covered by tests
})
}

// This allows to recognise as 'non-dynamic' those method calls which only
// use literals (or things composed from literals).
fn is_literal(ex: &Expr) -> bool {
match ex {
Expr::Lit(_) => true,
Expr::Tuple(t) => t.elems.iter().all(is_literal),
Expr::Array(a) => a.elems.iter().all(is_literal),
Expr::Struct(s) => s
.fields
.iter()
.all(|fv: &syn::FieldValue| is_literal(&fv.expr)),
Expr::Index(i) => is_literal(&i.expr) && is_literal(&i.index),

Check warning on line 234 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L227-L234

Added lines #L227 - L234 were not covered by tests
Expr::Unary(u) => is_literal(&u.expr),
Expr::Cast(c) => is_literal(&c.expr),

Check warning on line 236 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L236

Added line #L236 was not covered by tests
Expr::Paren(p) => is_literal(&p.expr),
Expr::Closure(c) => c.capture.is_none(),

Check warning on line 238 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L238

Added line #L238 was not covered by tests
Expr::MethodCall(mc) => is_literal(&mc.receiver) && mc.args.iter().all(is_literal),
_ => false,
}
}

match ex {
Expr::Lit(_) | Expr::Closure(_) | Expr::Path(_) | Expr::Field(_) => false,

Expr::Tuple(t) => t.elems.iter().any(is_dyn),
Expr::Array(a) => a.elems.iter().any(is_dyn),
Expr::Struct(s) => s.fields.iter().any(|fv: &syn::FieldValue| is_dyn(&fv.expr)),

Check warning on line 249 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L247-L249

Added lines #L247 - L249 were not covered by tests
Expr::Index(i) => is_dyn(&i.expr) || is_dyn(&i.index),
Expr::Unary(u) => is_dyn(&u.expr),
Expr::Cast(c) => is_dyn(&c.expr),
Expr::Paren(p) => is_dyn(&p.expr),

Check warning on line 253 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L252-L253

Added lines #L252 - L253 were not covered by tests
Expr::Macro(m) => is_dyn_macro(&m.mac),
Expr::Block(b) => is_dyn_block(&b.block),
Expr::Let(e) => is_dyn_pattern(&e.pat) || is_dyn(&e.expr),

Expr::Match(m) => {
is_dyn(&m.expr)
Expand All @@ -214,45 +264,21 @@ fn is_dyn(ex: &Expr) -> bool {
})

Check warning on line 264 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L258-L264

Added lines #L258 - L264 were not covered by tests
}

Expr::Index(i) => is_dyn(&i.expr) || is_dyn(&i.index),

Expr::Unary(u) => is_dyn(&u.expr),
Expr::Cast(c) => is_dyn(&c.expr),
Expr::Paren(p) => is_dyn(&p.expr),

Expr::Block(b) => b.block.stmts.iter().any(|s: &syn::Stmt| match s {
syn::Stmt::Expr(ex, _) => is_dyn(ex),
syn::Stmt::Macro(m) => is_dyn_macro(&m.mac),
syn::Stmt::Local(loc) => {
is_dyn_pattern(&loc.pat)
|| loc.init.as_ref().is_some_and(|i| {
is_dyn(&i.expr) || i.diverge.as_ref().is_some_and(|(_, ex)| is_dyn(ex))
})
}
syn::Stmt::Item(_) => false,
}),

Expr::Macro(m) => is_dyn_macro(&m.mac),
Expr::If(i) => {
is_dyn(&i.cond)
|| is_dyn_block(&i.then_branch)

Check warning on line 269 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L269

Added line #L269 was not covered by tests
|| i.else_branch.as_ref().is_some_and(|(_, e)| is_dyn(e))
}

// Would be nice to make these non-dynamic when they don't access signals.
Expr::Call(_) | Expr::MethodCall(_) => true,
// Would be nice to make more of these non-dynamic when they don't access signals.
Expr::Call(_) => true,
Expr::MethodCall(mc) => !(is_literal(&mc.receiver) && mc.args.iter().all(is_literal)),

// TODO
// TODO more
_ => true,
}
}

fn is_dyn_macro(m: &syn::Macro) -> bool {
// Don't descend into nested inner view! macros, because their bodies
// will be checked for dynamic parts when their own codegen is run.
//
// As for other macros: we have no idea what they could generate from
// their TokenStreams, so lets assume those all are dynamic.
!m.path
.get_ident()
.is_some_and(|ident| "view" == &ident.to_string())
}

fn is_dyn_pattern(pat: &Pat) -> bool {
match pat {
Pat::Wild(_) | Pat::Lit(_) | Pat::Path(_) | Pat::Rest(_) | Pat::Type(_) | Pat::Const(_) => {
Expand All @@ -263,14 +289,12 @@ fn is_dyn_pattern(pat: &Pat) -> bool {
Pat::Or(o) => o.cases.iter().any(is_dyn_pattern),

Check warning on line 289 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L288-L289

Added lines #L288 - L289 were not covered by tests
Pat::Tuple(t) => t.elems.iter().any(is_dyn_pattern),
Pat::TupleStruct(s) => s.elems.iter().any(is_dyn_pattern),

Pat::Struct(s) => s
.fields
.iter()
.any(|fp: &syn::FieldPat| is_dyn_pattern(&fp.pat)),
Pat::Slice(s) => s.elems.iter().any(is_dyn_pattern),
Pat::Range(r) => {
r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)

Check warning on line 294 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L292-L294

Added lines #L292 - L294 were not covered by tests
}

Pat::Reference(r) => r.mutability.is_some(),

Check warning on line 297 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L297

Added line #L297 was not covered by tests

Pat::Ident(id) => {
(id.by_ref.is_some() && id.mutability.is_some())
|| id
Expand All @@ -279,13 +303,12 @@ fn is_dyn_pattern(pat: &Pat) -> bool {
.is_some_and(|(_, pat)| is_dyn_pattern(pat))
}

Pat::Range(r) => {
r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)
}

Pat::Slice(s) => s.elems.iter().any(is_dyn_pattern),
Pat::Struct(s) => s
.fields
.iter()
.any(|fp: &syn::FieldPat| is_dyn_pattern(&fp.pat)),

Check warning on line 309 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L306-L309

Added lines #L306 - L309 were not covered by tests

// Pat is non-exhaustive
// syn::Pat is non-exhaustive
_ => true,

Check warning on line 312 in packages/sycamore-macro/src/view.rs

View check run for this annotation

Codecov / codecov/patch

packages/sycamore-macro/src/view.rs#L312

Added line #L312 was not covered by tests
}
}

0 comments on commit 5a869d0

Please sign in to comment.