Skip to content

Commit

Permalink
Merge pull request #130 from rylev/implicit-import-check
Browse files Browse the repository at this point in the history
  • Loading branch information
rylev authored Jul 9, 2024
2 parents 290c100 + e27d652 commit 1d314b0
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 48 deletions.
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

0 comments on commit 1d314b0

Please sign in to comment.