Skip to content

Commit b7860be

Browse files
committed
Merge branch 'main' of github.com:bytecodealliance/wac into fixed-size-lists
2 parents d2f883d + 6a88b29 commit b7860be

5 files changed

Lines changed: 189 additions & 124 deletions

File tree

Cargo.lock

Lines changed: 27 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ clap = { version = "4.5.4", features = ["derive"] }
7575
semver = { version = "1.0.22", features = ["serde"] }
7676
pretty_env_logger = "0.5.0"
7777
log = "0.4.21"
78-
tokio = { version = "1.37.0", default-features = false, features = [
78+
tokio = { version = "1.45.1", default-features = false, features = [
7979
"macros",
8080
"rt-multi-thread",
8181
] }

crates/wac-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ mod checker;
77
mod component;
88
mod core;
99
mod package;
10+
mod targets;
1011

1112
pub use aggregator::*;
1213
pub use checker::*;
1314
pub use component::*;
1415
pub use core::*;
1516
pub use package::*;
17+
pub use targets::*;

crates/wac-types/src/targets.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use crate::{ExternKind, ItemKind, SubtypeChecker, Types, WorldId};
2+
use std::collections::{BTreeMap, BTreeSet};
3+
use std::fmt;
4+
5+
/// The result of validating a component against a target world.
6+
pub type TargetValidationResult = Result<(), TargetValidationReport>;
7+
8+
/// The report of validating a component against a target world.
9+
#[derive(Debug, Default)]
10+
pub struct TargetValidationReport {
11+
/// Imports present in the component but not in the target world.
12+
imports_not_in_target: BTreeSet<String>,
13+
/// Exports not in the component but required by the target world.
14+
///
15+
/// This is a mapping from the name of the missing export to the expected item kind.
16+
missing_exports: BTreeMap<String, ItemKind>,
17+
/// Mismatched types between the component and the target world.
18+
///
19+
/// This is a mapping from name of the mismatched item to a tuple of
20+
/// the extern kind of the item and type error.
21+
mismatched_types: BTreeMap<String, (ExternKind, anyhow::Error)>,
22+
}
23+
24+
impl From<TargetValidationReport> for TargetValidationResult {
25+
fn from(report: TargetValidationReport) -> TargetValidationResult {
26+
if report.imports_not_in_target.is_empty()
27+
&& report.missing_exports.is_empty()
28+
&& report.mismatched_types.is_empty()
29+
{
30+
Ok(())
31+
} else {
32+
Err(report)
33+
}
34+
}
35+
}
36+
37+
impl fmt::Display for TargetValidationReport {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
if !self.imports_not_in_target.is_empty() {
40+
writeln!(
41+
f,
42+
"Imports present in the component but not in the target world:"
43+
)?;
44+
for import in &self.imports_not_in_target {
45+
writeln!(f, " - {}", import)?;
46+
}
47+
}
48+
if !self.missing_exports.is_empty() {
49+
writeln!(
50+
f,
51+
"Exports required by target world but not present in the component:"
52+
)?;
53+
for (name, item_kind) in &self.missing_exports {
54+
writeln!(f, " - {}: {:?}", name, item_kind)?;
55+
}
56+
}
57+
if !self.mismatched_types.is_empty() {
58+
writeln!(
59+
f,
60+
"Type mismatches between the target world and the component:"
61+
)?;
62+
for (name, (kind, error)) in &self.mismatched_types {
63+
writeln!(f, " - {}: {:?} ({})", name, kind, error)?;
64+
}
65+
}
66+
Ok(())
67+
}
68+
}
69+
70+
impl std::error::Error for TargetValidationReport {}
71+
72+
impl TargetValidationReport {
73+
/// Returns the set of imports present in the component but not in the target world.
74+
pub fn imports_not_in_target(&self) -> impl Iterator<Item = &str> {
75+
self.imports_not_in_target.iter().map(|s| s.as_str())
76+
}
77+
78+
/// Returns the exports not in the component but required by the target world.
79+
pub fn missing_exports(&self) -> impl Iterator<Item = (&str, &ItemKind)> {
80+
self.missing_exports.iter().map(|(s, k)| (s.as_str(), k))
81+
}
82+
83+
/// Returns the mismatched types between the component and the target world.
84+
pub fn mismatched_types(&self) -> impl Iterator<Item = (&str, &ExternKind, &anyhow::Error)> {
85+
self.mismatched_types
86+
.iter()
87+
.map(|(s, (k, e))| (s.as_str(), k, e))
88+
}
89+
}
90+
91+
/// Validate whether the component conforms to the given world.
92+
///
93+
/// # Example
94+
///
95+
/// ```ignore
96+
/// let mut types = Types::default();
97+
///
98+
/// let mut resolve = wit_parser::Resolve::new();
99+
/// let pkg = resolve.push_dir(path_to_wit_dir)?.0;
100+
/// let wit_bytes = wit_component::encode(&resolve, pkg)?;
101+
/// let wit = Package::from_bytes("wit", None, wit_bytes, &mut types)?;
102+
///
103+
/// let component_bytes = std::fs::read(path_to_component)?;
104+
/// let component = Package::from_bytes("component", None, component_bytes, &mut types)?;
105+
/// let wit_world = get_wit_world(&types, wit.ty())?;
106+
///
107+
/// validate_target(&types, wit_world, component.ty())?;
108+
/// ```
109+
pub fn validate_target(
110+
types: &Types,
111+
wit_world_id: WorldId,
112+
component_world_id: WorldId,
113+
) -> TargetValidationResult {
114+
let component_world = &types[component_world_id];
115+
let wit_world = &types[wit_world_id];
116+
// The interfaces imported implicitly through uses.
117+
let implicit_imported_interfaces = wit_world.implicit_imported_interfaces(types);
118+
let mut cache = Default::default();
119+
let mut checker = SubtypeChecker::new(&mut cache);
120+
121+
let mut report = TargetValidationReport::default();
122+
123+
// The output is allowed to import a subset of the world's imports
124+
checker.invert();
125+
for (import, item_kind) in component_world.imports.iter() {
126+
let Some(expected) = implicit_imported_interfaces
127+
.get(import.as_str())
128+
.or_else(|| wit_world.imports.get(import))
129+
else {
130+
report.imports_not_in_target.insert(import.to_owned());
131+
continue;
132+
};
133+
134+
if let Err(e) = checker.is_subtype(expected.promote(), types, *item_kind, types) {
135+
report
136+
.mismatched_types
137+
.insert(import.to_owned(), (ExternKind::Import, e));
138+
}
139+
}
140+
141+
checker.revert();
142+
143+
// The output must export every export in the world
144+
for (name, expected) in &wit_world.exports {
145+
let Some(export) = component_world.exports.get(name).copied() else {
146+
report.missing_exports.insert(name.to_owned(), *expected);
147+
continue;
148+
};
149+
150+
if let Err(e) = checker.is_subtype(export, types, expected.promote(), types) {
151+
report
152+
.mismatched_types
153+
.insert(name.to_owned(), (ExternKind::Export, e));
154+
}
155+
}
156+
157+
report.into()
158+
}

0 commit comments

Comments
 (0)