dusk_wasmtime/runtime/component/
linker.rs

1use crate::component::func::HostFunc;
2use crate::component::instance::RuntimeImport;
3use crate::component::matching::{InstanceType, TypeChecker};
4use crate::component::types;
5use crate::component::{
6    Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, ResourceType, Val,
7};
8use crate::{AsContextMut, Engine, Module, StoreContextMut};
9use anyhow::{bail, Context, Result};
10use semver::Version;
11use std::collections::hash_map::{Entry, HashMap};
12use std::future::Future;
13use std::marker;
14use std::pin::Pin;
15use std::sync::Arc;
16use wasmtime_environ::PrimaryMap;
17
18/// A type used to instantiate [`Component`]s.
19///
20/// This type is used to both link components together as well as supply host
21/// functionality to components. Values are defined in a [`Linker`] by their
22/// import name and then components are instantiated with a [`Linker`] using the
23/// names provided for name resolution of the component's imports.
24///
25/// # Names and Semver
26///
27/// Names defined in a [`Linker`] correspond to import names in the Component
28/// Model. Names in the Component Model are allowed to be semver-qualified, for
29/// example:
30///
31/// * `wasi:cli/stdout@0.2.0`
32/// * `wasi:http/types@0.2.0-rc-2023-10-25`
33/// * `my:custom/plugin@1.0.0-pre.2`
34///
35/// These version strings are taken into account when looking up names within a
36/// [`Linker`]. You're allowed to define any number of versions within a
37/// [`Linker`] still, for example you can define `a:b/c@0.2.0`, `a:b/c@0.2.1`,
38/// and `a:b/c@0.3.0` all at the same time.
39///
40/// Specifically though when names are looked up within a linker, for example
41/// during instantiation, semver-compatible names are automatically consulted.
42/// This means that if you define `a:b/c@0.2.1` in a [`Linker`] but a component
43/// imports `a:b/c@0.2.0` then that import will resolve to the `0.2.1` version.
44///
45/// This lookup behavior relies on hosts being well-behaved when using Semver,
46/// specifically that interfaces once defined are never changed. This reflects
47/// how Semver works at the Component Model layer, and it's assumed that if
48/// versions are present then hosts are respecting this.
49///
50/// Note that this behavior goes the other direction, too. If a component
51/// imports `a:b/c@0.2.1` and the host has provided `a:b/c@0.2.0` then that
52/// will also resolve correctly. This is because if an API was defined at 0.2.0
53/// and 0.2.1 then it must be the same API.
54///
55/// This behavior is intended to make it easier for hosts to upgrade WASI and
56/// for guests to upgrade WASI. So long as the actual "meat" of the
57/// functionality is defined then it should align correctly and components can
58/// be instantiated.
59pub struct Linker<T> {
60    engine: Engine,
61    strings: Strings,
62    map: NameMap,
63    path: Vec<usize>,
64    allow_shadowing: bool,
65    _marker: marker::PhantomData<fn() -> T>,
66}
67
68impl<T> Clone for Linker<T> {
69    fn clone(&self) -> Linker<T> {
70        Linker {
71            engine: self.engine.clone(),
72            strings: self.strings.clone(),
73            map: self.map.clone(),
74            path: self.path.clone(),
75            allow_shadowing: self.allow_shadowing,
76            _marker: self._marker,
77        }
78    }
79}
80
81#[derive(Clone, Default)]
82pub struct Strings {
83    string2idx: HashMap<Arc<str>, usize>,
84    strings: Vec<Arc<str>>,
85}
86
87/// Structure representing an "instance" being defined within a linker.
88///
89/// Instances do not need to be actual [`Instance`]s and instead are defined by
90/// a "bag of named items", so each [`LinkerInstance`] can further define items
91/// internally.
92pub struct LinkerInstance<'a, T> {
93    engine: &'a Engine,
94    path: &'a mut Vec<usize>,
95    path_len: usize,
96    strings: &'a mut Strings,
97    map: &'a mut NameMap,
98    allow_shadowing: bool,
99    _marker: marker::PhantomData<fn() -> T>,
100}
101
102#[derive(Clone, Default)]
103pub(crate) struct NameMap {
104    /// A map of interned strings to the name that they define.
105    ///
106    /// Note that this map is "exact" where the name here is the exact name that
107    /// was specified when the `Linker` was configured. This doesn't have any
108    /// semver-mangling or anything like that.
109    ///
110    /// This map is always consulted first during lookups.
111    definitions: HashMap<usize, Definition>,
112
113    /// An auxiliary map tracking semver-compatible names. This is a map from
114    /// "semver compatible alternate name" to a name present in `definitions`
115    /// and the semver version it was registered at.
116    ///
117    /// The `usize` entries here map to intern'd keys, so an example map could
118    /// be:
119    ///
120    /// ```text
121    /// {
122    ///     "a:b/c@0.2": ("a:b/c@0.2.1", 0.2.1),
123    ///     "a:b/c@2": ("a:b/c@2.0.0+abc", 2.0.0+abc),
124    /// }
125    /// ```
126    ///
127    /// As names are inserted into `definitions` each name may have up to one
128    /// semver-compatible name with extra numbers/info chopped off which is
129    /// inserted into this map. This map is the lookup table from `@0.2` to
130    /// `@0.2.x` where `x` is what was inserted manually.
131    ///
132    /// The `Version` here is tracked to ensure that when multiple versions on
133    /// one track are defined that only the maximal version here is retained.
134    alternate_lookups: HashMap<usize, (usize, Version)>,
135}
136
137#[derive(Clone)]
138pub(crate) enum Definition {
139    Instance(NameMap),
140    Func(Arc<HostFunc>),
141    Module(Module),
142    Resource(ResourceType, Arc<crate::func::HostFunc>),
143}
144
145impl<T> Linker<T> {
146    /// Creates a new linker for the [`Engine`] specified with no items defined
147    /// within it.
148    pub fn new(engine: &Engine) -> Linker<T> {
149        Linker {
150            engine: engine.clone(),
151            strings: Strings::default(),
152            map: NameMap::default(),
153            allow_shadowing: false,
154            path: Vec::new(),
155            _marker: marker::PhantomData,
156        }
157    }
158
159    /// Returns the [`Engine`] this is connected to.
160    pub fn engine(&self) -> &Engine {
161        &self.engine
162    }
163
164    /// Configures whether or not name-shadowing is allowed.
165    ///
166    /// By default name shadowing is not allowed and it's an error to redefine
167    /// the same name within a linker.
168    pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self {
169        self.allow_shadowing = allow;
170        self
171    }
172
173    /// Returns the "root instance" of this linker, used to define names into
174    /// the root namespace.
175    pub fn root(&mut self) -> LinkerInstance<'_, T> {
176        LinkerInstance {
177            engine: &self.engine,
178            path: &mut self.path,
179            path_len: 0,
180            strings: &mut self.strings,
181            map: &mut self.map,
182            allow_shadowing: self.allow_shadowing,
183            _marker: self._marker,
184        }
185    }
186
187    /// Returns a builder for the named instance specified.
188    ///
189    /// # Errors
190    ///
191    /// Returns an error if `name` is already defined within the linker.
192    pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
193        self.root().into_instance(name)
194    }
195
196    fn typecheck<'a>(&'a self, component: &'a Component) -> Result<TypeChecker<'a>> {
197        let mut cx = TypeChecker {
198            component: component.env_component(),
199            types: component.types(),
200            strings: &self.strings,
201            imported_resources: Default::default(),
202        };
203
204        // Walk over the component's list of import names and use that to lookup
205        // the definition within this linker that it corresponds to. When found
206        // perform a typecheck against the component's expected type.
207        let env_component = component.env_component();
208        for (_idx, (name, ty)) in env_component.import_types.iter() {
209            let import = self.map.get(name, &self.strings);
210            cx.definition(ty, import)
211                .with_context(|| format!("component imports {desc} `{name}`, but a matching implementation was not found in the linker", desc = ty.desc()))?;
212        }
213        Ok(cx)
214    }
215
216    /// Returns the [`types::Component`] corresponding to `component` with resource
217    /// types imported by it replaced using imports present in [`Self`].
218    pub fn substituted_component_type(&self, component: &Component) -> Result<types::Component> {
219        let cx = self.typecheck(&component)?;
220        Ok(types::Component::from(
221            component.ty(),
222            &InstanceType {
223                types: cx.types,
224                resources: &cx.imported_resources,
225            },
226        ))
227    }
228
229    /// Performs a "pre-instantiation" to resolve the imports of the
230    /// [`Component`] specified with the items defined within this linker.
231    ///
232    /// This method will perform as much work as possible short of actually
233    /// instantiating an instance. Internally this will use the names defined
234    /// within this linker to satisfy the imports of the [`Component`] provided.
235    /// Additionally this will perform type-checks against the component's
236    /// imports against all items defined within this linker.
237    ///
238    /// Note that unlike internally in components where subtyping at the
239    /// interface-types layer is supported this is not supported here. Items
240    /// defined in this linker must match the component's imports precisely.
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if this linker doesn't define a name that the
245    /// `component` imports or if a name defined doesn't match the type of the
246    /// item imported by the `component` provided.
247    pub fn instantiate_pre(&self, component: &Component) -> Result<InstancePre<T>> {
248        self.typecheck(&component)?;
249
250        // Now that all imports are known to be defined and satisfied by this
251        // linker a list of "flat" import items (aka no instances) is created
252        // using the import map within the component created at
253        // component-compile-time.
254        let env_component = component.env_component();
255        let mut imports = PrimaryMap::with_capacity(env_component.imports.len());
256        for (idx, (import, names)) in env_component.imports.iter() {
257            let (root, _) = &env_component.import_types[*import];
258
259            // This is the flattening process where we go from a definition
260            // optionally through a list of exported names to get to the final
261            // item.
262            let mut cur = self.map.get(root, &self.strings).unwrap();
263            for name in names {
264                cur = match cur {
265                    Definition::Instance(map) => map.get(&name, &self.strings).unwrap(),
266                    _ => unreachable!(),
267                };
268            }
269            let import = match cur {
270                Definition::Module(m) => RuntimeImport::Module(m.clone()),
271                Definition::Func(f) => RuntimeImport::Func(f.clone()),
272                Definition::Resource(t, dtor) => RuntimeImport::Resource {
273                    ty: t.clone(),
274                    _dtor: dtor.clone(),
275                    dtor_funcref: component.resource_drop_func_ref(dtor),
276                },
277
278                // This is guaranteed by the compilation process that "leaf"
279                // runtime imports are never instances.
280                Definition::Instance(_) => unreachable!(),
281            };
282            let i = imports.push(import);
283            assert_eq!(i, idx);
284        }
285        Ok(unsafe { InstancePre::new_unchecked(component.clone(), imports) })
286    }
287
288    /// Instantiates the [`Component`] provided into the `store` specified.
289    ///
290    /// This function will use the items defined within this [`Linker`] to
291    /// satisfy the imports of the [`Component`] provided as necessary. For more
292    /// information about this see [`Linker::instantiate_pre`] as well.
293    ///
294    /// # Errors
295    ///
296    /// Returns an error if this [`Linker`] doesn't define an import that
297    /// `component` requires or if it is of the wrong type. Additionally this
298    /// can return an error if something goes wrong during instantiation such as
299    /// a runtime trap or a runtime limit being exceeded.
300    pub fn instantiate(
301        &self,
302        store: impl AsContextMut<Data = T>,
303        component: &Component,
304    ) -> Result<Instance> {
305        assert!(
306            !store.as_context().async_support(),
307            "must use async instantiation when async support is enabled"
308        );
309        self.instantiate_pre(component)?.instantiate(store)
310    }
311
312    /// Instantiates the [`Component`] provided into the `store` specified.
313    ///
314    /// This is exactly like [`Linker::instantiate`] except for async stores.
315    ///
316    /// # Errors
317    ///
318    /// Returns an error if this [`Linker`] doesn't define an import that
319    /// `component` requires or if it is of the wrong type. Additionally this
320    /// can return an error if something goes wrong during instantiation such as
321    /// a runtime trap or a runtime limit being exceeded.
322    #[cfg(feature = "async")]
323    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
324    pub async fn instantiate_async(
325        &self,
326        store: impl AsContextMut<Data = T>,
327        component: &Component,
328    ) -> Result<Instance>
329    where
330        T: Send,
331    {
332        assert!(
333            store.as_context().async_support(),
334            "must use sync instantiation when async support is disabled"
335        );
336        self.instantiate_pre(component)?
337            .instantiate_async(store)
338            .await
339    }
340}
341
342impl<T> LinkerInstance<'_, T> {
343    fn as_mut(&mut self) -> LinkerInstance<'_, T> {
344        LinkerInstance {
345            engine: self.engine,
346            path: self.path,
347            path_len: self.path_len,
348            strings: self.strings,
349            map: self.map,
350            allow_shadowing: self.allow_shadowing,
351            _marker: self._marker,
352        }
353    }
354
355    /// Defines a new host-provided function into this [`Linker`].
356    ///
357    /// This method is used to give host functions to wasm components. The
358    /// `func` provided will be callable from linked components with the type
359    /// signature dictated by `Params` and `Return`. The `Params` is a tuple of
360    /// types that will come from wasm and `Return` is a value coming from the
361    /// host going back to wasm.
362    ///
363    /// Additionally the `func` takes a
364    /// [`StoreContextMut`](crate::StoreContextMut) as its first parameter.
365    ///
366    /// Note that `func` must be an `Fn` and must also be `Send + Sync +
367    /// 'static`. Shared state within a func is typically accessed with the `T`
368    /// type parameter from [`Store<T>`](crate::Store) which is accessible
369    /// through the leading [`StoreContextMut<'_, T>`](crate::StoreContextMut)
370    /// argument which can be provided to the `func` given here.
371    //
372    // TODO: needs more words and examples
373    pub fn func_wrap<F, Params, Return>(&mut self, name: &str, func: F) -> Result<()>
374    where
375        F: Fn(StoreContextMut<T>, Params) -> Result<Return> + Send + Sync + 'static,
376        Params: ComponentNamedList + Lift + 'static,
377        Return: ComponentNamedList + Lower + 'static,
378    {
379        self.insert(name, Definition::Func(HostFunc::from_closure(func)))?;
380        Ok(())
381    }
382
383    /// Defines a new host-provided async function into this [`Linker`].
384    ///
385    /// This is exactly like [`Self::func_wrap`] except it takes an async
386    /// host function.
387    #[cfg(feature = "async")]
388    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
389    pub fn func_wrap_async<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
390    where
391        F: for<'a> Fn(
392                StoreContextMut<'a, T>,
393                Params,
394            ) -> Box<dyn Future<Output = Result<Return>> + Send + 'a>
395            + Send
396            + Sync
397            + 'static,
398        Params: ComponentNamedList + Lift + 'static,
399        Return: ComponentNamedList + Lower + 'static,
400    {
401        assert!(
402            self.engine.config().async_support,
403            "cannot use `func_wrap_async` without enabling async support in the config"
404        );
405        let ff = move |mut store: StoreContextMut<'_, T>, params: Params| -> Result<Return> {
406            let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
407            let mut future = Pin::from(f(store.as_context_mut(), params));
408            unsafe { async_cx.block_on(future.as_mut()) }?
409        };
410        self.func_wrap(name, ff)
411    }
412
413    /// Define a new host-provided function using dynamically typed values.
414    ///
415    /// The `name` provided is the name of the function to define and the
416    /// `func` provided is the host-defined closure to invoke when this
417    /// function is called.
418    ///
419    /// This function is the "dynamic" version of defining a host function as
420    /// compared to [`LinkerInstance::func_wrap`]. With
421    /// [`LinkerInstance::func_wrap`] a function's type is statically known but
422    /// with this method the `func` argument's type isn't known ahead of time.
423    /// That means that `func` can be by imported component so long as it's
424    /// imported as a matching name.
425    ///
426    /// Type information will be available at execution time, however. For
427    /// example when `func` is invoked the second argument, a `&[Val]` list,
428    /// contains [`Val`] entries that say what type they are. Additionally the
429    /// third argument, `&mut [Val]`, is the expected number of results. Note
430    /// that the expected types of the results cannot be learned during the
431    /// execution of `func`. Learning that would require runtime introspection
432    /// of a component.
433    ///
434    /// Return values, stored in the third argument of `&mut [Val]`, are
435    /// type-checked at runtime to ensure that they have the appropriate type.
436    /// A trap will be raised if they do not have the right type.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use wasmtime::{Store, Engine};
442    /// use wasmtime::component::{Component, Linker, Val};
443    ///
444    /// # fn main() -> wasmtime::Result<()> {
445    /// let engine = Engine::default();
446    /// let component = Component::new(
447    ///     &engine,
448    ///     r#"
449    ///         (component
450    ///             (import "thunk" (func $thunk))
451    ///             (import "is-even" (func $is-even (param "x" u32) (result bool)))
452    ///
453    ///             (core module $m
454    ///                 (import "" "thunk" (func $thunk))
455    ///                 (import "" "is-even" (func $is-even (param i32) (result i32)))
456    ///
457    ///                 (func (export "run")
458    ///                     call $thunk
459    ///
460    ///                     (call $is-even (i32.const 1))
461    ///                     if unreachable end
462    ///
463    ///                     (call $is-even (i32.const 2))
464    ///                     i32.eqz
465    ///                     if unreachable end
466    ///                 )
467    ///             )
468    ///             (core func $thunk (canon lower (func $thunk)))
469    ///             (core func $is-even (canon lower (func $is-even)))
470    ///             (core instance $i (instantiate $m
471    ///                 (with "" (instance
472    ///                     (export "thunk" (func $thunk))
473    ///                     (export "is-even" (func $is-even))
474    ///                 ))
475    ///             ))
476    ///
477    ///             (func (export "run") (canon lift (core func $i "run")))
478    ///         )
479    ///     "#,
480    /// )?;
481    ///
482    /// let mut linker = Linker::<()>::new(&engine);
483    ///
484    /// // Sample function that takes no arguments.
485    /// linker.root().func_new("thunk", |_store, params, results| {
486    ///     assert!(params.is_empty());
487    ///     assert!(results.is_empty());
488    ///     println!("Look ma, host hands!");
489    ///     Ok(())
490    /// })?;
491    ///
492    /// // This function takes one argument and returns one result.
493    /// linker.root().func_new("is-even", |_store, params, results| {
494    ///     assert_eq!(params.len(), 1);
495    ///     let param = match params[0] {
496    ///         Val::U32(n) => n,
497    ///         _ => panic!("unexpected type"),
498    ///     };
499    ///
500    ///     assert_eq!(results.len(), 1);
501    ///     results[0] = Val::Bool(param % 2 == 0);
502    ///     Ok(())
503    /// })?;
504    ///
505    /// let mut store = Store::new(&engine, ());
506    /// let instance = linker.instantiate(&mut store, &component)?;
507    /// let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
508    /// run.call(&mut store, ())?;
509    /// # Ok(())
510    /// # }
511    /// ```
512    pub fn func_new(
513        &mut self,
514        name: &str,
515        func: impl Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
516    ) -> Result<()> {
517        self.insert(name, Definition::Func(HostFunc::new_dynamic(func)))?;
518        Ok(())
519    }
520
521    /// Define a new host-provided async function using dynamic types.
522    ///
523    /// This is exactly like [`Self::func_new`] except it takes an async
524    /// host function.
525    #[cfg(feature = "async")]
526    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
527    pub fn func_new_async<F>(&mut self, name: &str, f: F) -> Result<()>
528    where
529        F: for<'a> Fn(
530                StoreContextMut<'a, T>,
531                &'a [Val],
532                &'a mut [Val],
533            ) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
534            + Send
535            + Sync
536            + 'static,
537    {
538        assert!(
539            self.engine.config().async_support,
540            "cannot use `func_new_async` without enabling async support in the config"
541        );
542        let ff = move |mut store: StoreContextMut<'_, T>, params: &[Val], results: &mut [Val]| {
543            let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
544            let mut future = Pin::from(f(store.as_context_mut(), params, results));
545            unsafe { async_cx.block_on(future.as_mut()) }?
546        };
547        self.func_new(name, ff)
548    }
549
550    /// Defines a [`Module`] within this instance.
551    ///
552    /// This can be used to provide a core wasm [`Module`] as an import to a
553    /// component. The [`Module`] provided is saved within the linker for the
554    /// specified `name` in this instance.
555    pub fn module(&mut self, name: &str, module: &Module) -> Result<()> {
556        self.insert(name, Definition::Module(module.clone()))?;
557        Ok(())
558    }
559
560    /// Defines a new resource of a given [`ResourceType`] in this linker.
561    ///
562    /// This function is used to specify resources defined in the host.
563    ///
564    /// The `name` argument is the name to define the resource within this
565    /// linker.
566    ///
567    /// The `dtor` provided is a destructor that will get invoked when an owned
568    /// version of this resource is destroyed from the guest. Note that this
569    /// destructor is not called when a host-owned resource is destroyed as it's
570    /// assumed the host knows how to handle destroying its own resources.
571    ///
572    /// The `dtor` closure is provided the store state as the first argument
573    /// along with the representation of the resource that was just destroyed.
574    ///
575    /// [`Resource<U>`]: crate::component::Resource
576    ///
577    /// # Errors
578    ///
579    /// The provided `dtor` closure returns an error if something goes wrong
580    /// when a guest calls the `dtor` to drop a `Resource<T>` such as
581    /// a runtime trap or a runtime limit being exceeded.
582    pub fn resource(
583        &mut self,
584        name: &str,
585        ty: ResourceType,
586        dtor: impl Fn(StoreContextMut<'_, T>, u32) -> Result<()> + Send + Sync + 'static,
587    ) -> Result<()> {
588        let dtor = Arc::new(crate::func::HostFunc::wrap(
589            &self.engine,
590            move |mut cx: crate::Caller<'_, T>, param: u32| dtor(cx.as_context_mut(), param),
591        ));
592        self.insert(name, Definition::Resource(ty, dtor))?;
593        Ok(())
594    }
595
596    /// Defines a nested instance within this instance.
597    ///
598    /// This can be used to describe arbitrarily nested levels of instances
599    /// within a linker to satisfy nested instance exports of components.
600    pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
601        self.as_mut().into_instance(name)
602    }
603
604    /// Same as [`LinkerInstance::instance`] except with different lifetime
605    /// parameters.
606    pub fn into_instance(mut self, name: &str) -> Result<Self> {
607        let name = self.insert(name, Definition::Instance(NameMap::default()))?;
608        self.map = match self.map.definitions.get_mut(&name) {
609            Some(Definition::Instance(map)) => map,
610            _ => unreachable!(),
611        };
612        self.path.truncate(self.path_len);
613        self.path.push(name);
614        self.path_len += 1;
615        Ok(self)
616    }
617
618    fn insert(&mut self, name: &str, item: Definition) -> Result<usize> {
619        self.map
620            .insert(name, &mut self.strings, self.allow_shadowing, item)
621    }
622}
623
624impl NameMap {
625    /// Looks up `name` within this map, using the interning specified by
626    /// `strings`.
627    ///
628    /// This may return a definition even if `name` wasn't exactly defined in
629    /// this map, such as looking up `a:b/c@0.2.0` when the map only has
630    /// `a:b/c@0.2.1` defined.
631    pub(crate) fn get(&self, name: &str, strings: &Strings) -> Option<&Definition> {
632        // First look up an exact match and if that's found return that. This
633        // enables defining multiple versions in the map and the requested
634        // version is returned if it matches exactly.
635        let candidate = strings.lookup(name).and_then(|k| self.definitions.get(&k));
636        if let Some(def) = candidate {
637            return Some(def);
638        }
639
640        // Failing that, then try to look for a semver-compatible alternative.
641        // This looks up the key based on `name`, if any, and then looks to see
642        // if that was intern'd in `strings`. Given all that look to see if it
643        // was defined in `alternate_lookups` and finally at the end that exact
644        // key is then used to look up again in `self.definitions`.
645        let (alternate_name, _version) = alternate_lookup_key(name)?;
646        let alternate_key = strings.lookup(alternate_name)?;
647        let (exact_key, _version) = self.alternate_lookups.get(&alternate_key)?;
648        self.definitions.get(&exact_key)
649    }
650
651    /// Inserts the `name` specified into this map.
652    ///
653    /// The name is intern'd through the `strings` argument and shadowing is
654    /// controlled by the `allow_shadowing` variable.
655    ///
656    /// This function will automatically insert an entry in
657    /// `self.alternate_lookups` if `name` is a semver-looking name.
658    fn insert(
659        &mut self,
660        name: &str,
661        strings: &mut Strings,
662        allow_shadowing: bool,
663        item: Definition,
664    ) -> Result<usize> {
665        // Always insert `name` and `item` as an exact definition.
666        let key = strings.intern(name);
667        match self.definitions.entry(key) {
668            Entry::Occupied(_) if !allow_shadowing => {
669                bail!("import of `{}` defined twice", strings.strings[key])
670            }
671            Entry::Occupied(mut e) => {
672                e.insert(item);
673            }
674            Entry::Vacant(v) => {
675                v.insert(item);
676            }
677        }
678
679        // If `name` is a semver-looking thing, like `a:b/c@1.0.0`, then also
680        // insert an entry in the semver-compatible map under a key such as
681        // `a:b/c@1`.
682        //
683        // This key is used during `get` later on.
684        if let Some((alternate_key, version)) = alternate_lookup_key(name) {
685            let alternate_key = strings.intern(alternate_key);
686            match self.alternate_lookups.entry(alternate_key) {
687                Entry::Occupied(mut e) => {
688                    let (_, prev_version) = e.get();
689                    // Prefer the latest version, so only do this if we're
690                    // greater than the prior version.
691                    if version > *prev_version {
692                        e.insert((key, version));
693                    }
694                }
695                Entry::Vacant(v) => {
696                    v.insert((key, version));
697                }
698            }
699        }
700        Ok(key)
701    }
702}
703
704/// Determines a version-based "alternate lookup key" for the `name` specified.
705///
706/// Some examples are:
707///
708/// * `foo` => `None`
709/// * `foo:bar/baz` => `None`
710/// * `foo:bar/baz@1.1.2` => `Some(foo:bar/baz@1)`
711/// * `foo:bar/baz@0.1.0` => `Some(foo:bar/baz@0.1)`
712/// * `foo:bar/baz@0.0.1` => `None`
713/// * `foo:bar/baz@0.1.0-rc.2` => `None`
714///
715/// This alternate lookup key is intended to serve the purpose where a
716/// semver-compatible definition can be located, if one is defined, at perhaps
717/// either a newer or an older version.
718fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
719    let at = name.find('@')?;
720    let version_string = &name[at + 1..];
721    let version = Version::parse(version_string).ok()?;
722    if !version.pre.is_empty() {
723        // If there's a prerelease then don't consider that compatible with any
724        // other version number.
725        None
726    } else if version.major != 0 {
727        // If the major number is nonzero then compatibility is up to the major
728        // version number, so return up to the first decimal.
729        let first_dot = version_string.find('.')? + at + 1;
730        Some((&name[..first_dot], version))
731    } else if version.minor != 0 {
732        // Like the major version if the minor is nonzero then patch releases
733        // are all considered to be on a "compatible track".
734        let first_dot = version_string.find('.')? + at + 1;
735        let second_dot = name[first_dot + 1..].find('.')? + first_dot + 1;
736        Some((&name[..second_dot], version))
737    } else {
738        // If the patch number is the first nonzero entry then nothing can be
739        // compatible with this patch, e.g. 0.0.1 isn't' compatible with
740        // any other version inherently.
741        None
742    }
743}
744
745impl Strings {
746    fn intern(&mut self, string: &str) -> usize {
747        if let Some(idx) = self.string2idx.get(string) {
748            return *idx;
749        }
750        let string: Arc<str> = string.into();
751        let idx = self.strings.len();
752        self.strings.push(string.clone());
753        self.string2idx.insert(string, idx);
754        idx
755    }
756
757    pub fn lookup(&self, string: &str) -> Option<usize> {
758        self.string2idx.get(string).cloned()
759    }
760}
761
762#[cfg(test)]
763mod tests {
764    #[test]
765    fn alternate_lookup_key() {
766        fn alt(s: &str) -> Option<&str> {
767            super::alternate_lookup_key(s).map(|(s, _)| s)
768        }
769
770        assert_eq!(alt("x"), None);
771        assert_eq!(alt("x:y/z"), None);
772        assert_eq!(alt("x:y/z@1.0.0"), Some("x:y/z@1"));
773        assert_eq!(alt("x:y/z@1.1.0"), Some("x:y/z@1"));
774        assert_eq!(alt("x:y/z@1.1.2"), Some("x:y/z@1"));
775        assert_eq!(alt("x:y/z@2.1.2"), Some("x:y/z@2"));
776        assert_eq!(alt("x:y/z@2.1.2+abc"), Some("x:y/z@2"));
777        assert_eq!(alt("x:y/z@0.1.2"), Some("x:y/z@0.1"));
778        assert_eq!(alt("x:y/z@0.1.3"), Some("x:y/z@0.1"));
779        assert_eq!(alt("x:y/z@0.2.3"), Some("x:y/z@0.2"));
780        assert_eq!(alt("x:y/z@0.2.3+abc"), Some("x:y/z@0.2"));
781        assert_eq!(alt("x:y/z@0.0.1"), None);
782        assert_eq!(alt("x:y/z@0.0.1-pre"), None);
783        assert_eq!(alt("x:y/z@0.1.0-pre"), None);
784        assert_eq!(alt("x:y/z@1.0.0-pre"), None);
785    }
786}