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}