Skip to main content

nanvm_lib/vm/any/
dot.rs

1use crate::vm::{
2    Any, IVm, ToAny,
3    lambda::{Member, OptionPropertyLambda, PropertyLambda, Region},
4};
5
6impl<A: IVm> Any<A> {
7    /// The internal member-presence query. Both operands are established
8    /// before lookup; this performs the optional member-call guard without
9    /// evaluating call arguments or checking that the answer is callable.
10    pub fn member_present(self, key: Self, runtime: &Self) -> Result<Self, Self> {
11        Member::new(self, key)?
12            .is_nullish(runtime)
13            .map(|absent| (!absent).to_any())
14    }
15
16    /// The EDAG's `.` node, `['.', receiver, key]` or with a continuation,
17    /// `['.', receiver, key, k]`: the property step, as a chain a
18    /// continuation may follow (`fjs/edag/README.md`, Chains).
19    /// `dot(a, key).end()` is `a.b`, the one spelling of the node whether
20    /// or not a continuation follows, and the one property read `nanvm-lib`
21    /// has; `dot(a, key).end_call(&runtime, args)` is the method call
22    /// `a.b(...args)`, the property called on its receiver, the runtime
23    /// value asked for the body of a name the receiver does not own. The
24    /// key is a value: `a[k]` evaluates both operands whatever they are.
25    ///
26    /// A nullish receiver throws here, before any continuation's arguments
27    /// — the throw waits in the lambda until `end` or `end_call` surfaces
28    /// it, so `a.b(...c)` on a nullish `a` throws with `c` untouched. What
29    /// a live step reads or calls is [`Member`]'s.
30    pub fn dot(self, key: Self) -> PropertyLambda<A> {
31        PropertyLambda(Member::new(self, key))
32    }
33
34    /// The EDAG's `?.` node, `['?.', receiver, key, k]`: `a?.b`, opening a
35    /// short-circuit region. A nullish receiver skips the key and the rest
36    /// of the chain — `u?.[todo()]` never calls `todo` — which is why the
37    /// key is a thunk. Any other receiver takes the step `dot` takes.
38    pub fn option_dot(self, key: impl FnOnce() -> Result<Self, Self>) -> OptionPropertyLambda<A> {
39        OptionPropertyLambda(Region::Live(self).option_dot(key))
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use crate::{
46        naive::Naive,
47        vm::{Any, IVm, Nullish, ToAny, ToArray, ToObject, error},
48    };
49
50    type A = Naive;
51
52    #[test]
53    fn nullish_receiver_throws() {
54        assert_eq!(
55            Any::<A>::undefined().dot(0.0.to_any()).end(),
56            Err(error::nullish_to_object())
57        );
58        assert_eq!(
59            Nullish::Null.to_any::<A>().dot("a".into()).end(),
60            Err(error::nullish_to_object())
61        );
62    }
63
64    /// The dispatch wiring itself, as opposed to `Array::member_access`'s
65    /// own behavior, which is tested in `vm/member_access.rs` and
66    /// `vm/array/member_access.rs`.
67    #[test]
68    fn array_receiver_dispatches_to_array_member_access() {
69        let array: Any<A> = [10.0.to_any(), 20.0.to_any()].to_array().to_any();
70        assert_eq!(array.clone().dot(1.0.to_any()).end(), Ok(20.0.to_any()));
71        assert_eq!(array.dot(2.0.to_any()).end(), Ok(Any::undefined()));
72    }
73
74    /// The dispatch wiring itself, as opposed to `String::member_access`'s
75    /// own behavior, which is tested in `vm/member_access.rs` and
76    /// `vm/string/member_access.rs`.
77    #[test]
78    fn string_receiver_dispatches_to_string_member_access() {
79        let s: Any<A> = "ab".into();
80        assert_eq!(s.clone().dot(1.0.to_any()).end(), Ok("b".into()));
81        assert_eq!(s.dot(2.0.to_any()).end(), Ok(Any::undefined()));
82    }
83
84    /// The dispatch wiring itself, as opposed to `Object::member_access`'s
85    /// own behavior, which is tested in `vm/object/member_access.rs`.
86    #[test]
87    fn object_receiver_dispatches_to_object_member_access() {
88        let object: Any<A> = [("a".into(), 1.0.to_any())].to_object().to_any();
89        assert_eq!(object.clone().dot("a".into()).end(), Ok(1.0.to_any()));
90        assert_eq!(object.dot("b".into()).end(), Ok(Any::undefined()));
91    }
92
93    /// The dispatch wiring itself, as opposed to `Function::member_access`'s
94    /// own behavior, which is tested in `vm/function/member_access.rs`.
95    #[test]
96    fn function_receiver_dispatches_to_function_member_access() {
97        let f: Any<A> =
98            A::static_function(|_, _| Ok(Any::undefined()), 0, [].to_array(), None).to_any();
99        assert_eq!(f.clone().dot("length".into()).end(), Ok(0.0.to_any()));
100        assert_eq!(f.dot("a".into()).end(), Ok(Any::undefined()));
101    }
102
103    /// `Number`, `Boolean` and `BigInt` receivers have no own properties at
104    /// all, so every key on one reads `undefined` — the same fallback
105    /// `Any::entry` has.
106    #[test]
107    fn primitive_receiver_has_no_properties() {
108        assert_eq!(
109            1.0.to_any::<A>().dot(0.0.to_any()).end(),
110            Ok(Any::undefined())
111        );
112        assert_eq!(
113            true.to_any::<A>().dot("length".into()).end(),
114            Ok(Any::undefined())
115        );
116    }
117
118    /// The node's own key is inside the region it opens: skipped on a
119    /// nullish receiver, evaluated on any other.
120    #[test]
121    fn option_dot_guards_the_key() {
122        let object: Any<A> = [("a".into(), 1.0.to_any())].to_object().to_any();
123        assert_eq!(
124            Any::<A>::undefined()
125                .option_dot(|| Err("boom".into()))
126                .end(),
127            Ok(Any::undefined())
128        );
129        assert_eq!(
130            object.clone().option_dot(|| Ok("a".into())).end(),
131            Ok(1.0.to_any())
132        );
133        assert_eq!(
134            object.option_dot(|| Err("boom".into())).end(),
135            Err("boom".into())
136        );
137    }
138}