Skip to content

Commit 4371a22

Browse files
committed
Implement the targets clause on the package directive.
This commit implements the `targets` clause on the package directive. When this clause is present, the parser will validate that the output composition is capable of targeting the world, otherwise it will emit an error. Closes #20.
1 parent a37a32e commit 4371a22

34 files changed

Lines changed: 540 additions & 73 deletions

LANGUAGE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ statement ::= import-statement
409409
| let-statement
410410
| export-statement
411411
412-
package-decl ::= `package` package-name `;`
412+
package-decl ::= `package` package-name (`targets` package-path)? `;`
413413
package-name ::= id (':' id)+ ('@' version)?
414414
version ::= <SEMVER>
415415

crates/wac-parser/src/ast.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ pub(crate) trait Parse<'a>: Sized {
256256
fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self>;
257257
}
258258

259+
trait Peek {
260+
fn peek(lookahead: &mut Lookahead) -> bool;
261+
}
262+
259263
fn parse_delimited<'a, T: Parse<'a> + Peek>(
260264
lexer: &mut Lexer<'a>,
261265
until: Token,
@@ -288,8 +292,25 @@ fn parse_delimited<'a, T: Parse<'a> + Peek>(
288292
Ok(items)
289293
}
290294

291-
trait Peek {
292-
fn peek(lookahead: &mut Lookahead) -> bool;
295+
/// Represents a package directive in the AST.
296+
#[derive(Debug, Clone, Serialize)]
297+
#[serde(rename_all = "camelCase")]
298+
pub struct PackageDirective<'a> {
299+
/// The name of the package named by the directive.
300+
pub package: PackageName<'a>,
301+
/// The optional world being targeted by the package.
302+
#[serde(skip_serializing_if = "Option::is_none")]
303+
pub targets: Option<PackagePath<'a>>,
304+
}
305+
306+
impl<'a> Parse<'a> for PackageDirective<'a> {
307+
fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
308+
parse_token(lexer, Token::PackageKeyword)?;
309+
let package = Parse::parse(lexer)?;
310+
let targets = parse_optional(lexer, Token::TargetsKeyword, Parse::parse)?;
311+
parse_token(lexer, Token::Semicolon)?;
312+
Ok(Self { package, targets })
313+
}
293314
}
294315

295316
/// Represents a top-level WAC document.
@@ -298,8 +319,8 @@ trait Peek {
298319
pub struct Document<'a> {
299320
/// The doc comments for the package.
300321
pub docs: Vec<DocComment<'a>>,
301-
/// The package name of the document.
302-
pub package: PackageName<'a>,
322+
/// The package directive of the document.
323+
pub directive: PackageDirective<'a>,
303324
/// The statements in the document.
304325
pub statements: Vec<Statement<'a>>,
305326
}
@@ -312,10 +333,7 @@ impl<'a> Document<'a> {
312333
let mut lexer = Lexer::new(source).map_err(Error::from)?;
313334

314335
let docs = Parse::parse(&mut lexer)?;
315-
316-
parse_token(&mut lexer, Token::PackageKeyword)?;
317-
let package = Parse::parse(&mut lexer)?;
318-
parse_token(&mut lexer, Token::Semicolon)?;
336+
let directive = Parse::parse(&mut lexer)?;
319337

320338
let mut statements: Vec<Statement> = Default::default();
321339
while lexer.peek().is_some() {
@@ -325,7 +343,7 @@ impl<'a> Document<'a> {
325343
assert!(lexer.next().is_none(), "expected all tokens to be consumed");
326344
Ok(Self {
327345
docs,
328-
package,
346+
directive,
329347
statements,
330348
})
331349
}

crates/wac-parser/src/ast/printer.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ impl<'a, W: Write> DocumentPrinter<'a, W> {
3030
/// Prints the given document.
3131
pub fn document(&mut self, doc: &Document) -> std::fmt::Result {
3232
self.docs(&doc.docs)?;
33-
writeln!(
34-
self.writer,
35-
"package {package};",
36-
package = self.source(doc.package.span),
37-
)?;
38-
self.newline()?;
33+
self.package_directive(&doc.directive)?;
3934

4035
for (i, statement) in doc.statements.iter().enumerate() {
4136
if i > 0 {
@@ -49,6 +44,24 @@ impl<'a, W: Write> DocumentPrinter<'a, W> {
4944
Ok(())
5045
}
5146

47+
/// Prints the given package directive.
48+
pub fn package_directive(&mut self, directive: &PackageDirective) -> std::fmt::Result {
49+
self.indent()?;
50+
write!(
51+
self.writer,
52+
"package {package}",
53+
package = self.source(directive.package.span),
54+
)?;
55+
56+
if let Some(targets) = &directive.targets {
57+
write!(self.writer, " ")?;
58+
self.package_path(targets)?;
59+
}
60+
61+
writeln!(self.writer, ";")?;
62+
self.newline()
63+
}
64+
5265
/// Prints the given statement.
5366
pub fn statement(&mut self, statement: &Statement) -> std::fmt::Result {
5467
match statement {

crates/wac-parser/src/lexer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ pub enum Token {
235235
/// The `package` keyword.
236236
#[token("package")]
237237
PackageKeyword,
238+
/// The `targets` keyword.
239+
#[token("targets")]
240+
TargetsKeyword,
238241

239242
/// The `;` symbol.
240243
#[token(";")]
@@ -338,6 +341,7 @@ impl fmt::Display for Token {
338341
Token::IncludeKeyword => write!(f, "`include` keyword"),
339342
Token::AsKeyword => write!(f, "`as` keyword"),
340343
Token::PackageKeyword => write!(f, "`package` keyword"),
344+
Token::TargetsKeyword => write!(f, "`targets` keyword"),
341345
Token::Semicolon => write!(f, "`;`"),
342346
Token::OpenBrace => write!(f, "`{{`"),
343347
Token::CloseBrace => write!(f, "`}}`"),
@@ -827,6 +831,8 @@ let
827831
use
828832
include
829833
as
834+
package
835+
targets
830836
"#,
831837
&[
832838
(Ok(Token::ImportKeyword), "import", 1..7),
@@ -866,6 +872,8 @@ as
866872
(Ok(Token::UseKeyword), "use", 203..206),
867873
(Ok(Token::IncludeKeyword), "include", 207..214),
868874
(Ok(Token::AsKeyword), "as", 215..217),
875+
(Ok(Token::PackageKeyword), "package", 218..225),
876+
(Ok(Token::TargetsKeyword), "targets", 226..233),
869877
],
870878
);
871879
}

crates/wac-parser/src/resolution.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,44 @@ pub enum Error {
681681
#[label(primary, "spreading the exports of this instance has no effect")]
682682
span: SourceSpan,
683683
},
684+
/// An import is not in the target world.
685+
#[error("import `{name}` is not present in target world `{world}`")]
686+
ImportNotInTarget {
687+
/// The import name.
688+
name: String,
689+
/// The target world.
690+
world: String,
691+
/// The span where the error occurred.
692+
#[label(primary, "import `{name}` is not in the target world")]
693+
span: SourceSpan,
694+
},
695+
/// Missing an export for the target world.
696+
#[error("missing export `{name}` for target world `{world}`")]
697+
MissingTargetExport {
698+
/// The export name.
699+
name: String,
700+
/// The target world.
701+
world: String,
702+
/// The span where the error occurred.
703+
#[label(primary, "must export `{name}` to target this world")]
704+
span: SourceSpan,
705+
},
706+
/// An import or export has a mismatched type for the target world.
707+
#[error("{kind} `{name}` has a mismatched type for target world `{world}`")]
708+
TargetMismatch {
709+
/// The kind of mismatch.
710+
kind: ExternKind,
711+
/// The mismatched extern name.
712+
name: String,
713+
/// The target world.
714+
world: String,
715+
/// The span where the error occurred.
716+
#[label(primary, "mismatched type for {kind} `{name}`")]
717+
span: SourceSpan,
718+
/// The source of the error.
719+
#[source]
720+
source: anyhow::Error,
721+
},
684722
}
685723

686724
/// Represents a resolution result.
@@ -732,6 +770,17 @@ impl ItemKind {
732770
ItemKind::Value(_) => "value",
733771
}
734772
}
773+
774+
/// Promote function types, instance types, and component types
775+
/// to functions, instances, and components
776+
fn promote(&self) -> Self {
777+
match *self {
778+
ItemKind::Type(Type::Func(id)) => ItemKind::Func(id),
779+
ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id),
780+
ItemKind::Type(Type::World(id)) => ItemKind::Component(id),
781+
kind => kind,
782+
}
783+
}
735784
}
736785

737786
/// Represents a item defining a type.

crates/wac-parser/src/resolution/ast.rs

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,29 @@ impl<'a> AstResolver<'a> {
181181
}
182182
}
183183

184+
// If there's a target world in the directive, validate the composition
185+
// conforms to the target
186+
if let Some(path) = &self.document.directive.targets {
187+
let item = self.resolve_package_export(&mut state, path)?;
188+
match item {
189+
ItemKind::Type(Type::World(world)) => {
190+
self.validate_target(&state, path, world)?;
191+
}
192+
_ => {
193+
return Err(Error::NotWorld {
194+
name: path.string.to_owned(),
195+
kind: item.as_str(&self.definitions).to_owned(),
196+
span: path.span,
197+
});
198+
}
199+
}
200+
}
201+
184202
assert!(state.scopes.is_empty());
185203

186204
Ok(Composition {
187-
package: self.document.package.name.to_owned(),
188-
version: self.document.package.version.clone(),
205+
package: self.document.directive.package.name.to_owned(),
206+
version: self.document.directive.package.version.clone(),
189207
definitions: self.definitions,
190208
packages: state.packages,
191209
items: state.current.items,
@@ -227,13 +245,8 @@ impl<'a> AstResolver<'a> {
227245
ast::ImportType::Ident(id) => (state.local_item(id)?.1.kind(), stmt.id.span),
228246
};
229247

230-
// Promote function types, instance types, and component types to functions, instances, and components
231-
let kind = match kind {
232-
ItemKind::Type(Type::Func(id)) => ItemKind::Func(id),
233-
ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id),
234-
ItemKind::Type(Type::World(id)) => ItemKind::Component(id),
235-
_ => kind,
236-
};
248+
// Promote any types to their corresponding item kind
249+
let kind = kind.promote();
237250

238251
let (name, span) = if let Some(name) = &stmt.name {
239252
// Override the span to the `as` clause string
@@ -505,8 +518,8 @@ impl<'a> AstResolver<'a> {
505518
fn id(&self, name: &str) -> String {
506519
format!(
507520
"{pkg}/{name}{version}",
508-
pkg = self.document.package.name,
509-
version = if let Some(version) = &self.document.package.version {
521+
pkg = self.document.directive.package.name,
522+
version = if let Some(version) = &self.document.directive.package.version {
510523
format!("@{version}")
511524
} else {
512525
String::new()
@@ -1551,7 +1564,7 @@ impl<'a> AstResolver<'a> {
15511564
path: &'a ast::PackagePath<'a>,
15521565
) -> ResolutionResult<ItemKind> {
15531566
// Check for reference to local item
1554-
if path.name == self.document.package.name {
1567+
if path.name == self.document.directive.package.name {
15551568
return self.resolve_local_export(state, path);
15561569
}
15571570

@@ -1704,7 +1717,7 @@ impl<'a> AstResolver<'a> {
17041717
state: &mut State<'a>,
17051718
expr: &'a ast::NewExpr<'a>,
17061719
) -> ResolutionResult<ItemId> {
1707-
if expr.package.name == self.document.package.name {
1720+
if expr.package.name == self.document.directive.package.name {
17081721
return Err(Error::UnknownPackage {
17091722
name: expr.package.name.to_string(),
17101723
span: expr.package.span,
@@ -2248,4 +2261,67 @@ impl<'a> AstResolver<'a> {
22482261
aliases.insert(name.to_owned(), id);
22492262
Ok(Some(id))
22502263
}
2264+
2265+
fn validate_target(
2266+
&self,
2267+
state: &State,
2268+
path: &ast::PackagePath,
2269+
world: WorldId,
2270+
) -> ResolutionResult<()> {
2271+
let world = &self.definitions.worlds[world];
2272+
2273+
let mut checker = SubtypeChecker::new(&self.definitions, &state.packages);
2274+
2275+
// The output is allowed to import a subset of the world's imports
2276+
for (name, import) in &state.imports {
2277+
let expected = world
2278+
.imports
2279+
.get(name)
2280+
.ok_or_else(|| Error::ImportNotInTarget {
2281+
name: name.clone(),
2282+
world: path.string.to_owned(),
2283+
span: import.span,
2284+
})?;
2285+
2286+
checker
2287+
.is_subtype(
2288+
expected.promote(),
2289+
state.root_scope().items[import.item].kind(),
2290+
)
2291+
.map_err(|e| Error::TargetMismatch {
2292+
kind: ExternKind::Import,
2293+
name: name.clone(),
2294+
world: path.string.to_owned(),
2295+
span: import.span,
2296+
source: e,
2297+
})?;
2298+
}
2299+
2300+
// The output must export every export in the world
2301+
for (name, expected) in &world.exports {
2302+
let export = state
2303+
.exports
2304+
.get(name)
2305+
.ok_or_else(|| Error::MissingTargetExport {
2306+
name: name.clone(),
2307+
world: path.string.to_owned(),
2308+
span: path.span,
2309+
})?;
2310+
2311+
checker
2312+
.is_subtype(
2313+
expected.promote(),
2314+
state.root_scope().items[export.item].kind(),
2315+
)
2316+
.map_err(|e| Error::TargetMismatch {
2317+
kind: ExternKind::Export,
2318+
name: name.clone(),
2319+
world: path.string.to_owned(),
2320+
span: export.span,
2321+
source: e,
2322+
})?;
2323+
}
2324+
2325+
Ok(())
2326+
}
22512327
}

crates/wac-parser/tests/parser/export.wac.result

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"docs": [],
3-
"package": {
4-
"string": "test:comp",
5-
"name": "test:comp",
6-
"version": null,
7-
"span": {
8-
"offset": 8,
9-
"length": 9
3+
"directive": {
4+
"package": {
5+
"string": "test:comp",
6+
"name": "test:comp",
7+
"version": null,
8+
"span": {
9+
"offset": 8,
10+
"length": 9
11+
}
1012
}
1113
},
1214
"statements": [

0 commit comments

Comments
 (0)