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}