Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 83 additions & 30 deletions crates/wac-graph/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = (&str, ItemKind, Option<NodeId>)> {
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<NodeId> for CompositionGraph {
Expand Down Expand Up @@ -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<NodeIndex>,
) -> 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<NodeIndex>,
implicit_imports: &mut Vec<(&'a str, NodeIndex)>,
explicit_imports: &mut HashMap<&'a str, NodeIndex>,
) -> Result<TypeAggregator, EncodeError> {
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");

Expand Down Expand Up @@ -1541,7 +1618,7 @@ impl<'a> CompositionGraphEncoder<'a> {
second: NodeId(index),
source: e,
})?;
arguments.push((index, name));
implicit_imports.push((name, index));
}
}

Expand All @@ -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 {
Expand Down
31 changes: 13 additions & 18 deletions crates/wac-parser/src/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SourceSpan>,
},
/// Missing an export for the target world.
#[error("target world `{world}` requires an export named `{name}`")]
Expand All @@ -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<SourceSpan>,
/// The source of the error.
#[source]
source: anyhow::Error,
Expand Down Expand Up @@ -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,
})?;
}
Expand Down Expand Up @@ -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,
})?;
}
Expand Down
22 changes: 22 additions & 0 deletions crates/wac-parser/tests/resolution/fail/targets-interface-use.wac
Original file line number Diff line number Diff line change
@@ -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 { ... };
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)))
)
13 changes: 13 additions & 0 deletions crates/wac-parser/tests/resolution/fail/targets-world-use.wac
Original file line number Diff line number Diff line change
@@ -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 { ... };
Original file line number Diff line number Diff line change
@@ -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`
Original file line number Diff line number Diff line change
@@ -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;)))
)
28 changes: 28 additions & 0 deletions crates/wac-types/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down