midenc_hir/dialects/builtin/ops/component/interface.rs
1use alloc::format;
2use core::fmt;
3
4use super::Component;
5use crate::{
6 diagnostics::{miette, Diagnostic},
7 dialects::builtin::{Function, Module},
8 version::Version,
9 FxHashMap, Signature, Symbol, SymbolName, SymbolNameComponent, SymbolPath, SymbolTable, Type,
10 Visibility,
11};
12
13/// The fully-qualfied identifier of a component
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct ComponentId {
16 /// The namespace in which the component is defined
17 pub namespace: SymbolName,
18 /// The name of this component
19 pub name: SymbolName,
20 /// The semantic version number of this component
21 pub version: Version,
22}
23
24impl fmt::Display for ComponentId {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 write!(f, "{}:{}@{}", &self.namespace, &self.name, &self.version)
27 }
28}
29
30impl ComponentId {
31 /// Returns true if `self` and `other` are equal according to semantic versioning rules:
32 ///
33 /// * Namespace and name are identical
34 /// * Version numbers are considered equal according to semantic versioning (i.e. if the version
35 /// strings differ only in build metadata, then they are considered equal).
36 pub fn is_match(&self, other: &Self) -> bool {
37 self.namespace == other.namespace
38 && self.name == other.name
39 && self.version.cmp_precedence(&other.version).is_eq()
40 }
41
42 /// Get the Miden Assembly [LibraryPath] that uniquely identifies this interface.
43 pub fn to_library_path(&self) -> midenc_session::LibraryPath {
44 use midenc_session::{LibraryNamespace, LibraryPath};
45
46 let ns = format!("{}:{}@{}", &self.namespace, &self.name, &self.version);
47 let namespace = LibraryNamespace::User(ns.into_boxed_str().into());
48 LibraryPath::new_from_components(namespace, [])
49 }
50}
51
52#[derive(thiserror::Error, Debug, Diagnostic)]
53pub enum InvalidComponentIdError {
54 #[error("invalid component id: missing namespace identifier")]
55 #[diagnostic()]
56 MissingNamespace,
57 #[error("invalid component id: missing component name")]
58 #[diagnostic()]
59 MissingName,
60 #[error("invalid component id: missing version")]
61 #[diagnostic()]
62 MissingVersion,
63 #[error("invalid component version: {0}")]
64 #[diagnostic()]
65 InvalidVersion(#[from] crate::version::semver::Error),
66}
67
68impl TryFrom<&SymbolPath> for ComponentId {
69 type Error = InvalidComponentIdError;
70
71 fn try_from(path: &SymbolPath) -> Result<Self, Self::Error> {
72 let mut components = path.components().peekable();
73 components.next_if_eq(&SymbolNameComponent::Root);
74
75 let (ns, name, version) = match components.next().map(|c| c.as_symbol_name()) {
76 None => return Err(InvalidComponentIdError::MissingNamespace),
77 Some(name) => match name.as_str().split_once(':') {
78 Some((ns, name)) => match name.split_once('@') {
79 Some((name, version)) => (
80 SymbolName::intern(ns),
81 SymbolName::intern(name),
82 Version::parse(version).map_err(InvalidComponentIdError::InvalidVersion)?,
83 ),
84 None => return Err(InvalidComponentIdError::MissingVersion),
85 },
86 None => return Err(InvalidComponentIdError::MissingNamespace),
87 },
88 };
89
90 Ok(Self {
91 namespace: ns,
92 name,
93 version,
94 })
95 }
96}
97
98impl core::str::FromStr for ComponentId {
99 type Err = InvalidComponentIdError;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 let (version, rest) = match s.rsplit_once('@') {
103 None => (Version::new(1, 0, 0), s),
104 Some((rest, version)) => (version.parse::<Version>()?, rest),
105 };
106 let (ns, name) = match rest.split_once(':') {
107 Some((ns, name)) => (SymbolName::intern(ns), SymbolName::intern(name)),
108 None => return Err(InvalidComponentIdError::MissingNamespace),
109 };
110 Ok(Self {
111 namespace: ns,
112 name,
113 version,
114 })
115 }
116}
117
118impl From<&Component> for ComponentId {
119 fn from(value: &Component) -> Self {
120 let namespace = value.namespace().as_symbol();
121 let name = value.name().as_symbol();
122 let version = value.version().clone();
123
124 Self {
125 namespace,
126 name,
127 version,
128 }
129 }
130}
131
132/// A [ComponentInterface] is a description of the "skeleton" of a component, i.e.:
133///
134/// * Basic metadata about the component itself, e.g. name
135/// * The set of interfaces it requires to be provided in order to instantiate the component
136/// * The set of exported items provided by the interface, which can be used to fulfill imports
137/// of other components.
138///
139/// This type is derived from a [Component] operation, but does not represent an operation itself,
140/// instead, this is used by the compiler to reason about what components are available, what is
141/// required, and whether or not all requirements can be met.
142pub struct ComponentInterface {
143 id: ComponentId,
144 /// The visibility of this component in the interface (public or internal)
145 visibility: Visibility,
146 /// This flag is set to `true` if the interface is completely abstract (no definitions)
147 is_externally_defined: bool,
148 /// The set of imports required by this component.
149 ///
150 /// An import can be satisfied by any [Component] whose interface matches the one specified.
151 /// In specific terms, this refers to the signatures/types of all symbols in the interface,
152 /// rather than the names. The compiler will handle rebinding uses of symbols in the interface
153 /// to the concrete symbols of the provided implementation - the important part is that the
154 /// implementation is explicitly provided as an implementation of that interface, i.e. we do
155 /// not try to simply find a match amongst all components.
156 ///
157 /// In the Wasm Component Model, such explicit instantiations are provided for us, so wiring
158 /// up the component hierarchy derived from a Wasm component should be straightforward. It
159 /// remains to be seen if there are non-Wasm sources where this is more problematic.
160 imports: FxHashMap<ComponentId, ComponentInterface>,
161 /// The set of items which form the interface of this component, and can be referenced from
162 /// other components.
163 ///
164 /// All "exports" from a component interface are named, but can represent a variety of IR
165 /// entities.
166 exports: FxHashMap<SymbolName, ComponentExport>,
167}
168
169impl ComponentInterface {
170 /// Derive a [ComponentInterface] from the given [Component]
171 pub fn new(component: &Component) -> Self {
172 let mut imports = FxHashMap::default();
173 let mut exports = FxHashMap::default();
174 let mut is_externally_defined = true;
175
176 let id = ComponentId::from(component);
177
178 let symbol_manager = component.symbol_manager();
179 for symbol_ref in symbol_manager.symbols().symbols() {
180 let symbol = symbol_ref.borrow();
181 let symbol_op = symbol.as_symbol_operation();
182 let name = symbol.name();
183 if let Some(module) = symbol_op.downcast_ref::<Module>() {
184 let interface = ModuleInterface::new(module);
185 let visibility = interface.visibility;
186 let is_abstract = interface.is_abstract;
187 let item = ComponentExport::Module(interface);
188 // Modules at the top level of a component are always exports, however we care about
189 // whether the module is abstract or not. Abstract module interfaces are only
190 // permitted in abstract component interfaces, otherwise all modules in the component
191 // must be definitions. We assert that this is the case, in order to catch any
192 // instances where the compiler produces invalid component IR.
193 if is_abstract {
194 // This represents an abstract module interface provided by this component
195 assert!(
196 is_externally_defined,
197 "invalid component: abstract module '{name}' is not permitted in a \
198 non-abstract component"
199 );
200 assert!(visibility.is_public(), "abstract modules must have public visibility");
201 exports.insert(name, item);
202 } else {
203 // This represents a concrete module definition
204 assert!(
205 !is_externally_defined || exports.is_empty(),
206 "invalid component: concrete module '{name}' is not permitted in an \
207 abstract component interface"
208 );
209 // We only export public or internal modules
210 if !visibility.is_private() {
211 exports.insert(name, item);
212 }
213 is_externally_defined = false;
214 }
215 } else if let Some(child_component) = symbol_op.downcast_ref::<Component>() {
216 let interface = ComponentInterface::new(child_component);
217 let visibility = interface.visibility;
218 if interface.is_externally_defined {
219 // This is an import of an externally-defined component
220 let import_id = interface.id.clone();
221 imports.insert(import_id, interface);
222 } else {
223 if !visibility.is_private() {
224 // This is an exported component definition (either internally or globally)
225 exports.insert(name, ComponentExport::Component(interface));
226 }
227 is_externally_defined = false;
228 }
229 } else {
230 // If this happens we should assert - something is definitely wrong
231 unimplemented!(
232 "unsupported symbol type `{}` in component: '{}'",
233 symbol_op.name(),
234 symbol.name()
235 );
236 }
237 }
238
239 Self {
240 id,
241 is_externally_defined,
242 visibility: *component.visibility(),
243 imports,
244 exports,
245 }
246 }
247
248 pub fn id(&self) -> &ComponentId {
249 &self.id
250 }
251
252 /// Returns true if this interface describes a component for which we do not have a definition.
253 pub fn is_externally_defined(&self) -> bool {
254 self.is_externally_defined
255 }
256
257 /// Returns the visibility of this component in its current context.
258 ///
259 /// This is primarily used to determine whether or not this component is exportable from its
260 /// parent component, and whether or not symbols defined within it are visible to siblings.
261 pub fn visibility(&self) -> Visibility {
262 self.visibility
263 }
264
265 pub fn exports(&self) -> &FxHashMap<SymbolName, ComponentExport> {
266 &self.exports
267 }
268
269 /// Returns true if this component exports `name`
270 ///
271 /// The given symbol name is expected to be found at the top level of the component, i.e. this
272 /// function does not attempt to resolve nested symbol references.
273 pub fn exports_symbol(&self, name: SymbolName) -> bool {
274 self.exports.contains_key(&name)
275 }
276
277 /// Returns true if this component provides the given [ModuleInterface].
278 ///
279 /// A component "provides" a module if it defines a module with the same name, and which
280 /// contains all of the symbols of the given interface with matching types/signatures, i.e. it
281 /// is a superset of the provided interface.
282 ///
283 /// NOTE: This does not return false if the component or module are externally-defined, it only
284 /// answers the question of whether or not, if we had an instance of this component, would it
285 /// satisfy the provided interface's requirements.
286 pub fn provides_module(&self, interface: &ModuleInterface) -> bool {
287 self.exports
288 // Do we export a symbol with the given name
289 .get(&interface.name)
290 // The symbol must be a module
291 .and_then(|export| match export {
292 ComponentExport::Module(definition) => Some(definition),
293 _ => None,
294 })
295 // The module must provide exports for all of `interface`'s imports
296 .map(|definition| {
297 interface.imports.iter().all(|(imported_symbol, import)| {
298 definition.exports.get(imported_symbol).is_some_and(|export| export == import)
299 })
300 })
301 // If we didn't find the symbol, or it wasn't a module, return false
302 .unwrap_or(false)
303 }
304
305 /// Returns true if this component provides the given [ComponentInterface].
306 ///
307 /// A component "provides" a component if either of the following are true:
308 ///
309 /// * The component itself has the same name as the given interface, and defines all of the
310 /// items imported by the interface, with matching types/signatures where appropriate.
311 /// * The component exports a component that matches the given interface as described above.
312 ///
313 /// NOTE: This does not return false if the component or a matching child component are
314 /// externally-defined, it only answers the question of whether or not, if we had an instance of
315 /// the matching component, would it satisfy the provided interface's requirements.
316 pub fn provides_component(&self, interface: &ComponentInterface) -> bool {
317 if self.matches(interface) {
318 return true;
319 }
320
321 self.exports
322 // Do we export a symbol with the given name
323 .get(&interface.id.name)
324 // The symbol must be a component
325 .and_then(|export| match export {
326 ComponentExport::Component(definition) => Some(definition),
327 _ => None,
328 })
329 // The component must provide exports for all of `interface`'s imports
330 .map(|definition| definition.matches(interface))
331 // If we didn't find the symbol, or it wasn't a module, return false
332 .unwrap_or(false)
333 }
334
335 /// Returns true if `self` provides a superset of the imports required by `other`, or put
336 /// another way - `self` matches the component import described by `other`.
337 pub fn matches(&self, other: &Self) -> bool {
338 if !self.id.is_match(&other.id) {
339 return false;
340 }
341
342 other.imports.iter().all(|(imported_id, import)| {
343 self.exports
344 .get(&imported_id.name)
345 .is_some_and(|export| export.matches_component(import))
346 })
347 }
348}
349
350pub enum ComponentExport {
351 /// A nested component which has public visibility and is thus exported from its parent component
352 Component(ComponentInterface),
353 /// A module which has public visibility and is thus exported from its parent component
354 Module(ModuleInterface),
355}
356impl ComponentExport {
357 /// Returns true if this export describes a component that provides a superset of the imports
358 /// required by `other`, i.e. `self` is a component which matches the component import described
359 /// by `other`.
360 pub fn matches_component(&self, other: &ComponentInterface) -> bool {
361 let Self::Component(definition) = self else {
362 return false;
363 };
364
365 definition.matches(other)
366 }
367}
368
369pub struct ModuleInterface {
370 name: SymbolName,
371 /// The visibility of this module in the interface (public vs internal)
372 visibility: Visibility,
373 /// This flag is set to `true` if the interface is completely abstract (no definitions)
374 is_abstract: bool,
375 imports: FxHashMap<SymbolName, ModuleExport>,
376 exports: FxHashMap<SymbolName, ModuleExport>,
377}
378
379impl ModuleInterface {
380 /// Derive a [ModuleInterface] from the given [Module]
381 pub fn new(module: &Module) -> Self {
382 let mut imports = FxHashMap::default();
383 let mut exports = FxHashMap::default();
384 let mut is_abstract = true;
385
386 let symbol_manager = module.symbol_manager();
387 for symbol_ref in symbol_manager.symbols().symbols() {
388 let symbol = symbol_ref.borrow();
389 let name = symbol.name();
390 if let Some(func) = symbol.as_symbol_operation().downcast_ref::<Function>() {
391 let signature = func.signature().clone();
392 let visibility = signature.visibility;
393 let item = ModuleExport::Function { name, signature };
394 if func.is_declaration() {
395 // This is an import of an externally-defined function
396 imports.insert(name, item);
397 } else {
398 if !visibility.is_private() {
399 // This is an exported function definition (either internally or globally)
400 exports.insert(name, item);
401 }
402 is_abstract = false;
403 }
404 }
405
406 // TODO: GlobalVariable
407 }
408
409 Self {
410 name: module.name().as_symbol(),
411 visibility: *module.visibility(),
412 is_abstract,
413 imports,
414 exports,
415 }
416 }
417
418 pub fn name(&self) -> SymbolName {
419 self.name
420 }
421
422 pub fn visibility(&self) -> Visibility {
423 self.visibility
424 }
425
426 pub fn is_externally_defined(&self) -> bool {
427 self.is_abstract
428 }
429
430 pub fn imports(&self) -> &FxHashMap<SymbolName, ModuleExport> {
431 &self.imports
432 }
433
434 pub fn exports(&self) -> &FxHashMap<SymbolName, ModuleExport> {
435 &self.exports
436 }
437}
438
439#[derive(Clone, PartialEq, Eq)]
440pub enum ModuleExport {
441 /// A global symbol with the given type (if known/specified)
442 ///
443 /// NOTE: Global variables are _not_ exportable across component boundaries
444 #[allow(unused)]
445 Global { name: SymbolName, ty: Option<Type> },
446 /// A function symbol with the given signature
447 Function {
448 name: SymbolName,
449 signature: Signature,
450 },
451}