1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! Context state management.

use crate::*;

/// Provides a context in the current [`Scope`]. The context can later be accessed by using
/// [`use_context`] lower in the scope hierarchy.
///
/// The context can also be accessed in the same scope in which it is provided.
///
/// This method is simply a wrapper around [`create_ref`] and [`provide_context_ref`].
///
/// # Panics
/// This method panics if a context with the same type exists already in this scope.
/// Note that if a context with the same type exists in a parent scope, the new context will
/// shadow the old context.
#[track_caller]
pub fn provide_context<T: 'static>(cx: Scope, value: T) -> &T {
    let value = create_ref(cx, value);
    provide_context_ref(cx, value)
}

/// Provides a context in the current [`Scope`]. The context can later be accessed by using
/// [`use_context`] lower in the scope hierarchy.
///
/// The context can also be accessed in the same scope in which it is provided.
///
/// Unlike [`provide_context`], this method accepts a reference that
/// lives at least as long as the scope.
///
/// # Panics
/// This method panics if a context with the same type exists already in this scope.
/// Note that if a context with the same type exists in a parent scope, the new context will
/// shadow the old context.
#[track_caller]
pub fn provide_context_ref<'a, T: 'static>(cx: Scope<'a>, value: &'a T) -> &'a T {
    let type_id = TypeId::of::<T>();
    if cx
        .raw
        .inner
        .borrow_mut()
        .contexts
        .get_or_insert_with(Default::default)
        .insert(type_id, value)
        .is_some()
    {
        panic!("existing context with type exists already");
    }
    value
}

/// Tries to get a context value of the given type. If no context with the right type found,
/// returns `None`. For a panicking version, see [`use_context`].
pub fn try_use_context<T: 'static>(cx: Scope) -> Option<&T> {
    let type_id = TypeId::of::<T>();
    let mut this = Some(cx.raw);
    while let Some(current) = this {
        if let Some(value) = current
            .inner
            .borrow()
            .contexts
            .as_ref()
            .and_then(|c| c.get(&type_id))
        {
            let value = value.downcast_ref::<T>().unwrap();
            return Some(value);
        } else {
            // SAFETY: `current.parent` necessarily lives longer than `current`.
            this = current.parent.map(|x| unsafe { &*x });
        }
    }
    None
}

/// Gets a context value of the given type.
///
/// # Panics
/// This method panics if the context cannot be found in the current scope hierarchy.
/// For a non-panicking version, see [`try_use_context`].
#[track_caller]
pub fn use_context<T: 'static>(cx: Scope) -> &T {
    try_use_context(cx).expect("context not found for type")
}

/// Gets a context value of the given type or computes it from a closure.
///
/// Note that if no context exists, the new context will be created in the _current_ scope. This
/// means that the new value will still be inaccessible in an outer scope.
pub fn use_context_or_else<T, F>(cx: Scope, f: F) -> &T
where
    T: 'static,
    F: FnOnce() -> T,
{
    try_use_context(cx).unwrap_or_else(|| provide_context(cx, f()))
}

/// Gets a context value of the given type or computes it from a closure.
///
/// Unlike [`provide_context`], this closure should return a reference that lives at least as long
/// as the scope.
///
/// Note that if no context exists, the new context will be created in the _current_ scope. This
/// means that the new value will still be inaccessible in an outer scope.
pub fn use_context_or_else_ref<'a, T, F>(cx: Scope<'a>, f: F) -> &'a T
where
    T: 'static,
    F: FnOnce() -> &'a T,
{
    try_use_context(cx).unwrap_or_else(|| provide_context_ref(cx, f()))
}

/// Returns the current depth of the scope. If the scope is the root scope, returns `0`.
pub fn scope_depth(cx: Scope) -> u32 {
    let mut depth = 0;
    let mut current = cx.raw;

    // SAFETY: 'current.parent' necessarily lives longer than 'current'.
    while let Some(next) = current.parent.map(|x| unsafe { &*x }) {
        current = next;
        depth += 1;
    }
    depth
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn context() {
        create_scope_immediate(|cx| {
            provide_context(cx, 42i32);
            let x = use_context::<i32>(cx);
            assert_eq!(*x, 42);
        });
    }

    #[test]
    fn context_in_nested_scope() {
        create_scope_immediate(|cx| {
            provide_context(cx, 42i32);
            let _ = create_child_scope(cx, |cx| {
                let x = use_context::<i32>(cx);
                assert_eq!(*x, 42);
            });
        });
    }

    #[test]
    // Do not run under miri as there is a memory leak false positive.
    #[cfg_attr(miri, ignore)]
    #[should_panic = "existing context with type exists already"]
    fn existing_context_with_same_type_should_panic() {
        create_scope_immediate(|cx| {
            provide_context(cx, 0i32);
            provide_context(cx, 0i32);
            //                  ^^^^ -> has type `i32` and therefore should panic
        });
    }

    #[test]
    fn test_use_context_or_else() {
        create_scope_immediate(|cx| {
            assert!(try_use_context::<i32>(cx).is_none());

            let a = use_context_or_else(cx, || 123);
            assert_eq!(*a, 123);

            assert!(try_use_context::<i32>(cx).is_some());
            let b: &i32 = use_context_or_else(cx, || panic!("don't call me"));
            assert_eq!(*b, 123);
        });
    }

    #[test]
    fn test_use_context_or_else_ref() {
        create_scope_immediate(|cx| {
            assert!(try_use_context::<Signal<i32>>(cx).is_none());

            let a = use_context_or_else_ref(cx, || create_signal(cx, 123));
            assert_eq!(*a.get(), 123);

            assert!(try_use_context::<Signal<i32>>(cx).is_some());
            let b: &Signal<i32> = use_context_or_else_ref(cx, || panic!("don't call me"));
            assert_eq!(*b.get(), 123);
        });
    }

    #[test]
    fn root_scope_is_zero_depth() {
        create_scope_immediate(|cx| {
            assert_eq!(scope_depth(cx), 0);
        });
    }

    #[test]
    fn depth_of_scope_inc_with_child_scopes() {
        create_scope_immediate(|cx| {
            let _ = create_child_scope(cx, |cx| {
                // first non root scope should be 1
                assert_eq!(scope_depth(cx), 1);

                let _ = create_child_scope(cx, |cx| {
                    // next scope should thus be 2
                    assert_eq!(scope_depth(cx), 2);
                });

                // We should still be one out here - not that the current implementation would
                // suggest otherwise.
                assert_eq!(scope_depth(cx), 1);
            });
        });
    }
}