pint_abi/key.rs
1//! Items related to key construction.
2
3use crate::types::essential::{Key, Word};
4
5/// Represents a dynamically provided key element (e.g. map key or array index).
6///
7/// ABI-generated `Mutations` builders maintain a stack of key `Elem`s as users
8/// enter nested map/array entries.
9#[derive(Debug)]
10pub enum Elem {
11 /// A key element is provided by a map key.
12 MapKey(Key),
13 /// A key element is provided by an array index.
14 ArrayIx(usize),
15}
16
17/// Represents statically provided information about a storage var's type nesting to assist with
18/// key construction.
19///
20/// This is similar to the `pint_abi_visit::Nesting` type, but only contains nesting information
21/// relevant to key construction.
22#[derive(Debug)]
23pub enum Nesting {
24 Var { ix: usize },
25 MapEntry,
26 TupleField { flat_ix: usize },
27 ArrayElem { elem_len: usize },
28}
29
30/// Construct a full key for a given nested type and the associated stack of
31/// dynamically provided key elements.
32pub fn construct(nesting: &[Nesting], key_elems: &[Elem]) -> Key {
33 let mut nesting_iter = nesting.iter().peekable();
34 let mut key_elems_iter = key_elems.iter();
35 let mut words = vec![];
36 while let Some(nesting) = nesting_iter.next() {
37 match nesting {
38 // Push the storage var index directly. This is always the first nesting.
39 Nesting::Var { ix } => {
40 let word = Word::try_from(*ix).expect("out of `Word` range");
41 words.push(word);
42 }
43 // If this is a map entry, take the map key from the key elements.
44 Nesting::MapEntry => {
45 let key = expect_map_key(key_elems_iter.next());
46 words.extend(key);
47 }
48 // If we've encountered a tuple field, flatten any further tuple
49 // nestings and use the deepest `flat_ix` which represents the
50 // flattened index of the tuple field.
51 Nesting::TupleField { flat_ix } => {
52 let word = flattened_word(0, *flat_ix, &mut nesting_iter, &mut key_elems_iter);
53 words.push(word);
54 }
55 // If this is an array element, determine the key from the array
56 // index. For directly nested arrays, detemine the flattened index
57 // by multiplying the index by the inner array lenghts.
58 Nesting::ArrayElem { elem_len } => {
59 let arr_ix = elem_len * expect_array_ix(key_elems_iter.next());
60 let word = flattened_word(arr_ix, 0, &mut nesting_iter, &mut key_elems_iter);
61 words.push(word);
62 }
63 }
64 }
65 words
66}
67
68/// Flattens directly nested tuple and array nestings into a single word for a key.
69///
70/// Flattening begins once an array or tuple nesting is encountered.
71///
72/// If the first nesting is an array, provide the `array_ix` and a zeroed
73/// `tuple_flat_ix`.
74///
75/// If the first nesting is a tuple, provide the `tuple_flat_ix` and a zeroed
76/// `array_ix`.
77///
78/// The `nestings` iterator's `next` method will only be called for each
79/// directly nested array and tuple.
80///
81/// The `key_elems` iterator's `next` method will only be called for each
82/// directly nested array.
83fn flattened_word<'a>(
84 mut array_ix: usize,
85 mut tuple_flat_ix: usize,
86 nestings: &mut std::iter::Peekable<impl Iterator<Item = &'a Nesting>>,
87 key_elems: &mut impl Iterator<Item = &'a Elem>,
88) -> Word {
89 let mut sum_ix = 0;
90 while let Some(nesting) = nestings.peek() {
91 match nesting {
92 Nesting::ArrayElem { elem_len } => {
93 // If the previous nesting was a tuple field, add its flattened ix to the sum.
94 sum_ix += tuple_flat_ix;
95 tuple_flat_ix = 0;
96 // Sum the new array index.
97 array_ix += elem_len * expect_array_ix(key_elems.next());
98 }
99 Nesting::TupleField { flat_ix } => {
100 // If the previous nesting was an array nesting, add it to the sum.
101 sum_ix += array_ix;
102 array_ix = 0;
103 // Sum the nested tuple's flat index onto the current.
104 tuple_flat_ix += *flat_ix;
105 }
106 // If we encounter a non-array, non-tuple nesting, flattening is complete.
107 _ => break,
108 }
109 nestings.next();
110 }
111 sum_ix += array_ix + tuple_flat_ix;
112 Word::try_from(sum_ix).expect("out of `Word` range")
113}
114
115fn expect_array_ix(elem: Option<&Elem>) -> usize {
116 match elem {
117 Some(Elem::ArrayIx(ix)) => *ix,
118 elem => unreachable!(
119 "invalid key `Elem` provided by ABI-generated code: \
120 expected array index, found {elem:?}",
121 ),
122 }
123}
124
125fn expect_map_key(elem: Option<&Elem>) -> Key {
126 match elem {
127 Some(Elem::MapKey(key)) => key.clone(),
128 elem => unreachable!(
129 "invalid key `Elem` provided by ABI-generated code: \
130 expected map key, found {elem:?}",
131 ),
132 }
133}