Skip to content

Commit fb7ca26

Browse files
authored
Merge pull request #33 from peterhuene/targets
Implement the `targets` clause on the package directive.
2 parents a37a32e + edd161e commit fb7ca26

34 files changed

Lines changed: 543 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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,46 @@ 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("target world `{world}` does not have an import named `{name}`")]
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, "cannot have an import named `{name}`")]
693+
span: SourceSpan,
694+
},
695+
/// Missing an export for the target world.
696+
#[error("target world `{world}` requires an export named `{name}`")]
697+
MissingTargetExport {
698+
/// The export name.
699+
name: String,
700+
/// The expected item kind.
701+
kind: String,
702+
/// The target world.
703+
world: String,
704+
/// The span where the error occurred.
705+
#[label(primary, "must export a {kind} named `{name}` to target this world")]
706+
span: SourceSpan,
707+
},
708+
/// An import or export has a mismatched type for the target world.
709+
#[error("{kind} `{name}` has a mismatched type for target world `{world}`")]
710+
TargetMismatch {
711+
/// The kind of mismatch.
712+
kind: ExternKind,
713+
/// The mismatched extern name.
714+
name: String,
715+
/// The target world.
716+
world: String,
717+
/// The span where the error occurred.
718+
#[label(primary, "mismatched type for {kind} `{name}`")]
719+
span: SourceSpan,
720+
/// The source of the error.
721+
#[source]
722+
source: anyhow::Error,
723+
},
684724
}
685725

686726
/// Represents a resolution result.
@@ -732,6 +772,17 @@ impl ItemKind {
732772
ItemKind::Value(_) => "value",
733773
}
734774
}
775+
776+
/// Promote function types, instance types, and component types
777+
/// to functions, instances, and components
778+
fn promote(&self) -> Self {
779+
match *self {
780+
ItemKind::Type(Type::Func(id)) => ItemKind::Func(id),
781+
ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id),
782+
ItemKind::Type(Type::World(id)) => ItemKind::Component(id),
783+
kind => kind,
784+
}
785+
}
735786
}
736787

737788
/// Represents a item defining a type.

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

Lines changed: 90 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,68 @@ 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+
kind: expected.as_str(&self.definitions).to_string(),
2309+
span: path.span,
2310+
})?;
2311+
2312+
checker
2313+
.is_subtype(
2314+
expected.promote(),
2315+
state.root_scope().items[export.item].kind(),
2316+
)
2317+
.map_err(|e| Error::TargetMismatch {
2318+
kind: ExternKind::Export,
2319+
name: name.clone(),
2320+
world: path.string.to_owned(),
2321+
span: export.span,
2322+
source: e,
2323+
})?;
2324+
}
2325+
2326+
Ok(())
2327+
}
22512328
}

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)