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}