diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 7d9b34e9..09fc6852 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -1259,6 +1259,41 @@ impl CompositionGraph { generation: entry.generation, } } + + /// Yields an iterator over the resolved imports (both implicit and explicit) of the graph. + /// + /// The iterator returns the name, the `ItemKind`, and an optional node id for explicit imports. + pub fn imports(&self) -> impl Iterator)> { + let mut imports = Vec::new(); + for index in self.graph.node_indices() { + let node = &self.graph[index]; + if !matches!(node.kind, NodeKind::Instantiation(_)) { + continue; + } + + let package = &self[node.package.unwrap()]; + let world = &self.types[package.ty()]; + + let unsatisfied_args = world + .imports + .iter() + .enumerate() + .filter(|(i, _)| !node.is_arg_satisfied(*i)); + + // Go through the unsatisfied arguments and import them + for (_, (name, item_kind)) in unsatisfied_args { + imports.push((name.as_str(), *item_kind, None)); + } + } + + for n in self.node_ids() { + let node = &self.graph[n.0]; + if let NodeKind::Import(name) = &node.kind { + imports.push((name.as_str(), node.item_kind, Some(n))); + } + } + imports.into_iter() + } } impl Index for CompositionGraph { @@ -1489,18 +1524,60 @@ impl<'a> CompositionGraphEncoder<'a> { } /// Encode both implicit and explicit imports. + /// + /// `import_nodes` are expected to only be `NodeKind::Import` nodes. fn encode_imports( &self, state: &mut State, import_nodes: Vec, ) -> Result<(), EncodeError> { - let mut aggregator = TypeAggregator::default(); - let mut instantiations = HashMap::new(); - let mut arguments = Vec::new(); + let mut explicit_imports = HashMap::new(); + let mut implicit_imports = Vec::new(); + let aggregator = + self.resolve_imports(import_nodes, &mut implicit_imports, &mut explicit_imports)?; + let mut encoded = HashMap::new(); + + // Next encode the imports + for (name, kind) in aggregator.imports() { + log::debug!("import `{name}` is being imported"); + let index = self.import(state, name, aggregator.types(), kind); + encoded.insert(name, (kind.into(), index)); + } + + // Populate the implicit argument map + for (name, node) in implicit_imports { + let (kind, index) = encoded[name]; + state + .implicit_args + .entry(node) + .or_default() + .push((name.to_owned(), kind, index)); + } + + // Finally, populate the node indexes with the encoded explicit imports + for (name, node_index) in explicit_imports { + let (_, encoded_index) = encoded[name]; + state.node_indexes.insert(node_index, encoded_index); + } + + Ok(()) + } + + /// Resolves the imports (both implicit and explicit) of the given nodes. + /// + /// Populates hashmaps that map the implicit and explicit import nodes to their import names. + /// Returns a type aggregator that contains the resolved types of the imports. + fn resolve_imports( + &'a self, + import_nodes: Vec, + implicit_imports: &mut Vec<(&'a str, NodeIndex)>, + explicit_imports: &mut HashMap<&'a str, NodeIndex>, + ) -> Result { + let mut instantiations = HashMap::new(); + let mut aggregator = TypeAggregator::default(); let mut cache = Default::default(); let mut checker = SubtypeChecker::new(&mut cache); - let mut explicit_imports = HashMap::new(); log::debug!("populating implicit imports"); @@ -1541,7 +1618,7 @@ impl<'a> CompositionGraphEncoder<'a> { second: NodeId(index), source: e, })?; - arguments.push((index, name)); + implicit_imports.push((name, index)); } } @@ -1556,31 +1633,7 @@ impl<'a> CompositionGraphEncoder<'a> { .unwrap(); } } - - // Next encode the imports - for (name, kind) in aggregator.imports() { - log::debug!("import `{name}` is being imported"); - let index = self.import(state, name, aggregator.types(), kind); - encoded.insert(name, (kind.into(), index)); - } - - // Populate the implicit argument map - for (node, name) in arguments { - let (kind, index) = encoded[name.as_str()]; - state - .implicit_args - .entry(node) - .or_default() - .push((name.clone(), kind, index)); - } - - // Finally, populate the node indexes with the encoded explicit imports - for (name, node_index) in explicit_imports { - let (_, encoded_index) = encoded[name]; - state.node_indexes.insert(node_index, encoded_index); - } - - Ok(()) + Ok(aggregator) } fn definition(&self, state: &mut State, node: &Node) -> u32 { diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index a5371443..a88612ca 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -609,7 +609,7 @@ pub enum Error { world: String, /// The span where the error occurred. #[label(primary, "cannot have an import named `{name}`")] - span: SourceSpan, + span: Option, }, /// Missing an export for the target world. #[error("target world `{world}` requires an export named `{name}`")] @@ -635,7 +635,7 @@ pub enum Error { world: String, /// The span where the error occurred. #[label(primary, "mismatched type for {kind} `{name}`")] - span: SourceSpan, + span: Option, /// The source of the error. #[source] source: anyhow::Error, @@ -2712,40 +2712,35 @@ impl<'a> AstResolver<'a> { world: WorldId, ) -> ResolutionResult<()> { let world = &state.graph.types()[world]; + // The interfaces imported implicitly through uses. + let implicit_imported_interfaces = world.implicit_imported_interfaces(state.graph.types()); let mut cache = Default::default(); let mut checker = SubtypeChecker::new(&mut cache); // The output is allowed to import a subset of the world's imports checker.invert(); - for (name, import) in state - .graph - .node_ids() - .filter_map(|n| match &state.graph[n].kind() { - NodeKind::Import(name) => Some((name, n)), - _ => None, - }) - { - let expected = world - .imports + for (name, item_kind, import_node) in state.graph.imports() { + let expected = implicit_imported_interfaces .get(name) + .or_else(|| world.imports.get(name)) .ok_or_else(|| Error::ImportNotInTarget { - name: name.clone(), + name: name.to_owned(), world: path.string.to_owned(), - span: state.import_spans[&import], + span: import_node.map(|n| state.import_spans[&n]), })?; checker .is_subtype( expected.promote(), state.graph.types(), - state.graph[import].item_kind(), + item_kind, state.graph.types(), ) .map_err(|e| Error::TargetMismatch { kind: ExternKind::Import, - name: name.clone(), + name: name.to_owned(), world: path.string.to_owned(), - span: state.import_spans[&import], + span: import_node.map(|n| state.import_spans[&n]), source: e, })?; } @@ -2776,7 +2771,7 @@ impl<'a> AstResolver<'a> { kind: ExternKind::Export, name: name.clone(), world: path.string.to_owned(), - span: state.export_spans[&export], + span: state.export_spans.get(&export).copied(), source: e, })?; } diff --git a/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac b/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac new file mode 100644 index 00000000..ca49423b --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac @@ -0,0 +1,22 @@ +package test:comp targets test:comp/foo; + +interface indirect-dependency { + variant my-variant { + foo, + // Extra variant that the instance does not have + bar + } +} + +interface direct-dependency { + use indirect-dependency.{my-variant}; + + fun: func() -> my-variant; +} + +world foo { + import direct-dependency; +} + + +let i = new foo:bar { ... }; diff --git a/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac.result b/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac.result new file mode 100644 index 00000000..ed618a67 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-interface-use.wac.result @@ -0,0 +1,5 @@ +failed to resolve document + + × import `test:comp/indirect-dependency` has a mismatched type for target world `test:comp/foo` + ├─▶ mismatched type for export `my-variant` + ╰─▶ expected a variant case count of 2, found a count of 1 diff --git a/crates/wac-parser/tests/resolution/fail/targets-interface-use/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/targets-interface-use/foo/bar.wat new file mode 100644 index 00000000..be48df94 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-interface-use/foo/bar.wat @@ -0,0 +1,19 @@ +(component + (type (;0;) + (instance + (type (;0;) (variant (case "foo"))) + (export (;1;) "my-variant" (type (eq 0))) + ) + ) + (import "test:comp/indirect-dependency" (instance (;0;) (type 0))) + (alias export 0 "my-variant" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "my-variant" (type (eq 0))) + (type (;2;) (func (result 1))) + (export (;0;) "fun" (func (type 2))) + ) + ) + (import "test:comp/direct-dependency" (instance (;1;) (type 2))) +) diff --git a/crates/wac-parser/tests/resolution/fail/targets-world-use.wac b/crates/wac-parser/tests/resolution/fail/targets-world-use.wac new file mode 100644 index 00000000..e4c95ed8 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-world-use.wac @@ -0,0 +1,13 @@ +package test:comp targets test:comp/foo; + +interface indirect-dependency { + // The resource is actually named "other-resource" in the instance + resource my-resource {} +} + +world foo { + use indirect-dependency.{my-resource}; + import my-func: func() -> my-resource; +} + +let i = new foo:bar { ... }; diff --git a/crates/wac-parser/tests/resolution/fail/targets-world-use.wac.result b/crates/wac-parser/tests/resolution/fail/targets-world-use.wac.result new file mode 100644 index 00000000..05a2db75 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-world-use.wac.result @@ -0,0 +1,4 @@ +failed to resolve document + + × import `test:comp/indirect-dependency` has a mismatched type for target world `test:comp/foo` + ╰─▶ instance has unexpected resource export `other-resource` diff --git a/crates/wac-parser/tests/resolution/fail/targets-world-use/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/targets-world-use/foo/bar.wat new file mode 100644 index 00000000..848bf460 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/targets-world-use/foo/bar.wat @@ -0,0 +1,14 @@ +(component + (type (;0;) + (instance + (export (;0;) "other-resource" (type (sub resource))) + ) + ) + (import "test:comp/indirect-dependency" (instance (;0;) (type 0))) + (alias export 0 "other-resource" (type (;1;))) + (import "my-resource" (type (;2;) (eq 1))) + (type (;3;) (own 2)) + (type (;4;) (func (result 3))) + (import "my-func" (func (;0;) (type 4))) + (alias export 0 "other-resource" (type (;5;))) +) diff --git a/crates/wac-types/src/component.rs b/crates/wac-types/src/component.rs index 6e2b2efd..54d7239c 100644 --- a/crates/wac-types/src/component.rs +++ b/crates/wac-types/src/component.rs @@ -983,6 +983,34 @@ impl World { Ok(()) } + + /// The interfaces imported implicitly through uses. + pub fn implicit_imported_interfaces<'a>( + &'a self, + types: &'a Types, + ) -> IndexMap<&str, ItemKind> { + let mut interfaces = IndexMap::new(); + let mut add_interface_for_used_type = |used_item: &UsedType| { + let used_interface_id = used_item.interface; + // The id must be set since used interfaces are always named. + let used_interface_name = types[used_interface_id].id.as_deref().unwrap(); + interfaces.insert(used_interface_name, ItemKind::Instance(used_interface_id)); + }; + + for (_, used_type) in self.uses.iter() { + add_interface_for_used_type(used_type); + } + + for (_, import) in self.imports.iter() { + if let ItemKind::Instance(interface_id) = import { + let import = &types[*interface_id]; + for (_, used_item) in &import.uses { + add_interface_for_used_type(used_item); + } + } + } + interfaces + } } /// Represents a kind of an extern item.