Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/environ/src/component/names.rs
1692 views
1
use crate::prelude::*;
2
use anyhow::{Result, bail};
3
use core::hash::Hash;
4
use semver::Version;
5
use serde_derive::{Deserialize, Serialize};
6
7
/// A semver-aware map for imports/exports of a component.
8
///
9
/// This data structure is used when looking up the names of imports/exports of
10
/// a component to enable semver-compatible matching of lookups. This will
11
/// enable lookups of `a:b/[email protected]` to match entries defined as `a:b/[email protected]`
12
/// which is currently considered a key feature of WASI's compatibility story.
13
///
14
/// On the outside this looks like a map of `K` to `V`.
15
#[derive(Clone, Serialize, Deserialize, Debug)]
16
pub struct NameMap<K: Clone + Hash + Eq + Ord, V> {
17
/// A map of keys to the value that they define.
18
///
19
/// Note that this map is "exact" where the name here is the exact name that
20
/// was specified when the `insert` was called. This doesn't have any
21
/// semver-mangling or anything like that.
22
///
23
/// This map is always consulted first during lookups.
24
definitions: IndexMap<K, V>,
25
26
/// An auxiliary map tracking semver-compatible names. This is a map from
27
/// "semver compatible alternate name" to a name present in `definitions`
28
/// and the semver version it was registered at.
29
///
30
/// An example map would be:
31
///
32
/// ```text
33
/// {
34
/// "a:b/[email protected]": ("a:b/[email protected]", 0.2.1),
35
/// "a:b/c@2": ("a:b/[email protected]+abc", 2.0.0+abc),
36
/// }
37
/// ```
38
///
39
/// As names are inserted into `definitions` each name may have up to one
40
/// semver-compatible name with extra numbers/info chopped off which is
41
/// inserted into this map. This map is the lookup table from `@0.2` to
42
/// `@0.2.x` where `x` is what was inserted manually.
43
///
44
/// The `Version` here is tracked to ensure that when multiple versions on
45
/// one track are defined that only the maximal version here is retained.
46
alternate_lookups: IndexMap<K, (K, Version)>,
47
}
48
49
impl<K, V> NameMap<K, V>
50
where
51
K: Clone + Hash + Eq + Ord,
52
{
53
/// Inserts the `name` specified into this map.
54
///
55
/// The name is intern'd through the `cx` argument and shadowing is
56
/// controlled by the `allow_shadowing` variable.
57
///
58
/// This function will automatically insert an entry in
59
/// `self.alternate_lookups` if `name` is a semver-looking name.
60
///
61
/// Returns an error if `allow_shadowing` is `false` and the `name` is
62
/// already present in this map (by exact match). Otherwise returns the
63
/// intern'd version of `name`.
64
pub fn insert<I>(&mut self, name: &str, cx: &mut I, allow_shadowing: bool, item: V) -> Result<K>
65
where
66
I: NameMapIntern<Key = K>,
67
{
68
// Always insert `name` and `item` as an exact definition.
69
let key = cx.intern(name);
70
if let Some(prev) = self.definitions.insert(key.clone(), item) {
71
if !allow_shadowing {
72
self.definitions.insert(key, prev);
73
bail!("map entry `{name}` defined twice")
74
}
75
}
76
77
// If `name` is a semver-looking thing, like `a:b/[email protected]`, then also
78
// insert an entry in the semver-compatible map under a key such as
79
// `a:b/c@1`.
80
//
81
// This key is used during `get` later on.
82
if let Some((alternate_key, version)) = alternate_lookup_key(name) {
83
let alternate_key = cx.intern(alternate_key);
84
if let Some((prev_key, prev_version)) = self
85
.alternate_lookups
86
.insert(alternate_key.clone(), (key.clone(), version.clone()))
87
{
88
// Prefer the latest version, so only do this if we're
89
// greater than the prior version.
90
if version < prev_version {
91
self.alternate_lookups
92
.insert(alternate_key, (prev_key, prev_version));
93
}
94
}
95
}
96
Ok(key)
97
}
98
99
/// Looks up `name` within this map, using the interning specified by
100
/// `cx`.
101
///
102
/// This may return a definition even if `name` wasn't exactly defined in
103
/// this map, such as looking up `a:b/[email protected]` when the map only has
104
/// `a:b/[email protected]` defined.
105
pub fn get<I>(&self, name: &str, cx: &I) -> Option<&V>
106
where
107
I: NameMapIntern<Key = K>,
108
{
109
// First look up an exact match and if that's found return that. This
110
// enables defining multiple versions in the map and the requested
111
// version is returned if it matches exactly.
112
let candidate = cx.lookup(name).and_then(|k| self.definitions.get(&k));
113
if let Some(def) = candidate {
114
return Some(def);
115
}
116
117
// Failing that, then try to look for a semver-compatible alternative.
118
// This looks up the key based on `name`, if any, and then looks to see
119
// if that was intern'd in `strings`. Given all that look to see if it
120
// was defined in `alternate_lookups` and finally at the end that exact
121
// key is then used to look up again in `self.definitions`.
122
let (alternate_name, _version) = alternate_lookup_key(name)?;
123
let alternate_key = cx.lookup(alternate_name)?;
124
let (exact_key, _version) = self.alternate_lookups.get(&alternate_key)?;
125
self.definitions.get(exact_key)
126
}
127
128
/// Returns an iterator over inserted values in this map.
129
///
130
/// Note that the iterator return yields intern'd keys and additionally does
131
/// not do anything special with semver names and such, it only literally
132
/// yields what's been inserted with [`NameMap::insert`].
133
pub fn raw_iter(&self) -> impl Iterator<Item = (&K, &V)> {
134
self.definitions.iter()
135
}
136
137
/// TODO
138
pub fn raw_get_mut(&mut self, key: &K) -> Option<&mut V> {
139
self.definitions.get_mut(key)
140
}
141
}
142
143
impl<K, V> Default for NameMap<K, V>
144
where
145
K: Clone + Hash + Eq + Ord,
146
{
147
fn default() -> NameMap<K, V> {
148
NameMap {
149
definitions: Default::default(),
150
alternate_lookups: Default::default(),
151
}
152
}
153
}
154
155
/// A helper trait used in conjunction with [`NameMap`] to optionally intern
156
/// keys to non-strings.
157
pub trait NameMapIntern {
158
/// The key that this interning context generates.
159
type Key;
160
161
/// Inserts `s` into `self` and returns the intern'd key `Self::Key`.
162
fn intern(&mut self, s: &str) -> Self::Key;
163
164
/// Looks up `s` in `self` returning `Some` if it was found or `None` if
165
/// it's not present.
166
fn lookup(&self, s: &str) -> Option<Self::Key>;
167
}
168
169
/// For use with [`NameMap`] when no interning should happen and instead string
170
/// keys are copied as-is.
171
pub struct NameMapNoIntern;
172
173
impl NameMapIntern for NameMapNoIntern {
174
type Key = String;
175
176
fn intern(&mut self, s: &str) -> String {
177
s.to_string()
178
}
179
180
fn lookup(&self, s: &str) -> Option<String> {
181
Some(s.to_string())
182
}
183
}
184
185
/// Determines a version-based "alternate lookup key" for the `name` specified.
186
///
187
/// Some examples are:
188
///
189
/// * `foo` => `None`
190
/// * `foo:bar/baz` => `None`
191
/// * `foo:bar/[email protected]` => `Some(foo:bar/baz@1)`
192
/// * `foo:bar/[email protected]` => `Some(foo:bar/[email protected])`
193
/// * `foo:bar/[email protected]` => `None`
194
/// * `foo:bar/[email protected]` => `None`
195
///
196
/// This alternate lookup key is intended to serve the purpose where a
197
/// semver-compatible definition can be located, if one is defined, at perhaps
198
/// either a newer or an older version.
199
fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
200
let at = name.find('@')?;
201
let version_string = &name[at + 1..];
202
let version = Version::parse(version_string).ok()?;
203
if !version.pre.is_empty() {
204
// If there's a prerelease then don't consider that compatible with any
205
// other version number.
206
None
207
} else if version.major != 0 {
208
// If the major number is nonzero then compatibility is up to the major
209
// version number, so return up to the first decimal.
210
let first_dot = version_string.find('.')? + at + 1;
211
Some((&name[..first_dot], version))
212
} else if version.minor != 0 {
213
// Like the major version if the minor is nonzero then patch releases
214
// are all considered to be on a "compatible track".
215
let first_dot = version_string.find('.')? + at + 1;
216
let second_dot = name[first_dot + 1..].find('.')? + first_dot + 1;
217
Some((&name[..second_dot], version))
218
} else {
219
// If the patch number is the first nonzero entry then nothing can be
220
// compatible with this patch, e.g. 0.0.1 isn't' compatible with
221
// any other version inherently.
222
None
223
}
224
}
225
226
#[cfg(test)]
227
mod tests {
228
use super::{NameMap, NameMapNoIntern};
229
230
#[test]
231
fn alternate_lookup_key() {
232
fn alt(s: &str) -> Option<&str> {
233
super::alternate_lookup_key(s).map(|(s, _)| s)
234
}
235
236
assert_eq!(alt("x"), None);
237
assert_eq!(alt("x:y/z"), None);
238
assert_eq!(alt("x:y/[email protected]"), Some("x:y/z@1"));
239
assert_eq!(alt("x:y/[email protected]"), Some("x:y/z@1"));
240
assert_eq!(alt("x:y/[email protected]"), Some("x:y/z@1"));
241
assert_eq!(alt("x:y/[email protected]"), Some("x:y/z@2"));
242
assert_eq!(alt("x:y/[email protected]+abc"), Some("x:y/z@2"));
243
assert_eq!(alt("x:y/[email protected]"), Some("x:y/[email protected]"));
244
assert_eq!(alt("x:y/[email protected]"), Some("x:y/[email protected]"));
245
assert_eq!(alt("x:y/[email protected]"), Some("x:y/[email protected]"));
246
assert_eq!(alt("x:y/[email protected]+abc"), Some("x:y/[email protected]"));
247
assert_eq!(alt("x:y/[email protected]"), None);
248
assert_eq!(alt("x:y/[email protected]"), None);
249
assert_eq!(alt("x:y/[email protected]"), None);
250
assert_eq!(alt("x:y/[email protected]"), None);
251
}
252
253
#[test]
254
fn name_map_smoke() {
255
let mut map = NameMap::default();
256
let mut intern = NameMapNoIntern;
257
258
map.insert("a", &mut intern, false, 0).unwrap();
259
map.insert("b", &mut intern, false, 1).unwrap();
260
261
assert!(map.insert("a", &mut intern, false, 0).is_err());
262
assert!(map.insert("a", &mut intern, true, 0).is_ok());
263
264
assert_eq!(map.get("a", &intern), Some(&0));
265
assert_eq!(map.get("b", &intern), Some(&1));
266
assert_eq!(map.get("c", &intern), None);
267
268
map.insert("a:b/[email protected]", &mut intern, false, 2).unwrap();
269
map.insert("a:b/[email protected]", &mut intern, false, 3).unwrap();
270
assert_eq!(map.get("a:b/[email protected]", &intern), Some(&2));
271
assert_eq!(map.get("a:b/[email protected]", &intern), Some(&3));
272
assert_eq!(map.get("a:b/[email protected]", &intern), Some(&3));
273
assert_eq!(map.get("a:b/[email protected]", &intern), Some(&3));
274
}
275
}
276
277