Skip to content

Commit

Permalink
Merge branch 'master' into http-example
Browse files Browse the repository at this point in the history
  • Loading branch information
attriaayush authored Nov 30, 2021
2 parents f25bae1 + eda8536 commit 0a42691
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 31 deletions.
17 changes: 15 additions & 2 deletions packages/sycamore-macro/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,23 @@ pub fn component_impl(
arg,
mut generics,
vis,
attrs,
mut attrs,
name,
return_type,
} = component;

let mut doc_attrs = Vec::new();
let mut i = 0;
while i < attrs.len() {
if attrs[i].path.is_ident("doc") {
// Attribute is a doc attribute. Remove from attrs and add to doc_attrs.
let at = attrs.remove(i);
doc_attrs.push(at);
} else {
i += 1;
}
}

let prop_ty = match &arg {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(pat_ty) => &pat_ty.ty,
Expand Down Expand Up @@ -226,7 +238,7 @@ pub fn component_impl(
}

let quoted = quote! {
#(#attrs)*
#(#doc_attrs)*
#vis struct #component_name#generics {
#[doc(hidden)]
_marker: ::std::marker::PhantomData<(#phantom_generics)>,
Expand All @@ -239,6 +251,7 @@ pub fn component_impl(
const NAME: &'static ::std::primitive::str = #component_name_str;
type Props = #prop_ty;

#(#attrs)*
fn create_component(#arg) -> #return_type{
#block
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sycamore-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod view;
/// A macro for ergonomically creating complex UI structures.
///
/// To learn more about the template syntax, see the chapter on
/// [the `view!` macro](https://sycamore-rs.netlify.app/docs/basics/template) in the Sycamore Book.
/// [the `view!` macro](https://sycamore-rs.netlify.app/docs/basics/view) in the Sycamore Book.
#[proc_macro]
pub fn view(component: TokenStream) -> TokenStream {
let component = parse_macro_input!(component as view::HtmlRoot);
Expand Down
27 changes: 24 additions & 3 deletions packages/sycamore-reactive/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ pub(super) trait ContextAny {
/// Get the value stored in the context. The concrete type of the returned value is guaranteed
/// to match the type when calling [`get_type_id`](ContextAny::get_type_id).
fn get_value(&self) -> &dyn Any;

/// Get the name of type of context or `None` if not available.
fn get_type_name(&self) -> Option<&'static str>;
}

/// Inner representation of a context.
struct Context<T: 'static> {
value: T,
/// The type name of the context. Only available in debug mode.
#[cfg(debug_assertions)]
type_name: &'static str,
}

impl<T: 'static> ContextAny for Context<T> {
Expand All @@ -31,6 +37,13 @@ impl<T: 'static> ContextAny for Context<T> {
fn get_value(&self) -> &dyn Any {
&self.value
}

fn get_type_name(&self) -> Option<&'static str> {
#[cfg(debug_assertions)]
return Some(self.type_name);
#[cfg(not(debug_assertions))]
return None;
}
}

/// Get the value of a context in the current [`ReactiveScope`] or `None` if not found.
Expand Down Expand Up @@ -68,11 +81,19 @@ pub fn use_context<T: Clone + 'static>() -> T {
}

/// Creates a new [`ReactiveScope`] with a context and runs the supplied callback function.
#[cfg_attr(debug_assertions, track_caller)]
pub fn create_context_scope<T: 'static, Out>(value: T, f: impl FnOnce() -> Out) -> Out {
// Create a new ReactiveScope.
// We make sure to create the ReactiveScope outside of the closure so that track_caller can do
// its thing.
let scope = ReactiveScope::new();
SCOPES.with(|scopes| {
// Create a new ReactiveScope with a context.
let scope = ReactiveScope::new();
scope.0.borrow_mut().context = Some(Box::new(Context { value }));
// Attach the context to the scope.
scope.0.borrow_mut().context = Some(Box::new(Context {
value,
#[cfg(debug_assertions)]
type_name: std::any::type_name::<T>(),
}));
scopes.borrow_mut().push(scope);
let out = f();
let scope = scopes.borrow_mut().pop().unwrap_throw();
Expand Down
106 changes: 99 additions & 7 deletions packages/sycamore-reactive/src/effect.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::cell::RefCell;
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::hash::{Hash, Hasher};
use std::panic::Location;
use std::rc::{Rc, Weak};
use std::{mem, ptr};

Expand Down Expand Up @@ -57,7 +59,6 @@ impl Listener {
}

/// Internal representation for [`ReactiveScope`].
#[derive(Default)]
pub(crate) struct ReactiveScopeInner {
/// Effects created in this scope.
effects: SmallVec<[Rc<RefCell<Option<Listener>>>; REACTIVE_SCOPE_EFFECTS_STACK_CAPACITY]>,
Expand All @@ -66,6 +67,31 @@ pub(crate) struct ReactiveScopeInner {
/// Contexts created in this scope.
pub context: Option<Box<dyn ContextAny>>,
pub parent: ReactiveScopeWeak,
/// The source location where this scope was created.
/// Only available when in debug mode.
#[cfg(debug_assertions)]
pub loc: &'static Location<'static>,
}

impl ReactiveScopeInner {
#[cfg_attr(debug_assertions, track_caller)]
pub fn new() -> Self {
Self {
effects: SmallVec::new(),
cleanup: Vec::new(),
context: None,
parent: ReactiveScopeWeak::default(),
#[cfg(debug_assertions)]
loc: Location::caller(),
}
}
}

impl Default for ReactiveScopeInner {
#[cfg_attr(debug_assertions, track_caller)]
fn default() -> Self {
Self::new()
}
}

/// Owns the effects created in the current reactive scope.
Expand All @@ -75,15 +101,17 @@ pub(crate) struct ReactiveScopeInner {
/// A new [`ReactiveScope`] is usually created with [`create_root`]. A new [`ReactiveScope`] is also
/// created when a new effect is created with [`create_effect`] and other reactive utilities that
/// call it under the hood.
#[derive(Default)]
pub struct ReactiveScope(pub(crate) Rc<RefCell<ReactiveScopeInner>>);

impl ReactiveScope {
/// Create a new empty [`ReactiveScope`].
///
/// This should be rarely used and only serve as a placeholder.
#[cfg_attr(debug_assertions, track_caller)]
pub fn new() -> Self {
Self::default()
// We call this first to make sure that track_caller can do its thing.
let inner = ReactiveScopeInner::new();
Self(Rc::new(RefCell::new(inner)))
}

/// Add an effect that is owned by this [`ReactiveScope`].
Expand Down Expand Up @@ -126,6 +154,21 @@ impl ReactiveScope {
});
u
}

/// Returns the source code [`Location`] where this [`ReactiveScope`] was created.
pub fn creation_loc(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_assertions)]
return Some(self.0.borrow().loc);
#[cfg(not(debug_assertions))]
return None;
}
}

impl Default for ReactiveScope {
#[cfg_attr(debug_assertions, track_caller)]
fn default() -> Self {
Self::new()
}
}

impl Drop for ReactiveScope {
Expand Down Expand Up @@ -293,7 +336,7 @@ fn _create_effect(mut effect: Box<dyn FnMut()>) {

// Run effect closure.
drop(listener_mut); // Drop the RefMut because Signals will access it inside the effect callback.
let new_scope = create_root(|| {
let new_scope = create_scope(|| {
effect();
});
let mut listener_mut = listener.borrow_mut();
Expand Down Expand Up @@ -621,6 +664,55 @@ pub fn current_scope() -> Option<ReactiveScopeWeak> {
})
}

/// A struct that can be debug-printed to view the scope hierarchy at the location it was created.
pub struct DebugScopeHierarchy {
scope: Option<Rc<RefCell<ReactiveScopeInner>>>,
loc: &'static Location<'static>,
}

/// Returns a [`DebugScopeHierarchy`] which can be printed using [`std::fmt::Debug`] to debug the
/// scope hierarchy at the current level.
#[track_caller]
pub fn debug_scope_hierarchy() -> DebugScopeHierarchy {
let loc = Location::caller();
SCOPES.with(|scope| DebugScopeHierarchy {
scope: scope.borrow().last().map(|x| x.0.clone()),
loc,
})
}

impl Debug for DebugScopeHierarchy {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Reactive scope hierarchy at {}:", self.loc)?;
if let Some(scope) = &self.scope {
let mut s = Some(scope.clone());
while let Some(x) = s {
// Print scope.
if let Some(loc) = ReactiveScope(x.clone()).creation_loc() {
write!(f, "\tScope created at {}", loc)?;
} else {
write!(f, "\tScope")?;
}
// Print context.
if let Some(context) = &x.borrow().context {
let type_name = context.get_type_name();
if let Some(type_name) = type_name {
write!(f, " with context (type = {})", type_name)?;
} else {
write!(f, " with context")?;
}
}
writeln!(f)?;
// Set next iteration with scope parent.
s = x.borrow().parent.0.upgrade();
}
} else {
writeln!(f, "Not inside a reactive scope")?;
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -838,7 +930,7 @@ mod tests {

let trigger = Signal::new(());

let scope = create_root(cloned!((trigger, counter) => move || {
let scope = create_scope(cloned!((trigger, counter) => move || {
create_effect(move || {
trigger.get(); // subscribe to trigger
counter.set(*counter.get_untracked() + 1);
Expand Down Expand Up @@ -986,7 +1078,7 @@ mod tests {
#[test]
fn cleanup() {
let cleanup_called = Signal::new(false);
let scope = create_root(cloned!((cleanup_called) => move || {
let scope = create_scope(cloned!((cleanup_called) => move || {
on_cleanup(move || {
cleanup_called.set(true);
});
Expand Down Expand Up @@ -1045,7 +1137,7 @@ mod tests {
fn cleanup_in_extended_scope() {
let counter = Signal::new(0);

let root = create_root(cloned!((counter) => move || {
let root = create_scope(cloned!((counter) => move || {
on_cleanup(cloned!((counter) => move || {
counter.set(*counter.get_untracked() + 1);
}));
Expand Down
17 changes: 12 additions & 5 deletions packages/sycamore-reactive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ use wasm_bindgen::prelude::*;
/// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped
/// ```
#[must_use = "create_scope returns the reactive scope of the effects created inside this scope"]
#[cfg_attr(debug_assertions, track_caller)]
pub fn create_scope<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope {
_create_child_scope_in(None, Box::new(callback))
}

#[must_use = "create_child_scope_in returns the reactive scope of the effects created inside this scope"]
#[cfg_attr(debug_assertions, track_caller)]
pub fn create_child_scope_in<'a>(
parent: Option<&ReactiveScopeWeak>,
callback: impl FnOnce() + 'a,
Expand Down Expand Up @@ -79,21 +82,25 @@ pub fn create_child_scope_in<'a>(
/// trigger.set(());
/// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped
/// ```
/// TODO: deprecate this method in favor of [`create_scope`].
#[must_use = "create_root returns the reactive scope of the effects created inside this scope"]
#[deprecated(note = "use create_scope instead", since = "0.7.0")]
#[cfg_attr(debug_assertions, track_caller)]
pub fn create_root<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope {
_create_child_scope_in(None, Box::new(callback))
}

/// Internal implementation: use dynamic dispatch to reduce code bloat.
#[cfg_attr(debug_assertions, track_caller)]
fn _create_child_scope_in<'a>(
parent: Option<&ReactiveScopeWeak>,
callback: Box<dyn FnOnce() + 'a>,
) -> ReactiveScope {
SCOPES.with(|scopes| {
// Push new empty scope on the stack.
let scope = ReactiveScope::new();
// Push new empty scope on the stack.
// We make sure to create the ReactiveScope outside of the closure so that track_caller can do
// its thing.
let scope = ReactiveScope::new();

SCOPES.with(|scopes| {
// If `parent` was specified, use it as the parent of the new scope. Else use the parent of
// the scope this function is called in.
if let Some(parent) = parent {
Expand Down Expand Up @@ -150,7 +157,7 @@ mod tests {
fn drop_scope_inside_effect() {
let scope = Rc::new(RefCell::new(None));

*scope.borrow_mut() = Some(create_root({
*scope.borrow_mut() = Some(create_scope({
let scope = Rc::clone(&scope);
move || {
let scope = scope.take();
Expand Down
5 changes: 3 additions & 2 deletions packages/sycamore/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ where
/// # }
/// ```
#[component(ContextProvider<G>)]
#[cfg_attr(debug_assertions, track_caller)]
pub fn context_provider<T, F>(props: ContextProviderProps<T, F, G>) -> View<G>
where
T: 'static,
Expand All @@ -66,7 +67,7 @@ where
#[cfg(all(test, feature = "ssr"))]
mod tests {
use super::*;
use sycamore_reactive::use_context;
use sycamore_reactive::{create_scope, use_context};

#[test]
fn basic_context() {
Expand Down Expand Up @@ -122,7 +123,7 @@ mod tests {
#[test]
#[should_panic = "context not found for type"]
fn should_panic_with_unknown_context_type_inside_scope() {
let _ = create_root(move || {
let _ = create_scope(move || {
let _ = use_context::<u32>();
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/sycamore/src/generic_node/dom_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use wasm_bindgen::{intern, JsCast};
use web_sys::{Comment, Element, Node, Text};

use crate::generic_node::{GenericNode, Html};
use crate::reactive::{create_root, on_cleanup, ReactiveScope};
use crate::reactive::{create_scope, on_cleanup, ReactiveScope};
use crate::utils::render::insert;
use crate::view::View;

Expand Down Expand Up @@ -322,7 +322,7 @@ pub fn render_to(template: impl FnOnce() -> View<DomNode>, parent: &Node) {
/// _This API requires the following crate features to be activated: `dom`_
#[must_use = "please hold onto the ReactiveScope until you want to clean things up, or use render_to() instead"]
pub fn render_get_scope(template: impl FnOnce() -> View<DomNode>, parent: &Node) -> ReactiveScope {
create_root(|| {
create_scope(|| {
insert(
&DomNode::from_web_sys(parent.clone()),
template(),
Expand Down
Loading

0 comments on commit 0a42691

Please sign in to comment.