wasmtime/runtime/component/
matching.rs

1use crate::Module;
2use crate::component::ResourceType;
3use crate::component::func::HostFunc;
4use crate::component::linker::{Definition, Strings};
5use crate::component::types::{FutureType, StreamType};
6use crate::runtime::vm::component::ComponentInstance;
7use crate::types::matching;
8use crate::{Engine, prelude::*};
9use alloc::sync::Arc;
10use wasmtime_environ::PrimaryMap;
11use wasmtime_environ::component::{
12    ComponentTypes, NameMap, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex,
13    TypeFutureTableIndex, TypeModule, TypeResourceTable, TypeResourceTableIndex,
14    TypeStreamTableIndex,
15};
16
17pub struct TypeChecker<'a> {
18    pub engine: &'a Engine,
19    pub types: &'a Arc<ComponentTypes>,
20    pub strings: &'a Strings,
21    pub imported_resources: Arc<PrimaryMap<ResourceIndex, ResourceType>>,
22}
23
24#[derive(Copy, Clone)]
25#[doc(hidden)]
26pub struct InstanceType<'a> {
27    pub types: &'a Arc<ComponentTypes>,
28    pub resources: &'a Arc<PrimaryMap<ResourceIndex, ResourceType>>,
29}
30
31impl TypeChecker<'_> {
32    pub(crate) fn definition(
33        &mut self,
34        expected: &TypeDef,
35        actual: Option<&Definition>,
36    ) -> Result<()> {
37        match *expected {
38            TypeDef::Module(t) => match actual {
39                Some(Definition::Module(actual)) => self.module(&self.types[t], actual),
40                Some(actual) => bail!("expected module found {}", actual.desc()),
41                None => bail!("module implementation is missing"),
42            },
43            TypeDef::ComponentInstance(t) => match actual {
44                Some(Definition::Instance(actual)) => self.instance(&self.types[t], Some(actual)),
45                None => self.instance(&self.types[t], None),
46                Some(actual) => bail!("expected instance found {}", actual.desc()),
47            },
48            TypeDef::ComponentFunc(t) => match actual {
49                Some(Definition::Func(actual)) => self.func(t, actual),
50                Some(actual) => bail!("expected function found {}", actual.desc()),
51                None => bail!("function implementation is missing"),
52            },
53            TypeDef::Component(_) => match actual {
54                Some(actual) => bail!("expected component found {}", actual.desc()),
55                None => bail!("component implementation is missing"),
56            },
57            TypeDef::Interface(_) => match actual {
58                Some(actual) => bail!("expected type found {}", actual.desc()),
59                None => bail!("type implementation is missing"),
60            },
61
62            TypeDef::Resource(i) => {
63                let i = self.types[i].unwrap_concrete_ty();
64                let actual = match actual {
65                    Some(Definition::Resource(actual, _dtor)) => actual,
66
67                    // If a resource is imported yet nothing was supplied then
68                    // that's only successful if the resource has itself
69                    // already been defined. If it's already defined then that
70                    // means that this is an `(eq ...)` import which is not
71                    // required to be satisfied via `Linker` definitions in the
72                    // Wasmtime API.
73                    None if self.imported_resources.get(i).is_some() => return Ok(()),
74
75                    Some(actual) => bail!("expected resource found {}", actual.desc()),
76                    None => bail!("resource implementation is missing"),
77                };
78
79                match self.imported_resources.get(i) {
80                    // If `i` hasn't been pushed onto `imported_resources` yet
81                    // then that means that it's the first time a new resource
82                    // was introduced, so record the type of this resource.  It
83                    // should always be the case that the next index assigned
84                    // is equal to `i` since types should be checked in the
85                    // same order they were assigned into the `Component` type.
86                    //
87                    // Note the `get_mut` here which is expected to always
88                    // succeed since `imported_resources` has not yet been
89                    // cloned.
90                    None => {
91                        let resources = Arc::get_mut(&mut self.imported_resources).unwrap();
92                        let id = resources.push(*actual);
93                        assert_eq!(id, i);
94                    }
95
96                    // If `i` has been defined, however, then that means that
97                    // this is an `(eq ..)` bounded type imported because it's
98                    // referring to a previously defined type.  In this
99                    // situation it's not required to provide a type import but
100                    // if it's supplied then it must be equal. In this situation
101                    // it's supplied, so test for equality.
102                    Some(expected) => {
103                        if expected != actual {
104                            bail!("mismatched resource types");
105                        }
106                    }
107                }
108                Ok(())
109            }
110
111            // not possible for valid components to import
112            TypeDef::CoreFunc(_) => unreachable!(),
113        }
114    }
115
116    fn module(&self, expected: &TypeModule, actual: &Module) -> Result<()> {
117        let actual = actual.env_module();
118
119        // Every export that is expected should be in the actual module we have
120        for (name, expected) in expected.exports.iter() {
121            let idx = actual
122                .exports
123                .get(name)
124                .ok_or_else(|| anyhow!("module export `{name}` not defined"))?;
125            let actual = actual.type_of(*idx);
126            matching::entity_ty(self.engine, expected, &actual)
127                .with_context(|| format!("module export `{name}` has the wrong type"))?;
128        }
129
130        // Note the opposite order of checks here. Every import that the actual
131        // module expects should be imported by the expected module since the
132        // expected module has the set of items given to the actual module.
133        // Additionally the "matches" check is inverted here.
134        for (module, name, actual) in actual.imports() {
135            // TODO: shouldn't need a `.to_string()` here ideally
136            let expected = expected
137                .imports
138                .get(&(module.to_string(), name.to_string()))
139                .ok_or_else(|| anyhow!("module import `{module}::{name}` not defined"))?;
140            matching::entity_ty(self.engine, &actual, expected)
141                .with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
142        }
143        Ok(())
144    }
145
146    fn instance(
147        &mut self,
148        expected: &TypeComponentInstance,
149        actual: Option<&NameMap<usize, Definition>>,
150    ) -> Result<()> {
151        // Like modules, every export in the expected type must be present in
152        // the actual type. It's ok, though, to have extra exports in the actual
153        // type.
154        for (name, expected) in expected.exports.iter() {
155            // Interface types may be exported from a component in order to give them a name, but
156            // they don't have a definition in the sense that this search is interested in, so
157            // ignore them.
158            if let TypeDef::Interface(_) = expected {
159                continue;
160            }
161            let actual = actual.and_then(|map| map.get(name, self.strings));
162            self.definition(expected, actual)
163                .with_context(|| format!("instance export `{name}` has the wrong type"))?;
164        }
165        Ok(())
166    }
167
168    fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> {
169        let instance_type = InstanceType {
170            types: self.types,
171            resources: &self.imported_resources,
172        };
173        actual.typecheck(expected, &instance_type)
174    }
175}
176
177impl Definition {
178    fn desc(&self) -> &'static str {
179        match self {
180            Definition::Module(_) => "module",
181            Definition::Func(_) => "func",
182            Definition::Instance(_) => "instance",
183            Definition::Resource(..) => "resource",
184        }
185    }
186}
187
188impl<'a> InstanceType<'a> {
189    pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> {
190        InstanceType {
191            types: instance.component().types(),
192            resources: instance.resource_types(),
193        }
194    }
195
196    pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType {
197        match self.types[index] {
198            TypeResourceTable::Concrete { ty, .. } => self
199                .resources
200                .get(ty)
201                .copied()
202                .unwrap_or_else(|| ResourceType::uninstantiated(&self.types, ty)),
203            TypeResourceTable::Abstract(ty) => ResourceType::abstract_(&self.types, ty),
204        }
205    }
206
207    pub fn future_type(&self, index: TypeFutureTableIndex) -> FutureType {
208        FutureType::from(self.types[index].ty, self)
209    }
210
211    pub fn stream_type(&self, index: TypeStreamTableIndex) -> StreamType {
212        StreamType::from(self.types[index].ty, self)
213    }
214}