Skip to content

Commit 1d314b0

Browse files
authored
Merge pull request #130 from rylev/implicit-import-check
2 parents 290c100 + e27d652 commit 1d314b0

File tree

9 files changed

+201
-48
lines changed

9 files changed

+201
-48
lines changed

crates/wac-graph/src/graph.rs

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,41 @@ impl CompositionGraph {
12591259
generation: entry.generation,
12601260
}
12611261
}
1262+
1263+
/// Yields an iterator over the resolved imports (both implicit and explicit) of the graph.
1264+
///
1265+
/// The iterator returns the name, the `ItemKind`, and an optional node id for explicit imports.
1266+
pub fn imports(&self) -> impl Iterator<Item = (&str, ItemKind, Option<NodeId>)> {
1267+
let mut imports = Vec::new();
1268+
for index in self.graph.node_indices() {
1269+
let node = &self.graph[index];
1270+
if !matches!(node.kind, NodeKind::Instantiation(_)) {
1271+
continue;
1272+
}
1273+
1274+
let package = &self[node.package.unwrap()];
1275+
let world = &self.types[package.ty()];
1276+
1277+
let unsatisfied_args = world
1278+
.imports
1279+
.iter()
1280+
.enumerate()
1281+
.filter(|(i, _)| !node.is_arg_satisfied(*i));
1282+
1283+
// Go through the unsatisfied arguments and import them
1284+
for (_, (name, item_kind)) in unsatisfied_args {
1285+
imports.push((name.as_str(), *item_kind, None));
1286+
}
1287+
}
1288+
1289+
for n in self.node_ids() {
1290+
let node = &self.graph[n.0];
1291+
if let NodeKind::Import(name) = &node.kind {
1292+
imports.push((name.as_str(), node.item_kind, Some(n)));
1293+
}
1294+
}
1295+
imports.into_iter()
1296+
}
12621297
}
12631298

12641299
impl Index<NodeId> for CompositionGraph {
@@ -1489,18 +1524,60 @@ impl<'a> CompositionGraphEncoder<'a> {
14891524
}
14901525

14911526
/// Encode both implicit and explicit imports.
1527+
///
1528+
/// `import_nodes` are expected to only be `NodeKind::Import` nodes.
14921529
fn encode_imports(
14931530
&self,
14941531
state: &mut State,
14951532
import_nodes: Vec<NodeIndex>,
14961533
) -> Result<(), EncodeError> {
1497-
let mut aggregator = TypeAggregator::default();
1498-
let mut instantiations = HashMap::new();
1499-
let mut arguments = Vec::new();
1534+
let mut explicit_imports = HashMap::new();
1535+
let mut implicit_imports = Vec::new();
1536+
let aggregator =
1537+
self.resolve_imports(import_nodes, &mut implicit_imports, &mut explicit_imports)?;
1538+
15001539
let mut encoded = HashMap::new();
1540+
1541+
// Next encode the imports
1542+
for (name, kind) in aggregator.imports() {
1543+
log::debug!("import `{name}` is being imported");
1544+
let index = self.import(state, name, aggregator.types(), kind);
1545+
encoded.insert(name, (kind.into(), index));
1546+
}
1547+
1548+
// Populate the implicit argument map
1549+
for (name, node) in implicit_imports {
1550+
let (kind, index) = encoded[name];
1551+
state
1552+
.implicit_args
1553+
.entry(node)
1554+
.or_default()
1555+
.push((name.to_owned(), kind, index));
1556+
}
1557+
1558+
// Finally, populate the node indexes with the encoded explicit imports
1559+
for (name, node_index) in explicit_imports {
1560+
let (_, encoded_index) = encoded[name];
1561+
state.node_indexes.insert(node_index, encoded_index);
1562+
}
1563+
1564+
Ok(())
1565+
}
1566+
1567+
/// Resolves the imports (both implicit and explicit) of the given nodes.
1568+
///
1569+
/// Populates hashmaps that map the implicit and explicit import nodes to their import names.
1570+
/// Returns a type aggregator that contains the resolved types of the imports.
1571+
fn resolve_imports(
1572+
&'a self,
1573+
import_nodes: Vec<NodeIndex>,
1574+
implicit_imports: &mut Vec<(&'a str, NodeIndex)>,
1575+
explicit_imports: &mut HashMap<&'a str, NodeIndex>,
1576+
) -> Result<TypeAggregator, EncodeError> {
1577+
let mut instantiations = HashMap::new();
1578+
let mut aggregator = TypeAggregator::default();
15011579
let mut cache = Default::default();
15021580
let mut checker = SubtypeChecker::new(&mut cache);
1503-
let mut explicit_imports = HashMap::new();
15041581

15051582
log::debug!("populating implicit imports");
15061583

@@ -1541,7 +1618,7 @@ impl<'a> CompositionGraphEncoder<'a> {
15411618
second: NodeId(index),
15421619
source: e,
15431620
})?;
1544-
arguments.push((index, name));
1621+
implicit_imports.push((name, index));
15451622
}
15461623
}
15471624

@@ -1556,31 +1633,7 @@ impl<'a> CompositionGraphEncoder<'a> {
15561633
.unwrap();
15571634
}
15581635
}
1559-
1560-
// Next encode the imports
1561-
for (name, kind) in aggregator.imports() {
1562-
log::debug!("import `{name}` is being imported");
1563-
let index = self.import(state, name, aggregator.types(), kind);
1564-
encoded.insert(name, (kind.into(), index));
1565-
}
1566-
1567-
// Populate the implicit argument map
1568-
for (node, name) in arguments {
1569-
let (kind, index) = encoded[name.as_str()];
1570-
state
1571-
.implicit_args
1572-
.entry(node)
1573-
.or_default()
1574-
.push((name.clone(), kind, index));
1575-
}
1576-
1577-
// Finally, populate the node indexes with the encoded explicit imports
1578-
for (name, node_index) in explicit_imports {
1579-
let (_, encoded_index) = encoded[name];
1580-
state.node_indexes.insert(node_index, encoded_index);
1581-
}
1582-
1583-
Ok(())
1636+
Ok(aggregator)
15841637
}
15851638

15861639
fn definition(&self, state: &mut State, node: &Node) -> u32 {

crates/wac-parser/src/resolution.rs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ pub enum Error {
609609
world: String,
610610
/// The span where the error occurred.
611611
#[label(primary, "cannot have an import named `{name}`")]
612-
span: SourceSpan,
612+
span: Option<SourceSpan>,
613613
},
614614
/// Missing an export for the target world.
615615
#[error("target world `{world}` requires an export named `{name}`")]
@@ -635,7 +635,7 @@ pub enum Error {
635635
world: String,
636636
/// The span where the error occurred.
637637
#[label(primary, "mismatched type for {kind} `{name}`")]
638-
span: SourceSpan,
638+
span: Option<SourceSpan>,
639639
/// The source of the error.
640640
#[source]
641641
source: anyhow::Error,
@@ -2712,40 +2712,35 @@ impl<'a> AstResolver<'a> {
27122712
world: WorldId,
27132713
) -> ResolutionResult<()> {
27142714
let world = &state.graph.types()[world];
2715+
// The interfaces imported implicitly through uses.
2716+
let implicit_imported_interfaces = world.implicit_imported_interfaces(state.graph.types());
27152717
let mut cache = Default::default();
27162718
let mut checker = SubtypeChecker::new(&mut cache);
27172719

27182720
// The output is allowed to import a subset of the world's imports
27192721
checker.invert();
2720-
for (name, import) in state
2721-
.graph
2722-
.node_ids()
2723-
.filter_map(|n| match &state.graph[n].kind() {
2724-
NodeKind::Import(name) => Some((name, n)),
2725-
_ => None,
2726-
})
2727-
{
2728-
let expected = world
2729-
.imports
2722+
for (name, item_kind, import_node) in state.graph.imports() {
2723+
let expected = implicit_imported_interfaces
27302724
.get(name)
2725+
.or_else(|| world.imports.get(name))
27312726
.ok_or_else(|| Error::ImportNotInTarget {
2732-
name: name.clone(),
2727+
name: name.to_owned(),
27332728
world: path.string.to_owned(),
2734-
span: state.import_spans[&import],
2729+
span: import_node.map(|n| state.import_spans[&n]),
27352730
})?;
27362731

27372732
checker
27382733
.is_subtype(
27392734
expected.promote(),
27402735
state.graph.types(),
2741-
state.graph[import].item_kind(),
2736+
item_kind,
27422737
state.graph.types(),
27432738
)
27442739
.map_err(|e| Error::TargetMismatch {
27452740
kind: ExternKind::Import,
2746-
name: name.clone(),
2741+
name: name.to_owned(),
27472742
world: path.string.to_owned(),
2748-
span: state.import_spans[&import],
2743+
span: import_node.map(|n| state.import_spans[&n]),
27492744
source: e,
27502745
})?;
27512746
}
@@ -2776,7 +2771,7 @@ impl<'a> AstResolver<'a> {
27762771
kind: ExternKind::Export,
27772772
name: name.clone(),
27782773
world: path.string.to_owned(),
2779-
span: state.export_spans[&export],
2774+
span: state.export_spans.get(&export).copied(),
27802775
source: e,
27812776
})?;
27822777
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package test:comp targets test:comp/foo;
2+
3+
interface indirect-dependency {
4+
variant my-variant {
5+
foo,
6+
// Extra variant that the instance does not have
7+
bar
8+
}
9+
}
10+
11+
interface direct-dependency {
12+
use indirect-dependency.{my-variant};
13+
14+
fun: func() -> my-variant;
15+
}
16+
17+
world foo {
18+
import direct-dependency;
19+
}
20+
21+
22+
let i = new foo:bar { ... };
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
failed to resolve document
2+
3+
× import `test:comp/indirect-dependency` has a mismatched type for target world `test:comp/foo`
4+
├─▶ mismatched type for export `my-variant`
5+
╰─▶ expected a variant case count of 2, found a count of 1
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(component
2+
(type (;0;)
3+
(instance
4+
(type (;0;) (variant (case "foo")))
5+
(export (;1;) "my-variant" (type (eq 0)))
6+
)
7+
)
8+
(import "test:comp/indirect-dependency" (instance (;0;) (type 0)))
9+
(alias export 0 "my-variant" (type (;1;)))
10+
(type (;2;)
11+
(instance
12+
(alias outer 1 1 (type (;0;)))
13+
(export (;1;) "my-variant" (type (eq 0)))
14+
(type (;2;) (func (result 1)))
15+
(export (;0;) "fun" (func (type 2)))
16+
)
17+
)
18+
(import "test:comp/direct-dependency" (instance (;1;) (type 2)))
19+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package test:comp targets test:comp/foo;
2+
3+
interface indirect-dependency {
4+
// The resource is actually named "other-resource" in the instance
5+
resource my-resource {}
6+
}
7+
8+
world foo {
9+
use indirect-dependency.{my-resource};
10+
import my-func: func() -> my-resource;
11+
}
12+
13+
let i = new foo:bar { ... };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
failed to resolve document
2+
3+
× import `test:comp/indirect-dependency` has a mismatched type for target world `test:comp/foo`
4+
╰─▶ instance has unexpected resource export `other-resource`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(component
2+
(type (;0;)
3+
(instance
4+
(export (;0;) "other-resource" (type (sub resource)))
5+
)
6+
)
7+
(import "test:comp/indirect-dependency" (instance (;0;) (type 0)))
8+
(alias export 0 "other-resource" (type (;1;)))
9+
(import "my-resource" (type (;2;) (eq 1)))
10+
(type (;3;) (own 2))
11+
(type (;4;) (func (result 3)))
12+
(import "my-func" (func (;0;) (type 4)))
13+
(alias export 0 "other-resource" (type (;5;)))
14+
)

crates/wac-types/src/component.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,34 @@ impl World {
983983

984984
Ok(())
985985
}
986+
987+
/// The interfaces imported implicitly through uses.
988+
pub fn implicit_imported_interfaces<'a>(
989+
&'a self,
990+
types: &'a Types,
991+
) -> IndexMap<&str, ItemKind> {
992+
let mut interfaces = IndexMap::new();
993+
let mut add_interface_for_used_type = |used_item: &UsedType| {
994+
let used_interface_id = used_item.interface;
995+
// The id must be set since used interfaces are always named.
996+
let used_interface_name = types[used_interface_id].id.as_deref().unwrap();
997+
interfaces.insert(used_interface_name, ItemKind::Instance(used_interface_id));
998+
};
999+
1000+
for (_, used_type) in self.uses.iter() {
1001+
add_interface_for_used_type(used_type);
1002+
}
1003+
1004+
for (_, import) in self.imports.iter() {
1005+
if let ItemKind::Instance(interface_id) = import {
1006+
let import = &types[*interface_id];
1007+
for (_, used_item) in &import.uses {
1008+
add_interface_for_used_type(used_item);
1009+
}
1010+
}
1011+
}
1012+
interfaces
1013+
}
9861014
}
9871015

9881016
/// Represents a kind of an extern item.

0 commit comments

Comments
 (0)