feat: add a histrical wit-bindgen

This commit is contained in:
2023-01-01 00:25:48 +08:00
parent 01e8f5a959
commit aa50d63aec
419 changed files with 45283 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
target
Cargo.lock

View File

@@ -0,0 +1,22 @@
[package]
name = "wit-parser"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[dependencies]
id-arena = "2"
anyhow = "1.0"
pulldown-cmark = { version = "0.8", default-features = false }
wast = { version = "33", default-features = false, optional = true }
unicode-xid = "0.2.2"
unicode-normalization = "0.1.19"
[dev-dependencies]
rayon = "1"
serde_json = "1"
serde = { version = "1", features = ['derive'] }
[[test]]
name = "all"
harness = false

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,711 @@
use anyhow::Result;
use lex::{Span, Token, Tokenizer};
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
mod lex;
mod resolve;
pub use lex::validate_id;
pub struct Ast<'a> {
pub items: Vec<Item<'a>>,
}
pub enum Item<'a> {
Use(Use<'a>),
Resource(Resource<'a>),
TypeDef(TypeDef<'a>),
Value(Value<'a>),
Interface(Interface<'a>),
}
pub struct Id<'a> {
pub name: Cow<'a, str>,
pub span: Span,
}
impl<'a> From<&'a str> for Id<'a> {
fn from(s: &'a str) -> Id<'a> {
Id {
name: s.into(),
span: Span { start: 0, end: 0 },
}
}
}
impl<'a> From<String> for Id<'a> {
fn from(s: String) -> Id<'a> {
Id {
name: s.into(),
span: Span { start: 0, end: 0 },
}
}
}
pub struct Use<'a> {
pub from: Vec<Id<'a>>,
names: Option<Vec<UseName<'a>>>,
}
struct UseName<'a> {
name: Id<'a>,
as_: Option<Id<'a>>,
}
pub struct Resource<'a> {
docs: Docs<'a>,
name: Id<'a>,
supertype: Option<Id<'a>>,
values: Vec<(bool, Value<'a>)>,
}
#[derive(Default)]
struct Docs<'a> {
docs: Vec<Cow<'a, str>>,
}
pub struct TypeDef<'a> {
docs: Docs<'a>,
name: Id<'a>,
ty: Type<'a>,
}
enum Type<'a> {
Unit,
Bool,
U8,
U16,
U32,
U64,
S8,
S16,
S32,
S64,
Float32,
Float64,
Char,
String,
Handle(Id<'a>),
Name(Id<'a>),
List(Box<Type<'a>>),
Record(Record<'a>),
Flags(Flags<'a>),
Variant(Variant<'a>),
Tuple(Vec<Type<'a>>),
Enum(Enum<'a>),
Option(Box<Type<'a>>),
Expected(Expected<'a>),
Future(Box<Type<'a>>),
Stream(Stream<'a>),
Union(Union<'a>),
}
struct Record<'a> {
fields: Vec<Field<'a>>,
}
struct Field<'a> {
docs: Docs<'a>,
name: Id<'a>,
ty: Type<'a>,
}
struct Flags<'a> {
flags: Vec<Flag<'a>>,
}
struct Flag<'a> {
docs: Docs<'a>,
name: Id<'a>,
}
struct Variant<'a> {
span: Span,
cases: Vec<Case<'a>>,
}
struct Case<'a> {
docs: Docs<'a>,
name: Id<'a>,
ty: Option<Type<'a>>,
}
struct Enum<'a> {
span: Span,
cases: Vec<EnumCase<'a>>,
}
struct EnumCase<'a> {
docs: Docs<'a>,
name: Id<'a>,
}
struct Expected<'a> {
ok: Box<Type<'a>>,
err: Box<Type<'a>>,
}
struct Stream<'a> {
element: Box<Type<'a>>,
end: Box<Type<'a>>,
}
pub struct Value<'a> {
docs: Docs<'a>,
name: Id<'a>,
kind: ValueKind<'a>,
}
struct Union<'a> {
span: Span,
cases: Vec<UnionCase<'a>>,
}
struct UnionCase<'a> {
docs: Docs<'a>,
ty: Type<'a>,
}
enum ValueKind<'a> {
Function {
is_async: bool,
params: Vec<(Id<'a>, Type<'a>)>,
result: Type<'a>,
},
Global(Type<'a>),
}
#[allow(dead_code)] // TODO
pub struct Interface<'a> {
docs: Docs<'a>,
name: Id<'a>,
items: Vec<Item<'a>>,
}
impl<'a> Ast<'a> {
pub fn parse(input: &'a str) -> Result<Ast<'a>> {
let mut lexer = Tokenizer::new(input)?;
let mut items = Vec::new();
while lexer.clone().next()?.is_some() {
let docs = parse_docs(&mut lexer)?;
items.push(Item::parse(&mut lexer, docs)?);
}
Ok(Ast { items })
}
pub fn resolve(
&self,
name: &str,
map: &HashMap<String, crate::Interface>,
) -> Result<crate::Interface> {
let mut resolver = resolve::Resolver::default();
let instance = resolver.resolve(name, &self.items, map)?;
Ok(instance)
}
}
impl<'a> Item<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Item<'a>> {
match tokens.clone().next()? {
Some((_span, Token::Use)) => Use::parse(tokens, docs).map(Item::Use),
Some((_span, Token::Type)) => TypeDef::parse(tokens, docs).map(Item::TypeDef),
Some((_span, Token::Flags)) => TypeDef::parse_flags(tokens, docs).map(Item::TypeDef),
Some((_span, Token::Enum)) => TypeDef::parse_enum(tokens, docs).map(Item::TypeDef),
Some((_span, Token::Variant)) => {
TypeDef::parse_variant(tokens, docs).map(Item::TypeDef)
}
Some((_span, Token::Record)) => TypeDef::parse_record(tokens, docs).map(Item::TypeDef),
Some((_span, Token::Union)) => TypeDef::parse_union(tokens, docs).map(Item::TypeDef),
Some((_span, Token::Resource)) => Resource::parse(tokens, docs).map(Item::Resource),
Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Item::Interface),
Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => {
Value::parse(tokens, docs).map(Item::Value)
}
other => Err(err_expected(tokens, "`type`, `resource`, or `func`", other).into()),
}
}
}
impl<'a> Use<'a> {
fn parse(tokens: &mut Tokenizer<'a>, _docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Use)?;
let mut names = None;
loop {
if names.is_none() {
if tokens.eat(Token::Star)? {
break;
}
tokens.expect(Token::LeftBrace)?;
names = Some(Vec::new());
}
let names = names.as_mut().unwrap();
let mut name = UseName {
name: parse_id(tokens)?,
as_: None,
};
if tokens.eat(Token::As)? {
name.as_ = Some(parse_id(tokens)?);
}
names.push(name);
if !tokens.eat(Token::Comma)? {
break;
}
}
if names.is_some() {
tokens.expect(Token::RightBrace)?;
}
tokens.expect(Token::From_)?;
let mut from = vec![parse_id(tokens)?];
while tokens.eat(Token::Colon)? {
tokens.expect_raw(Token::Colon)?;
from.push(parse_id(tokens)?);
}
Ok(Use { from, names })
}
}
impl<'a> TypeDef<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Type)?;
let name = parse_id(tokens)?;
tokens.expect(Token::Equals)?;
let ty = Type::parse(tokens)?;
Ok(TypeDef { docs, name, ty })
}
fn parse_flags(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Flags)?;
let name = parse_id(tokens)?;
let ty = Type::Flags(Flags {
flags: parse_list(
tokens,
Token::LeftBrace,
Token::RightBrace,
|docs, tokens| {
let name = parse_id(tokens)?;
Ok(Flag { docs, name })
},
)?,
});
Ok(TypeDef { docs, name, ty })
}
fn parse_record(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Record)?;
let name = parse_id(tokens)?;
let ty = Type::Record(Record {
fields: parse_list(
tokens,
Token::LeftBrace,
Token::RightBrace,
|docs, tokens| {
let name = parse_id(tokens)?;
tokens.expect(Token::Colon)?;
let ty = Type::parse(tokens)?;
Ok(Field { docs, name, ty })
},
)?,
});
Ok(TypeDef { docs, name, ty })
}
fn parse_variant(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Variant)?;
let name = parse_id(tokens)?;
let ty = Type::Variant(Variant {
span: name.span,
cases: parse_list(
tokens,
Token::LeftBrace,
Token::RightBrace,
|docs, tokens| {
let name = parse_id(tokens)?;
let ty = if tokens.eat(Token::LeftParen)? {
let ty = Type::parse(tokens)?;
tokens.expect(Token::RightParen)?;
Some(ty)
} else {
None
};
Ok(Case { docs, name, ty })
},
)?,
});
Ok(TypeDef { docs, name, ty })
}
fn parse_union(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Union)?;
let name = parse_id(tokens)?;
let ty = Type::Union(Union {
span: name.span,
cases: parse_list(
tokens,
Token::LeftBrace,
Token::RightBrace,
|docs, tokens| {
let ty = Type::parse(tokens)?;
Ok(UnionCase { docs, ty })
},
)?,
});
Ok(TypeDef { docs, name, ty })
}
fn parse_enum(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Enum)?;
let name = parse_id(tokens)?;
let ty = Type::Enum(Enum {
span: name.span,
cases: parse_list(
tokens,
Token::LeftBrace,
Token::RightBrace,
|docs, tokens| {
let name = parse_id(tokens)?;
Ok(EnumCase { docs, name })
},
)?,
});
Ok(TypeDef { docs, name, ty })
}
}
impl<'a> Resource<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Resource)?;
let name = parse_id(tokens)?;
let supertype = if tokens.eat(Token::Implements)? {
Some(parse_id(tokens)?)
} else {
None
};
let mut values = Vec::new();
if tokens.eat(Token::LeftBrace)? {
loop {
let docs = parse_docs(tokens)?;
if tokens.eat(Token::RightBrace)? {
break;
}
let statik = tokens.eat(Token::Static)?;
values.push((statik, Value::parse(tokens, docs)?));
}
}
Ok(Resource {
docs,
name,
supertype,
values,
})
}
}
impl<'a> Value<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
let name = parse_id(tokens)?;
tokens.expect(Token::Colon)?;
let kind = if tokens.eat(Token::Func)? {
parse_func(tokens, false)?
} else if tokens.eat(Token::Async)? {
tokens.expect(Token::Func)?;
parse_func(tokens, true)?
} else {
ValueKind::Global(Type::parse(tokens)?)
};
return Ok(Value { docs, name, kind });
fn parse_func<'a>(tokens: &mut Tokenizer<'a>, is_async: bool) -> Result<ValueKind<'a>> {
let params = parse_list(
tokens,
Token::LeftParen,
Token::RightParen,
|_docs, tokens| {
let name = parse_id(tokens)?;
tokens.expect(Token::Colon)?;
let ty = Type::parse(tokens)?;
Ok((name, ty))
},
)?;
let result = if tokens.eat(Token::RArrow)? {
Type::parse(tokens)?
} else {
Type::Unit
};
Ok(ValueKind::Function {
is_async,
params,
result,
})
}
}
}
fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<Id<'a>> {
match tokens.next()? {
Some((span, Token::Id)) => Ok(Id {
name: tokens.parse_id(span)?.into(),
span,
}),
Some((span, Token::ExplicitId)) => Ok(Id {
name: tokens.parse_explicit_id(span)?.into(),
span,
}),
other => Err(err_expected(tokens, "an identifier or string", other).into()),
}
}
fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result<Docs<'a>> {
let mut docs = Docs::default();
let mut clone = tokens.clone();
while let Some((span, token)) = clone.next_raw()? {
match token {
Token::Whitespace => {}
Token::Comment => docs.docs.push(tokens.get_span(span).into()),
_ => break,
};
*tokens = clone.clone();
}
Ok(docs)
}
impl<'a> Type<'a> {
fn parse(tokens: &mut Tokenizer<'a>) -> Result<Self> {
match tokens.next()? {
Some((_span, Token::U8)) => Ok(Type::U8),
Some((_span, Token::U16)) => Ok(Type::U16),
Some((_span, Token::U32)) => Ok(Type::U32),
Some((_span, Token::U64)) => Ok(Type::U64),
Some((_span, Token::S8)) => Ok(Type::S8),
Some((_span, Token::S16)) => Ok(Type::S16),
Some((_span, Token::S32)) => Ok(Type::S32),
Some((_span, Token::S64)) => Ok(Type::S64),
Some((_span, Token::Float32)) => Ok(Type::Float32),
Some((_span, Token::Float64)) => Ok(Type::Float64),
Some((_span, Token::Char)) => Ok(Type::Char),
Some((_span, Token::Handle)) => {
let name = parse_id(tokens)?;
Ok(Type::Handle(name))
}
// tuple<T, U, ...>
Some((_span, Token::Tuple)) => {
let types = parse_list(
tokens,
Token::LessThan,
Token::GreaterThan,
|_docs, tokens| Type::parse(tokens),
)?;
Ok(Type::Tuple(types))
}
Some((_span, Token::Unit)) => Ok(Type::Unit),
Some((_span, Token::Bool)) => Ok(Type::Bool),
Some((_span, Token::String_)) => Ok(Type::String),
// list<T>
Some((_span, Token::List)) => {
tokens.expect(Token::LessThan)?;
let ty = Type::parse(tokens)?;
tokens.expect(Token::GreaterThan)?;
Ok(Type::List(Box::new(ty)))
}
// option<T>
Some((_span, Token::Option_)) => {
tokens.expect(Token::LessThan)?;
let ty = Type::parse(tokens)?;
tokens.expect(Token::GreaterThan)?;
Ok(Type::Option(Box::new(ty)))
}
// expected<T, E>
Some((_span, Token::Expected)) => {
tokens.expect(Token::LessThan)?;
let ok = Box::new(Type::parse(tokens)?);
tokens.expect(Token::Comma)?;
let err = Box::new(Type::parse(tokens)?);
tokens.expect(Token::GreaterThan)?;
Ok(Type::Expected(Expected { ok, err }))
}
// future<T>
Some((_span, Token::Future)) => {
tokens.expect(Token::LessThan)?;
let ty = Box::new(Type::parse(tokens)?);
tokens.expect(Token::GreaterThan)?;
Ok(Type::Future(ty))
}
// stream<T, Z>
Some((_span, Token::Stream)) => {
tokens.expect(Token::LessThan)?;
let element = Box::new(Type::parse(tokens)?);
tokens.expect(Token::Comma)?;
let end = Box::new(Type::parse(tokens)?);
tokens.expect(Token::GreaterThan)?;
Ok(Type::Stream(Stream { element, end }))
}
// `foo`
Some((span, Token::Id)) => Ok(Type::Name(Id {
name: tokens.parse_id(span)?.into(),
span,
})),
// `@foo`
Some((span, Token::ExplicitId)) => Ok(Type::Name(Id {
name: tokens.parse_explicit_id(span)?.into(),
span,
})),
other => Err(err_expected(tokens, "a type", other).into()),
}
}
}
impl<'a> Interface<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
tokens.expect(Token::Interface)?;
let name = parse_id(tokens)?;
tokens.expect(Token::LeftBrace)?;
let mut items = Vec::new();
loop {
let docs = parse_docs(tokens)?;
if tokens.eat(Token::RightBrace)? {
break;
}
items.push(Item::parse(tokens, docs)?);
}
Ok(Interface { docs, name, items })
}
}
fn parse_list<'a, T>(
tokens: &mut Tokenizer<'a>,
start: Token,
end: Token,
mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result<T>,
) -> Result<Vec<T>> {
tokens.expect(start)?;
let mut items = Vec::new();
loop {
// get docs before we skip them to try to eat the end token
let docs = parse_docs(tokens)?;
// if we found an end token then we're done
if tokens.eat(end)? {
break;
}
let item = parse(docs, tokens)?;
items.push(item);
// if there's no trailing comma then this is required to be the end,
// otherwise we go through the loop to try to get another item
if !tokens.eat(Token::Comma)? {
tokens.expect(end)?;
break;
}
}
Ok(items)
}
fn err_expected(
tokens: &Tokenizer<'_>,
expected: &'static str,
found: Option<(Span, Token)>,
) -> Error {
match found {
Some((span, token)) => Error {
span,
msg: format!("expected {}, found {}", expected, token.describe()),
},
None => Error {
span: Span {
start: u32::try_from(tokens.input().len()).unwrap(),
end: u32::try_from(tokens.input().len()).unwrap(),
},
msg: format!("expected {}, found eof", expected),
},
}
}
#[derive(Debug)]
struct Error {
span: Span,
msg: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.msg.fmt(f)
}
}
impl std::error::Error for Error {}
pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) {
let parse = match err.downcast_mut::<Error>() {
Some(err) => err,
None => return lex::rewrite_error(err, file, contents),
};
let msg = highlight_err(
parse.span.start as usize,
Some(parse.span.end as usize),
file,
contents,
&parse.msg,
);
*err = anyhow::anyhow!("{}", msg);
}
fn highlight_err(
start: usize,
end: Option<usize>,
file: &str,
input: &str,
err: impl fmt::Display,
) -> String {
let (line, col) = linecol_in(start, input);
let snippet = input.lines().nth(line).unwrap_or("");
let mut msg = format!(
"\
{err}
--> {file}:{line}:{col}
|
{line:4} | {snippet}
| {marker:>0$}",
col + 1,
file = file,
line = line + 1,
col = col + 1,
err = err,
snippet = snippet,
marker = "^",
);
if let Some(end) = end {
if let Some(s) = input.get(start..end) {
for _ in s.chars().skip(1) {
msg.push('-');
}
}
}
return msg;
fn linecol_in(pos: usize, text: &str) -> (usize, usize) {
let mut cur = 0;
// Use split_terminator instead of lines so that if there is a `\r`,
// it is included in the offset calculation. The `+1` values below
// account for the `\n`.
for (i, line) in text.split_terminator('\n').enumerate() {
if cur + line.len() + 1 > pos {
return (i, pos - cur);
}
cur += line.len() + 1;
}
(text.lines().count(), 0)
}
}

View File

@@ -0,0 +1,709 @@
use anyhow::{bail, Result};
use std::char;
use std::convert::TryFrom;
use std::fmt;
use std::str;
use unicode_normalization::char::canonical_combining_class;
use unicode_xid::UnicodeXID;
use self::Token::*;
#[derive(Clone)]
pub struct Tokenizer<'a> {
input: &'a str,
chars: CrlfFold<'a>,
}
#[derive(Clone)]
struct CrlfFold<'a> {
chars: str::CharIndices<'a>,
}
/// A span, designating a range of bytes where a token is located.
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct Span {
/// The start of the range.
pub start: u32,
/// The end of the range (exclusive).
pub end: u32,
}
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum Token {
Whitespace,
Comment,
Equals,
Comma,
Colon,
Semicolon,
LeftParen,
RightParen,
LeftBrace,
RightBrace,
LessThan,
GreaterThan,
RArrow,
Star,
Use,
Type,
Resource,
Func,
U8,
U16,
U32,
U64,
S8,
S16,
S32,
S64,
Float32,
Float64,
Char,
Handle,
Record,
Flags,
Variant,
Enum,
Union,
Bool,
String_,
Option_,
Expected,
Future,
Stream,
List,
Underscore,
As,
From_,
Static,
Interface,
Tuple,
Async,
Unit,
Implements,
Id,
ExplicitId,
}
#[derive(Eq, PartialEq, Debug)]
#[allow(dead_code)]
pub enum Error {
InvalidCharInString(usize, char),
InvalidCharInId(usize, char),
IdNotSSNFC(usize),
IdPartEmpty(usize),
InvalidEscape(usize, char),
// InvalidHexEscape(usize, char),
// InvalidEscapeValue(usize, u32),
Unexpected(usize, char),
UnterminatedComment(usize),
UnterminatedString(usize),
NewlineInString(usize),
Wanted {
at: usize,
expected: &'static str,
found: &'static str,
},
}
impl<'a> Tokenizer<'a> {
pub fn new(input: &'a str) -> Result<Tokenizer<'a>> {
detect_invalid_input(input)?;
let mut t = Tokenizer {
input,
chars: CrlfFold {
chars: input.char_indices(),
},
};
// Eat utf-8 BOM
t.eatc('\u{feff}');
Ok(t)
}
pub fn input(&self) -> &'a str {
self.input
}
pub fn get_span(&self, span: Span) -> &'a str {
&self.input[span.start as usize..span.end as usize]
}
pub fn parse_id(&self, span: Span) -> Result<String> {
let ret = self.get_span(span).to_owned();
validate_id(span.start as usize, &ret)?;
Ok(ret)
}
pub fn parse_explicit_id(&self, span: Span) -> Result<String> {
let token = self.get_span(span);
let id_part = token.strip_prefix('%').unwrap();
validate_id(span.start as usize, id_part)?;
Ok(id_part.to_owned())
}
pub fn next(&mut self) -> Result<Option<(Span, Token)>, Error> {
loop {
match self.next_raw()? {
Some((_, Token::Whitespace)) | Some((_, Token::Comment)) => {}
other => break Ok(other),
}
}
}
pub fn next_raw(&mut self) -> Result<Option<(Span, Token)>, Error> {
let (start, ch) = match self.chars.next() {
Some(pair) => pair,
None => return Ok(None),
};
let token = match ch {
'\n' | '\t' | ' ' => {
// Eat all contiguous whitespace tokens
while self.eatc(' ') || self.eatc('\t') || self.eatc('\n') {}
Whitespace
}
'/' => {
// Eat a line comment if it's `//...`
if self.eatc('/') {
for (_, ch) in &mut self.chars {
if ch == '\n' {
break;
}
}
// eat a block comment if it's `/*...`
} else if self.eatc('*') {
let mut depth = 1;
while depth > 0 {
let (_, ch) = match self.chars.next() {
Some(pair) => pair,
None => return Err(Error::UnterminatedComment(start)),
};
match ch {
'/' if self.eatc('*') => depth += 1,
'*' if self.eatc('/') => depth -= 1,
_ => {}
}
}
} else {
return Err(Error::Unexpected(start, ch));
}
Comment
}
'=' => Equals,
',' => Comma,
':' => Colon,
';' => Semicolon,
'(' => LeftParen,
')' => RightParen,
'{' => LeftBrace,
'}' => RightBrace,
'<' => LessThan,
'>' => GreaterThan,
'*' => Star,
'-' => {
if self.eatc('>') {
RArrow
} else {
return Err(Error::Unexpected(start, '-'));
}
}
'%' => {
let mut iter = self.chars.clone();
if let Some((_, ch)) = iter.next() {
if is_keylike_start(ch) {
self.chars = iter.clone();
while let Some((_, ch)) = iter.next() {
if !is_keylike_continue(ch) {
break;
}
self.chars = iter.clone();
}
}
}
ExplicitId
}
ch if is_keylike_start(ch) => {
let remaining = self.chars.chars.as_str().len();
let mut iter = self.chars.clone();
while let Some((_, ch)) = iter.next() {
if !is_keylike_continue(ch) {
break;
}
self.chars = iter.clone();
}
let end = start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len());
match &self.input[start..end] {
"use" => Use,
"type" => Type,
"resource" => Resource,
"func" => Func,
"u8" => U8,
"u16" => U16,
"u32" => U32,
"u64" => U64,
"s8" => S8,
"s16" => S16,
"s32" => S32,
"s64" => S64,
"float32" => Float32,
"float64" => Float64,
"char" => Char,
"handle" => Handle,
"record" => Record,
"flags" => Flags,
"variant" => Variant,
"enum" => Enum,
"union" => Union,
"bool" => Bool,
"string" => String_,
"option" => Option_,
"expected" => Expected,
"future" => Future,
"stream" => Stream,
"list" => List,
"_" => Underscore,
"as" => As,
"from" => From_,
"static" => Static,
"interface" => Interface,
"tuple" => Tuple,
"async" => Async,
"unit" => Unit,
"implements" => Implements,
_ => Id,
}
}
ch => return Err(Error::Unexpected(start, ch)),
};
let end = match self.chars.clone().next() {
Some((i, _)) => i,
None => self.input.len(),
};
let start = u32::try_from(start).unwrap();
let end = u32::try_from(end).unwrap();
Ok(Some((Span { start, end }, token)))
}
pub fn eat(&mut self, expected: Token) -> Result<bool, Error> {
let mut other = self.clone();
match other.next()? {
Some((_span, found)) if expected == found => {
*self = other;
Ok(true)
}
Some(_) => Ok(false),
None => Ok(false),
}
}
pub fn expect(&mut self, expected: Token) -> Result<Span, Error> {
match self.next()? {
Some((span, found)) => {
if expected == found {
Ok(span)
} else {
Err(Error::Wanted {
at: usize::try_from(span.start).unwrap(),
expected: expected.describe(),
found: found.describe(),
})
}
}
None => Err(Error::Wanted {
at: self.input.len(),
expected: expected.describe(),
found: "eof",
}),
}
}
pub fn expect_raw(&mut self, expected: Token) -> Result<Span, Error> {
match self.next_raw()? {
Some((span, found)) => {
if expected == found {
Ok(span)
} else {
Err(Error::Wanted {
at: usize::try_from(span.start).unwrap(),
expected: expected.describe(),
found: found.describe(),
})
}
}
None => Err(Error::Wanted {
at: self.input.len(),
expected: expected.describe(),
found: "eof",
}),
}
}
fn eatc(&mut self, ch: char) -> bool {
let mut iter = self.chars.clone();
match iter.next() {
Some((_, ch2)) if ch == ch2 => {
self.chars = iter;
true
}
_ => false,
}
}
}
impl<'a> Iterator for CrlfFold<'a> {
type Item = (usize, char);
fn next(&mut self) -> Option<(usize, char)> {
self.chars.next().map(|(i, c)| {
if c == '\r' {
let mut attempt = self.chars.clone();
if let Some((_, '\n')) = attempt.next() {
self.chars = attempt;
return (i, '\n');
}
}
(i, c)
})
}
}
fn detect_invalid_input(input: &str) -> Result<()> {
// Disallow specific codepoints.
let mut line = 1;
for ch in input.chars() {
match ch {
'\n' => line += 1,
'\r' | '\t' => {}
// Bidirectional override codepoints can be used to craft source code that
// appears to have a different meaning than its actual meaning. See
// [CVE-2021-42574] for background and motivation.
//
// [CVE-2021-42574]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574
'\u{202a}' | '\u{202b}' | '\u{202c}' | '\u{202d}' | '\u{202e}' | '\u{2066}'
| '\u{2067}' | '\u{2068}' | '\u{2069}' => {
bail!(
"Input contains bidirectional override codepoint {:?} at line {}",
ch.escape_unicode(),
line
);
}
// Disallow several characters which are deprecated or discouraged in Unicode.
//
// U+149 deprecated; see Unicode 13.0.0, sec. 7.1 Latin, Compatibility Digraphs.
// U+673 deprecated; see Unicode 13.0.0, sec. 9.2 Arabic, Additional Vowel Marks.
// U+F77 and U+F79 deprecated; see Unicode 13.0.0, sec. 13.4 Tibetan, Vowels.
// U+17A3 and U+17A4 deprecated, and U+17B4 and U+17B5 discouraged; see
// Unicode 13.0.0, sec. 16.4 Khmer, Characters Whose Use Is Discouraged.
'\u{149}' | '\u{673}' | '\u{f77}' | '\u{f79}' | '\u{17a3}' | '\u{17a4}'
| '\u{17b4}' | '\u{17b5}' => {
bail!(
"Codepoint {:?} at line {} is discouraged by Unicode",
ch.escape_unicode(),
line
);
}
// Disallow control codes other than the ones explicitly recognized above,
// so that viewing a wit file on a terminal doesn't have surprising side
// effects or appear to have a different meaning than its actual meaning.
ch if ch.is_control() => {
bail!("Control code '{}' at line {}", ch.escape_unicode(), line);
}
_ => {}
}
}
Ok(())
}
fn is_keylike_start(ch: char) -> bool {
// Lex any XID start, `_`, or '-'. These aren't all valid identifier chars,
// but we'll diagnose that after we've lexed the full string.
UnicodeXID::is_xid_start(ch) || ch == '_' || ch == '-'
}
fn is_keylike_continue(ch: char) -> bool {
// Lex any XID continue (which includes `_`) or '-'.
UnicodeXID::is_xid_continue(ch) || ch == '-'
}
pub fn validate_id(start: usize, id: &str) -> Result<(), Error> {
// Ids must be in stream-safe NFC.
if !unicode_normalization::is_nfc_stream_safe(&id) {
return Err(Error::IdNotSSNFC(start));
}
// IDs must have at least one part.
if id.is_empty() {
return Err(Error::IdPartEmpty(start));
}
// Ids consist of parts separated by '-'s.
for part in id.split("-") {
// Parts must be non-empty and start with a non-combining XID start.
match part.chars().next() {
None => return Err(Error::IdPartEmpty(start)),
Some(first) => {
// Require the first character of each part to be non-combining,
// so that if a source langauge uses `CamelCase`, they won't
// combine with the last character of the previous part.
if canonical_combining_class(first) != 0 {
return Err(Error::InvalidCharInId(start, first));
}
// Require the first character to be a XID start.
if !UnicodeXID::is_xid_start(first) {
return Err(Error::InvalidCharInId(start, first));
}
// TODO: Disallow values with 'Grapheme_Extend = Yes', to
// prevent them from combining with previous parts?
// TODO: Disallow values with 'Grapheme_Cluster_Break = SpacingMark'?
}
};
// Some XID values are not valid ID part values.
for ch in part.chars() {
// Disallow uppercase and underscore, so that identifiers
// consistently use `kebab-case`, and source languages can map
// identifiers according to their own conventions (which might use
// `CamelCase` or `snake_case` or something else) without worrying
// about collisions.
if ch.is_uppercase() || ch == '_' || !UnicodeXID::is_xid_continue(ch) {
return Err(Error::InvalidCharInId(start, ch));
}
}
}
Ok(())
}
impl Token {
pub fn describe(&self) -> &'static str {
match self {
Whitespace => "whitespace",
Comment => "a comment",
Equals => "'='",
Comma => "','",
Colon => "':'",
Semicolon => "';'",
LeftParen => "'('",
RightParen => "')'",
LeftBrace => "'{'",
RightBrace => "'}'",
LessThan => "'<'",
GreaterThan => "'>'",
Use => "keyword `use`",
Type => "keyword `type`",
Resource => "keyword `resource`",
Func => "keyword `func`",
U8 => "keyword `u8`",
U16 => "keyword `u16`",
U32 => "keyword `u32`",
U64 => "keyword `u64`",
S8 => "keyword `s8`",
S16 => "keyword `s16`",
S32 => "keyword `s32`",
S64 => "keyword `s64`",
Float32 => "keyword `float32`",
Float64 => "keyword `float64`",
Char => "keyword `char`",
Handle => "keyword `handle`",
Record => "keyword `record`",
Flags => "keyword `flags`",
Variant => "keyword `variant`",
Enum => "keyword `enum`",
Union => "keyword `union`",
Bool => "keyword `bool`",
String_ => "keyword `string`",
Option_ => "keyword `option`",
Expected => "keyword `expected`",
Future => "keyword `future`",
Stream => "keyword `stream`",
List => "keyword `list`",
Underscore => "keyword `_`",
Id => "an identifier",
ExplicitId => "an '%' identifier",
RArrow => "`->`",
Star => "`*`",
As => "keyword `as`",
From_ => "keyword `from`",
Static => "keyword `static`",
Interface => "keyword `interface`",
Tuple => "keyword `tuple`",
Async => "keyword `async`",
Unit => "keyword `unit`",
Implements => "keyword `implements`",
}
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Unexpected(_, ch) => write!(f, "unexpected character {:?}", ch),
Error::UnterminatedComment(_) => write!(f, "unterminated block comment"),
Error::Wanted {
expected, found, ..
} => write!(f, "expected {}, found {}", expected, found),
Error::UnterminatedString(_) => write!(f, "unterminated string literal"),
Error::NewlineInString(_) => write!(f, "newline in string literal"),
Error::InvalidCharInString(_, ch) => write!(f, "invalid character in string {:?}", ch),
Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {:?}", ch),
Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"),
Error::IdNotSSNFC(_) => write!(f, "identifiers must be in stream-safe NFC"),
Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {:?}", ch),
}
}
}
pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) {
let lex = match err.downcast_mut::<Error>() {
Some(err) => err,
None => return,
};
let pos = match lex {
Error::Unexpected(at, _)
| Error::UnterminatedComment(at)
| Error::Wanted { at, .. }
| Error::UnterminatedString(at)
| Error::NewlineInString(at)
| Error::InvalidCharInString(at, _)
| Error::InvalidCharInId(at, _)
| Error::IdNotSSNFC(at)
| Error::IdPartEmpty(at)
| Error::InvalidEscape(at, _) => *at,
};
let msg = super::highlight_err(pos, None, file, contents, lex);
*err = anyhow::anyhow!("{}", msg);
}
#[test]
fn test_validate_id() {
validate_id(0, "apple").unwrap();
validate_id(0, "apple-pear").unwrap();
validate_id(0, "apple-pear-grape").unwrap();
validate_id(0, "garçon").unwrap();
validate_id(0, "hühnervögel").unwrap();
validate_id(0, "москва").unwrap();
validate_id(0, "東京").unwrap();
validate_id(0, "東-京").unwrap();
validate_id(0, "garçon-hühnervögel-москва-東京").unwrap();
validate_id(0, "garçon-hühnervögel-москва-東-京").unwrap();
validate_id(0, "a0").unwrap();
validate_id(0, "a").unwrap();
validate_id(0, "a-a").unwrap();
validate_id(0, "bool").unwrap();
assert!(validate_id(0, "").is_err());
assert!(validate_id(0, "0").is_err());
assert!(validate_id(0, "%").is_err());
assert!(validate_id(0, "$").is_err());
assert!(validate_id(0, "0a").is_err());
assert!(validate_id(0, ".").is_err());
assert!(validate_id(0, "·").is_err());
assert!(validate_id(0, "a a").is_err());
assert!(validate_id(0, "_").is_err());
assert!(validate_id(0, "-").is_err());
assert!(validate_id(0, "a-").is_err());
assert!(validate_id(0, "-a").is_err());
assert!(validate_id(0, "Apple").is_err());
assert!(validate_id(0, "APPLE").is_err());
assert!(validate_id(0, "applE").is_err());
assert!(validate_id(0, "-apple-pear").is_err());
assert!(validate_id(0, "apple-pear-").is_err());
assert!(validate_id(0, "apple_pear").is_err());
assert!(validate_id(0, "apple.pear").is_err());
assert!(validate_id(0, "apple pear").is_err());
assert!(validate_id(0, "apple/pear").is_err());
assert!(validate_id(0, "apple|pear").is_err());
assert!(validate_id(0, "apple-Pear").is_err());
assert!(validate_id(0, "apple-0").is_err());
assert!(validate_id(0, "()()").is_err());
assert!(validate_id(0, "").is_err());
assert!(validate_id(0, "*").is_err());
assert!(validate_id(0, "apple\u{5f3}pear").is_err());
assert!(validate_id(0, "apple\u{200c}pear").is_err());
assert!(validate_id(0, "apple\u{200d}pear").is_err());
assert!(validate_id(0, "apple--pear").is_err());
assert!(validate_id(0, "_apple").is_err());
assert!(validate_id(0, "apple_").is_err());
assert!(validate_id(0, "_Znwj").is_err());
assert!(validate_id(0, "__i386").is_err());
assert!(validate_id(0, "__i386__").is_err());
assert!(validate_id(0, "ENOENT").is_err());
assert!(validate_id(0, "Москва").is_err());
assert!(validate_id(0, "garçon-hühnervögel-Москва-東京").is_err());
assert!(validate_id(0, "😼").is_err(), "non-identifier");
assert!(validate_id(0, "\u{212b}").is_err(), "not NFC");
}
#[test]
fn test_tokenizer() {
fn collect(s: &str) -> Result<Vec<Token>> {
let mut t = Tokenizer::new(s)?;
let mut tokens = Vec::new();
while let Some(token) = t.next()? {
tokens.push(token.1);
}
Ok(tokens)
}
assert_eq!(collect("").unwrap(), vec![]);
assert_eq!(collect("_").unwrap(), vec![Token::Underscore]);
assert_eq!(collect("apple").unwrap(), vec![Token::Id]);
assert_eq!(collect("apple-pear").unwrap(), vec![Token::Id]);
assert_eq!(collect("apple--pear").unwrap(), vec![Token::Id]);
assert_eq!(collect("apple-Pear").unwrap(), vec![Token::Id]);
assert_eq!(collect("apple-pear-grape").unwrap(), vec![Token::Id]);
assert_eq!(collect("apple pear").unwrap(), vec![Token::Id, Token::Id]);
assert_eq!(collect("_a_p_p_l_e_").unwrap(), vec![Token::Id]);
assert_eq!(collect("garçon").unwrap(), vec![Token::Id]);
assert_eq!(collect("hühnervögel").unwrap(), vec![Token::Id]);
assert_eq!(collect("москва").unwrap(), vec![Token::Id]);
assert_eq!(collect("東京").unwrap(), vec![Token::Id]);
assert_eq!(
collect("garçon-hühnervögel-москва-東京").unwrap(),
vec![Token::Id]
);
assert_eq!(collect("a0").unwrap(), vec![Token::Id]);
assert_eq!(collect("a").unwrap(), vec![Token::Id]);
assert_eq!(collect("%a").unwrap(), vec![Token::ExplicitId]);
assert_eq!(collect("%a-a").unwrap(), vec![Token::ExplicitId]);
assert_eq!(collect("%bool").unwrap(), vec![Token::ExplicitId]);
assert_eq!(collect("%").unwrap(), vec![Token::ExplicitId]);
assert_eq!(collect("func").unwrap(), vec![Token::Func]);
assert_eq!(
collect("a: func()").unwrap(),
vec![
Token::Id,
Token::Colon,
Token::Func,
Token::LeftParen,
Token::RightParen
]
);
assert!(collect("\u{149}").is_err(), "strongly discouraged");
assert!(collect("\u{673}").is_err(), "strongly discouraged");
assert!(collect("\u{17a3}").is_err(), "strongly discouraged");
assert!(collect("\u{17a4}").is_err(), "strongly discouraged");
assert!(collect("\u{202a}").is_err(), "bidirectional override");
assert!(collect("\u{2068}").is_err(), "bidirectional override");
assert!(collect("\u{0}").is_err(), "control code");
assert!(collect("\u{b}").is_err(), "control code");
assert!(collect("\u{c}").is_err(), "control code");
assert!(collect("\u{85}").is_err(), "control code");
}

View File

@@ -0,0 +1,751 @@
use super::{Error, Item, Span, Value, ValueKind};
use crate::*;
use anyhow::Result;
use std::collections::{HashMap, HashSet};
use std::mem;
#[derive(Default)]
pub struct Resolver {
type_lookup: HashMap<String, TypeId>,
types: Arena<TypeDef>,
resource_lookup: HashMap<String, ResourceId>,
resources_copied: HashMap<(String, ResourceId), ResourceId>,
types_copied: HashMap<(String, TypeId), TypeId>,
resources: Arena<Resource>,
anon_types: HashMap<Key, TypeId>,
functions: Vec<Function>,
globals: Vec<Global>,
}
#[derive(PartialEq, Eq, Hash)]
enum Key {
Variant(Vec<(String, Type)>),
Record(Vec<(String, Type)>),
Flags(Vec<String>),
Tuple(Vec<Type>),
Enum(Vec<String>),
List(Type),
Option(Type),
Expected(Type, Type),
Union(Vec<Type>),
Future(Type),
Stream(Type, Type),
}
impl Resolver {
pub(super) fn resolve(
&mut self,
name: &str,
fields: &[Item<'_>],
deps: &HashMap<String, Interface>,
) -> Result<Interface> {
// First pull in any names from our dependencies
self.process_use(fields, deps)?;
// ... then register our own names
self.register_names(fields)?;
// With all names registered we can now fully expand and translate all
// types.
for field in fields {
let t = match field {
Item::TypeDef(t) => t,
_ => continue,
};
let id = self.type_lookup[&*t.name.name];
let kind = self.resolve_type_def(&t.ty)?;
self.types.get_mut(id).unwrap().kind = kind;
}
// And finally we can resolve all type references in functions/globals
// and additionally validate that types thesmelves are not recursive
let mut valid_types = HashSet::new();
let mut visiting = HashSet::new();
for field in fields {
match field {
Item::Value(v) => self.resolve_value(v)?,
Item::Resource(r) => self.resolve_resource(r)?,
Item::TypeDef(t) => {
self.validate_type_not_recursive(
t.name.span,
self.type_lookup[&*t.name.name],
&mut visiting,
&mut valid_types,
)?;
}
_ => continue,
}
}
Ok(Interface {
name: name.to_string(),
module: None,
types: mem::take(&mut self.types),
type_lookup: mem::take(&mut self.type_lookup),
resources: mem::take(&mut self.resources),
resource_lookup: mem::take(&mut self.resource_lookup),
interface_lookup: Default::default(),
interfaces: Default::default(),
functions: mem::take(&mut self.functions),
globals: mem::take(&mut self.globals),
})
}
fn process_use<'a>(
&mut self,
fields: &[Item<'a>],
deps: &'a HashMap<String, Interface>,
) -> Result<()> {
for field in fields {
let u = match field {
Item::Use(u) => u,
_ => continue,
};
let mut dep = &deps[&*u.from[0].name];
let mut prev = &*u.from[0].name;
for name in u.from[1..].iter() {
dep = match dep.interface_lookup.get(&*name.name) {
Some(i) => &dep.interfaces[*i],
None => {
return Err(Error {
span: name.span,
msg: format!("`{}` not defined in `{}`", name.name, prev),
}
.into())
}
};
prev = &*name.name;
}
let mod_name = &u.from[0];
match &u.names {
Some(names) => {
for name in names {
let (my_name, span) = match &name.as_ {
Some(id) => (&id.name, id.span),
None => (&name.name.name, name.name.span),
};
let mut found = false;
if let Some(id) = dep.resource_lookup.get(&*name.name.name) {
let resource = self.copy_resource(&mod_name.name, dep, *id);
self.define_resource(my_name, span, resource)?;
found = true;
}
if let Some(id) = dep.type_lookup.get(&*name.name.name) {
let ty = self.copy_type_def(&mod_name.name, dep, *id);
self.define_type(my_name, span, ty)?;
found = true;
}
if !found {
return Err(Error {
span: name.name.span,
msg: "name not defined in submodule".to_string(),
}
.into());
}
}
}
None => {
for (id, resource) in dep.resources.iter() {
let id = self.copy_resource(&mod_name.name, dep, id);
self.define_resource(&resource.name, mod_name.span, id)?;
}
let mut names = dep.type_lookup.iter().collect::<Vec<_>>();
names.sort(); // produce a stable order by which to add names
for (name, id) in names {
let ty = self.copy_type_def(&mod_name.name, dep, *id);
self.define_type(name, mod_name.span, ty)?;
}
}
}
}
Ok(())
}
fn copy_resource(&mut self, dep_name: &str, dep: &Interface, r: ResourceId) -> ResourceId {
let resources = &mut self.resources;
*self
.resources_copied
.entry((dep_name.to_string(), r))
.or_insert_with(|| {
let r = &dep.resources[r];
let resource = Resource {
docs: r.docs.clone(),
name: r.name.clone(),
supertype: r.supertype.clone(),
foreign_module: Some(
r.foreign_module
.clone()
.unwrap_or_else(|| dep_name.to_string()),
),
};
resources.alloc(resource)
})
}
fn copy_type_def(&mut self, dep_name: &str, dep: &Interface, dep_id: TypeId) -> TypeId {
if let Some(id) = self.types_copied.get(&(dep_name.to_string(), dep_id)) {
return *id;
}
let ty = &dep.types[dep_id];
let ty = TypeDef {
docs: ty.docs.clone(),
name: ty.name.clone(),
foreign_module: Some(
ty.foreign_module
.clone()
.unwrap_or_else(|| dep_name.to_string()),
),
kind: match &ty.kind {
TypeDefKind::Type(t) => TypeDefKind::Type(self.copy_type(dep_name, dep, *t)),
TypeDefKind::Record(r) => TypeDefKind::Record(Record {
fields: r
.fields
.iter()
.map(|field| Field {
docs: field.docs.clone(),
name: field.name.clone(),
ty: self.copy_type(dep_name, dep, field.ty),
})
.collect(),
}),
TypeDefKind::Flags(f) => TypeDefKind::Flags(f.clone()),
TypeDefKind::Tuple(t) => TypeDefKind::Tuple(Tuple {
types: t
.types
.iter()
.map(|ty| self.copy_type(dep_name, dep, *ty))
.collect(),
}),
TypeDefKind::Variant(v) => TypeDefKind::Variant(Variant {
cases: v
.cases
.iter()
.map(|case| Case {
docs: case.docs.clone(),
name: case.name.clone(),
ty: self.copy_type(dep_name, dep, case.ty),
})
.collect(),
}),
TypeDefKind::Enum(e) => TypeDefKind::Enum(Enum {
cases: e.cases.clone(),
}),
TypeDefKind::List(t) => TypeDefKind::List(self.copy_type(dep_name, dep, *t)),
TypeDefKind::Option(t) => TypeDefKind::Option(self.copy_type(dep_name, dep, *t)),
TypeDefKind::Expected(e) => TypeDefKind::Expected(Expected {
ok: self.copy_type(dep_name, dep, e.ok),
err: self.copy_type(dep_name, dep, e.err),
}),
TypeDefKind::Union(u) => TypeDefKind::Union(Union {
cases: u
.cases
.iter()
.map(|c| UnionCase {
docs: c.docs.clone(),
ty: self.copy_type(dep_name, dep, c.ty),
})
.collect(),
}),
TypeDefKind::Future(t) => TypeDefKind::Future(self.copy_type(dep_name, dep, *t)),
TypeDefKind::Stream(e) => TypeDefKind::Stream(Stream {
element: self.copy_type(dep_name, dep, e.element),
end: self.copy_type(dep_name, dep, e.end),
}),
},
};
let id = self.types.alloc(ty);
self.types_copied.insert((dep_name.to_string(), dep_id), id);
id
}
fn copy_type(&mut self, dep_name: &str, dep: &Interface, ty: Type) -> Type {
match ty {
Type::Id(id) => Type::Id(self.copy_type_def(dep_name, dep, id)),
Type::Handle(id) => Type::Handle(self.copy_resource(dep_name, dep, id)),
other => other,
}
}
fn register_names(&mut self, fields: &[Item<'_>]) -> Result<()> {
let mut values = HashSet::new();
for field in fields {
match field {
Item::Resource(r) => {
let docs = self.docs(&r.docs);
let id = self.resources.alloc(Resource {
docs,
name: r.name.name.to_string(),
supertype: r
.supertype
.as_ref()
.map(|supertype| supertype.name.to_string()),
foreign_module: None,
});
self.define_resource(&r.name.name, r.name.span, id)?;
let type_id = self.types.alloc(TypeDef {
docs: Docs::default(),
kind: TypeDefKind::Type(Type::Handle(id)),
name: None,
foreign_module: None,
});
self.define_type(&r.name.name, r.name.span, type_id)?;
}
Item::TypeDef(t) => {
let docs = self.docs(&t.docs);
let id = self.types.alloc(TypeDef {
docs,
// a dummy kind is used for now which will get filled in
// later with the actual desired contents.
kind: TypeDefKind::List(Type::U8),
name: Some(t.name.name.to_string()),
foreign_module: None,
});
self.define_type(&t.name.name, t.name.span, id)?;
}
Item::Value(f) => {
if !values.insert(&f.name.name) {
return Err(Error {
span: f.name.span,
msg: format!("{:?} defined twice", f.name.name),
}
.into());
}
}
Item::Use(_) => {}
Item::Interface(_) => unimplemented!(),
}
}
Ok(())
}
fn define_resource(&mut self, name: &str, span: Span, id: ResourceId) -> Result<()> {
if self.resource_lookup.insert(name.to_string(), id).is_some() {
Err(Error {
span,
msg: format!("resource {:?} defined twice", name),
}
.into())
} else {
Ok(())
}
}
fn define_type(&mut self, name: &str, span: Span, id: TypeId) -> Result<()> {
if self.type_lookup.insert(name.to_string(), id).is_some() {
Err(Error {
span,
msg: format!("type {:?} defined twice", name),
}
.into())
} else {
Ok(())
}
}
fn resolve_type_def(&mut self, ty: &super::Type<'_>) -> Result<TypeDefKind> {
Ok(match ty {
super::Type::Unit => TypeDefKind::Type(Type::Unit),
super::Type::Bool => TypeDefKind::Type(Type::Bool),
super::Type::U8 => TypeDefKind::Type(Type::U8),
super::Type::U16 => TypeDefKind::Type(Type::U16),
super::Type::U32 => TypeDefKind::Type(Type::U32),
super::Type::U64 => TypeDefKind::Type(Type::U64),
super::Type::S8 => TypeDefKind::Type(Type::S8),
super::Type::S16 => TypeDefKind::Type(Type::S16),
super::Type::S32 => TypeDefKind::Type(Type::S32),
super::Type::S64 => TypeDefKind::Type(Type::S64),
super::Type::Float32 => TypeDefKind::Type(Type::Float32),
super::Type::Float64 => TypeDefKind::Type(Type::Float64),
super::Type::Char => TypeDefKind::Type(Type::Char),
super::Type::String => TypeDefKind::Type(Type::String),
super::Type::Handle(resource) => {
let id = match self.resource_lookup.get(&*resource.name) {
Some(id) => *id,
None => {
return Err(Error {
span: resource.span,
msg: format!("no resource named `{}`", resource.name),
}
.into())
}
};
TypeDefKind::Type(Type::Handle(id))
}
super::Type::Name(name) => {
let id = match self.type_lookup.get(&*name.name) {
Some(id) => *id,
None => {
return Err(Error {
span: name.span,
msg: format!("no type named `{}`", name.name),
}
.into())
}
};
TypeDefKind::Type(Type::Id(id))
}
super::Type::List(list) => {
let ty = self.resolve_type(list)?;
TypeDefKind::List(ty)
}
super::Type::Record(record) => {
let fields = record
.fields
.iter()
.map(|field| {
Ok(Field {
docs: self.docs(&field.docs),
name: field.name.name.to_string(),
ty: self.resolve_type(&field.ty)?,
})
})
.collect::<Result<Vec<_>>>()?;
TypeDefKind::Record(Record { fields })
}
super::Type::Flags(flags) => {
let flags = flags
.flags
.iter()
.map(|flag| Flag {
docs: self.docs(&flag.docs),
name: flag.name.name.to_string(),
})
.collect::<Vec<_>>();
TypeDefKind::Flags(Flags { flags })
}
super::Type::Tuple(types) => {
let types = types
.iter()
.map(|ty| self.resolve_type(ty))
.collect::<Result<Vec<_>>>()?;
TypeDefKind::Tuple(Tuple { types })
}
super::Type::Variant(variant) => {
if variant.cases.is_empty() {
return Err(Error {
span: variant.span,
msg: "empty variant".to_string(),
}
.into());
}
let cases = variant
.cases
.iter()
.map(|case| {
Ok(Case {
docs: self.docs(&case.docs),
name: case.name.name.to_string(),
ty: match &case.ty {
Some(ty) => self.resolve_type(ty)?,
None => Type::Unit,
},
})
})
.collect::<Result<Vec<_>>>()?;
TypeDefKind::Variant(Variant { cases })
}
super::Type::Enum(e) => {
if e.cases.is_empty() {
return Err(Error {
span: e.span,
msg: "empty enum".to_string(),
}
.into());
}
let cases = e
.cases
.iter()
.map(|case| {
Ok(EnumCase {
docs: self.docs(&case.docs),
name: case.name.name.to_string(),
})
})
.collect::<Result<Vec<_>>>()?;
TypeDefKind::Enum(Enum { cases })
}
super::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?),
super::Type::Expected(e) => TypeDefKind::Expected(Expected {
ok: self.resolve_type(&e.ok)?,
err: self.resolve_type(&e.err)?,
}),
super::Type::Union(e) => {
if e.cases.is_empty() {
return Err(Error {
span: e.span,
msg: "empty union".to_string(),
}
.into());
}
let cases = e
.cases
.iter()
.map(|case| {
Ok(UnionCase {
docs: self.docs(&case.docs),
ty: self.resolve_type(&case.ty)?,
})
})
.collect::<Result<Vec<_>>>()?;
TypeDefKind::Union(Union { cases })
}
super::Type::Future(t) => TypeDefKind::Future(self.resolve_type(t)?),
super::Type::Stream(s) => TypeDefKind::Stream(Stream {
element: self.resolve_type(&s.element)?,
end: self.resolve_type(&s.end)?,
}),
})
}
fn resolve_type(&mut self, ty: &super::Type<'_>) -> Result<Type> {
let kind = self.resolve_type_def(ty)?;
Ok(self.anon_type_def(TypeDef {
kind,
name: None,
docs: Docs::default(),
foreign_module: None,
}))
}
fn anon_type_def(&mut self, ty: TypeDef) -> Type {
let key = match &ty.kind {
TypeDefKind::Type(t) => return *t,
TypeDefKind::Variant(v) => Key::Variant(
v.cases
.iter()
.map(|case| (case.name.clone(), case.ty))
.collect::<Vec<_>>(),
),
TypeDefKind::Record(r) => Key::Record(
r.fields
.iter()
.map(|case| (case.name.clone(), case.ty))
.collect::<Vec<_>>(),
),
TypeDefKind::Flags(r) => {
Key::Flags(r.flags.iter().map(|f| f.name.clone()).collect::<Vec<_>>())
}
TypeDefKind::Tuple(t) => Key::Tuple(t.types.clone()),
TypeDefKind::Enum(r) => {
Key::Enum(r.cases.iter().map(|f| f.name.clone()).collect::<Vec<_>>())
}
TypeDefKind::List(ty) => Key::List(*ty),
TypeDefKind::Option(t) => Key::Option(*t),
TypeDefKind::Expected(e) => Key::Expected(e.ok, e.err),
TypeDefKind::Union(u) => Key::Union(u.cases.iter().map(|c| c.ty).collect()),
TypeDefKind::Future(ty) => Key::Future(*ty),
TypeDefKind::Stream(s) => Key::Stream(s.element, s.end),
};
let types = &mut self.types;
let id = self
.anon_types
.entry(key)
.or_insert_with(|| types.alloc(ty));
Type::Id(*id)
}
fn docs(&mut self, doc: &super::Docs<'_>) -> Docs {
let mut docs = None;
for doc in doc.docs.iter() {
// Comments which are not doc-comments are silently ignored
if let Some(doc) = doc.strip_prefix("///") {
let docs = docs.get_or_insert_with(String::new);
docs.push_str(doc.trim_start_matches('/').trim());
docs.push('\n');
} else if let Some(doc) = doc.strip_prefix("/**") {
let docs = docs.get_or_insert_with(String::new);
assert!(doc.ends_with("*/"));
for line in doc[..doc.len() - 2].lines() {
docs.push_str(line);
docs.push('\n');
}
}
}
Docs { contents: docs }
}
fn resolve_value(&mut self, value: &Value<'_>) -> Result<()> {
let docs = self.docs(&value.docs);
match &value.kind {
ValueKind::Function {
is_async,
params,
result,
} => {
let params = params
.iter()
.map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?)))
.collect::<Result<_>>()?;
let result = self.resolve_type(result)?;
self.functions.push(Function {
docs,
name: value.name.name.to_string(),
kind: FunctionKind::Freestanding,
params,
result,
is_async: *is_async,
});
}
ValueKind::Global(ty) => {
let ty = self.resolve_type(ty)?;
self.globals.push(Global {
docs,
name: value.name.name.to_string(),
ty,
});
}
}
Ok(())
}
fn resolve_resource(&mut self, resource: &super::Resource<'_>) -> Result<()> {
let mut names = HashSet::new();
let id = self.resource_lookup[&*resource.name.name];
for (statik, value) in resource.values.iter() {
let (is_async, params, result) = match &value.kind {
ValueKind::Function {
is_async,
params,
result,
} => (*is_async, params, result),
ValueKind::Global(_) => {
return Err(Error {
span: value.name.span,
msg: "globals not allowed in resources".to_string(),
}
.into());
}
};
if !names.insert(&value.name.name) {
return Err(Error {
span: value.name.span,
msg: format!("{:?} defined twice in this resource", value.name.name),
}
.into());
}
let docs = self.docs(&value.docs);
let mut params = params
.iter()
.map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?)))
.collect::<Result<Vec<_>>>()?;
let result = self.resolve_type(result)?;
let kind = if *statik {
FunctionKind::Static {
resource: id,
name: value.name.name.to_string(),
}
} else {
params.insert(0, ("self".to_string(), Type::Handle(id)));
FunctionKind::Method {
resource: id,
name: value.name.name.to_string(),
}
};
self.functions.push(Function {
is_async,
docs,
name: format!("{}::{}", resource.name.name, value.name.name),
kind,
params,
result,
});
}
Ok(())
}
fn validate_type_not_recursive(
&self,
span: Span,
ty: TypeId,
visiting: &mut HashSet<TypeId>,
valid: &mut HashSet<TypeId>,
) -> Result<()> {
if valid.contains(&ty) {
return Ok(());
}
if !visiting.insert(ty) {
return Err(Error {
span,
msg: "type can recursively refer to itself".to_string(),
}
.into());
}
match &self.types[ty].kind {
TypeDefKind::List(Type::Id(id)) | TypeDefKind::Type(Type::Id(id)) => {
self.validate_type_not_recursive(span, *id, visiting, valid)?
}
TypeDefKind::Variant(v) => {
for case in v.cases.iter() {
if let Type::Id(id) = case.ty {
self.validate_type_not_recursive(span, id, visiting, valid)?;
}
}
}
TypeDefKind::Record(r) => {
for case in r.fields.iter() {
if let Type::Id(id) = case.ty {
self.validate_type_not_recursive(span, id, visiting, valid)?;
}
}
}
TypeDefKind::Tuple(t) => {
for ty in t.types.iter() {
if let Type::Id(id) = *ty {
self.validate_type_not_recursive(span, id, visiting, valid)?;
}
}
}
TypeDefKind::Option(t) => {
if let Type::Id(id) = *t {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
}
TypeDefKind::Expected(e) => {
if let Type::Id(id) = e.ok {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
if let Type::Id(id) = e.err {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
}
TypeDefKind::Future(t) => {
if let Type::Id(id) = *t {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
}
TypeDefKind::Stream(s) => {
if let Type::Id(id) = s.element {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
if let Type::Id(id) = s.end {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
}
TypeDefKind::Union(u) => {
for c in u.cases.iter() {
if let Type::Id(id) = c.ty {
self.validate_type_not_recursive(span, id, visiting, valid)?
}
}
}
TypeDefKind::Flags(_)
| TypeDefKind::List(_)
| TypeDefKind::Type(_)
| TypeDefKind::Enum(_) => {}
}
valid.insert(ty);
visiting.remove(&ty);
Ok(())
}
}

View File

@@ -0,0 +1,524 @@
use anyhow::{anyhow, bail, Context, Result};
use id_arena::{Arena, Id};
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
pub mod abi;
mod ast;
mod sizealign;
pub use sizealign::*;
/// Checks if the given string is a legal identifier in wit.
pub fn validate_id(s: &str) -> Result<()> {
ast::validate_id(0, s)?;
Ok(())
}
#[derive(Debug, Default)]
pub struct Interface {
pub name: String,
/// The module name to use for bindings generation.
///
/// If `None`, then the interface name will be used.
///
/// If `Some`, then this value is used to format an export
/// name of `<module>#<name>` for exports or an import module
/// name of `<module>` for imports.
pub module: Option<String>,
pub types: Arena<TypeDef>,
pub type_lookup: HashMap<String, TypeId>,
pub resources: Arena<Resource>,
pub resource_lookup: HashMap<String, ResourceId>,
pub interfaces: Arena<Interface>,
pub interface_lookup: HashMap<String, InterfaceId>,
pub functions: Vec<Function>,
pub globals: Vec<Global>,
}
pub type TypeId = Id<TypeDef>;
pub type ResourceId = Id<Resource>;
pub type InterfaceId = Id<Interface>;
#[derive(Debug)]
pub struct TypeDef {
pub docs: Docs,
pub kind: TypeDefKind,
pub name: Option<String>,
/// `None` if this type is originally declared in this instance or
/// otherwise `Some` if it was originally defined in a different module.
pub foreign_module: Option<String>,
}
#[derive(Debug)]
pub enum TypeDefKind {
Record(Record),
Flags(Flags),
Tuple(Tuple),
Variant(Variant),
Enum(Enum),
Option(Type),
Expected(Expected),
Union(Union),
List(Type),
Future(Type),
Stream(Stream),
Type(Type),
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum Type {
Unit,
Bool,
U8,
U16,
U32,
U64,
S8,
S16,
S32,
S64,
Float32,
Float64,
Char,
String,
Handle(ResourceId),
Id(TypeId),
}
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum Int {
U8,
U16,
U32,
U64,
}
#[derive(Debug)]
pub struct Record {
pub fields: Vec<Field>,
}
#[derive(Debug)]
pub struct Field {
pub docs: Docs,
pub name: String,
pub ty: Type,
}
#[derive(Debug, Clone)]
pub struct Flags {
pub flags: Vec<Flag>,
}
#[derive(Debug, Clone)]
pub struct Flag {
pub docs: Docs,
pub name: String,
}
#[derive(Debug)]
pub enum FlagsRepr {
U8,
U16,
U32(usize),
}
impl Flags {
pub fn repr(&self) -> FlagsRepr {
match self.flags.len() {
n if n <= 8 => FlagsRepr::U8,
n if n <= 16 => FlagsRepr::U16,
n => FlagsRepr::U32(sizealign::align_to(n, 32) / 32),
}
}
}
impl FlagsRepr {
pub fn count(&self) -> usize {
match self {
FlagsRepr::U8 => 1,
FlagsRepr::U16 => 1,
FlagsRepr::U32(n) => *n,
}
}
}
#[derive(Debug, Clone)]
pub struct Tuple {
pub types: Vec<Type>,
}
#[derive(Debug)]
pub struct Variant {
pub cases: Vec<Case>,
}
#[derive(Debug)]
pub struct Case {
pub docs: Docs,
pub name: String,
pub ty: Type,
}
impl Variant {
pub fn tag(&self) -> Int {
match self.cases.len() {
n if n <= u8::max_value() as usize => Int::U8,
n if n <= u16::max_value() as usize => Int::U16,
n if n <= u32::max_value() as usize => Int::U32,
_ => panic!("too many cases to fit in a repr"),
}
}
}
#[derive(Debug)]
pub struct Enum {
pub cases: Vec<EnumCase>,
}
#[derive(Debug, Clone)]
pub struct EnumCase {
pub docs: Docs,
pub name: String,
}
impl Enum {
pub fn tag(&self) -> Int {
match self.cases.len() {
n if n <= u8::max_value() as usize => Int::U8,
n if n <= u16::max_value() as usize => Int::U16,
n if n <= u32::max_value() as usize => Int::U32,
_ => panic!("too many cases to fit in a repr"),
}
}
}
#[derive(Debug)]
pub struct Expected {
pub ok: Type,
pub err: Type,
}
#[derive(Debug)]
pub struct Union {
pub cases: Vec<UnionCase>,
}
#[derive(Debug, Clone)]
pub struct UnionCase {
pub docs: Docs,
pub ty: Type,
}
impl Union {
pub fn tag(&self) -> Int {
match self.cases.len() {
n if n <= u8::max_value() as usize => Int::U8,
n if n <= u16::max_value() as usize => Int::U16,
n if n <= u32::max_value() as usize => Int::U32,
_ => panic!("too many cases to fit in a repr"),
}
}
}
#[derive(Debug)]
pub struct Stream {
pub element: Type,
pub end: Type,
}
#[derive(Clone, Default, Debug)]
pub struct Docs {
pub contents: Option<String>,
}
#[derive(Debug)]
pub struct Resource {
pub docs: Docs,
pub name: String,
pub supertype: Option<String>,
/// `None` if this resource is defined within the containing instance,
/// otherwise `Some` if it's defined in an instance named here.
pub foreign_module: Option<String>,
}
#[derive(Debug)]
pub struct Global {
pub docs: Docs,
pub name: String,
pub ty: Type,
}
#[derive(Debug)]
pub struct Function {
pub is_async: bool,
pub docs: Docs,
pub name: String,
pub kind: FunctionKind,
pub params: Vec<(String, Type)>,
pub result: Type,
}
#[derive(Debug)]
pub enum FunctionKind {
Freestanding,
Static { resource: ResourceId, name: String },
Method { resource: ResourceId, name: String },
}
impl Function {
pub fn item_name(&self) -> &str {
match &self.kind {
FunctionKind::Freestanding => &self.name,
FunctionKind::Static { name, .. } => name,
FunctionKind::Method { name, .. } => name,
}
}
}
fn unwrap_md(contents: &str) -> String {
let mut wit = String::new();
let mut last_pos = 0;
let mut in_wit_code_block = false;
Parser::new_ext(contents, Options::empty())
.into_offset_iter()
.for_each(|(event, range)| match (event, range) {
(Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => {
in_wit_code_block = true;
}
(Event::Text(text), range) if in_wit_code_block => {
// Ensure that offsets are correct by inserting newlines to
// cover the Markdown content outside of wit code blocks.
for _ in contents[last_pos..range.start].lines() {
wit.push_str("\n");
}
wit.push_str(&text);
last_pos = range.end;
}
(Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => {
in_wit_code_block = false;
}
_ => {}
});
wit
}
impl Interface {
pub fn parse(name: &str, input: &str) -> Result<Interface> {
Interface::parse_with(name, input, |f| {
Err(anyhow!("cannot load submodule `{}`", f))
})
}
pub fn parse_file(path: impl AsRef<Path>) -> Result<Interface> {
let path = path.as_ref();
let parent = path.parent().unwrap();
let contents = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read: {}", path.display()))?;
Interface::parse_with(path, &contents, |path| load_fs(parent, path))
}
pub fn parse_with(
filename: impl AsRef<Path>,
contents: &str,
mut load: impl FnMut(&str) -> Result<(PathBuf, String)>,
) -> Result<Interface> {
Interface::_parse_with(
filename.as_ref(),
contents,
&mut load,
&mut HashSet::new(),
&mut HashMap::new(),
)
}
fn _parse_with(
filename: &Path,
contents: &str,
load: &mut dyn FnMut(&str) -> Result<(PathBuf, String)>,
visiting: &mut HashSet<PathBuf>,
map: &mut HashMap<String, Interface>,
) -> Result<Interface> {
let mut name = filename.file_stem().unwrap();
let mut contents = contents;
// If we have a ".md" file, it's a wit file wrapped in a markdown file;
// parse the markdown to extract the `wit` code blocks.
let md_contents;
if filename.extension().and_then(|s| s.to_str()) == Some("md") {
md_contents = unwrap_md(contents);
contents = &md_contents[..];
// Also strip the inner ".wit" extension.
name = Path::new(name).file_stem().unwrap();
}
// Parse the `contents `into an AST
let ast = match ast::Ast::parse(contents) {
Ok(ast) => ast,
Err(mut e) => {
let file = filename.display().to_string();
ast::rewrite_error(&mut e, &file, contents);
return Err(e);
}
};
// Load up any modules into our `map` that have not yet been parsed.
if !visiting.insert(filename.to_path_buf()) {
bail!("file `{}` recursively imports itself", filename.display())
}
for item in ast.items.iter() {
let u = match item {
ast::Item::Use(u) => u,
_ => continue,
};
if map.contains_key(&*u.from[0].name) {
continue;
}
let (filename, contents) = load(&u.from[0].name)
// TODO: insert context here about `u.name.span` and `filename`
?;
let instance = Interface::_parse_with(&filename, &contents, load, visiting, map)?;
map.insert(u.from[0].name.to_string(), instance);
}
visiting.remove(filename);
// and finally resolve everything into our final instance
match ast.resolve(name.to_str().unwrap(), map) {
Ok(i) => Ok(i),
Err(mut e) => {
let file = filename.display().to_string();
ast::rewrite_error(&mut e, &file, contents);
Err(e)
}
}
}
pub fn topological_types(&self) -> Vec<TypeId> {
let mut ret = Vec::new();
let mut visited = HashSet::new();
for (id, _) in self.types.iter() {
self.topo_visit(id, &mut ret, &mut visited);
}
ret
}
fn topo_visit(&self, id: TypeId, list: &mut Vec<TypeId>, visited: &mut HashSet<TypeId>) {
if !visited.insert(id) {
return;
}
match &self.types[id].kind {
TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => {}
TypeDefKind::Type(t) | TypeDefKind::List(t) => self.topo_visit_ty(t, list, visited),
TypeDefKind::Record(r) => {
for f in r.fields.iter() {
self.topo_visit_ty(&f.ty, list, visited);
}
}
TypeDefKind::Tuple(t) => {
for t in t.types.iter() {
self.topo_visit_ty(t, list, visited);
}
}
TypeDefKind::Variant(v) => {
for v in v.cases.iter() {
self.topo_visit_ty(&v.ty, list, visited);
}
}
TypeDefKind::Option(ty) => self.topo_visit_ty(ty, list, visited),
TypeDefKind::Expected(e) => {
self.topo_visit_ty(&e.ok, list, visited);
self.topo_visit_ty(&e.err, list, visited);
}
TypeDefKind::Union(u) => {
for t in u.cases.iter() {
self.topo_visit_ty(&t.ty, list, visited);
}
}
TypeDefKind::Future(ty) => {
self.topo_visit_ty(ty, list, visited);
}
TypeDefKind::Stream(s) => {
self.topo_visit_ty(&s.element, list, visited);
self.topo_visit_ty(&s.end, list, visited);
}
}
list.push(id);
}
fn topo_visit_ty(&self, ty: &Type, list: &mut Vec<TypeId>, visited: &mut HashSet<TypeId>) {
if let Type::Id(id) = ty {
self.topo_visit(*id, list, visited);
}
}
pub fn all_bits_valid(&self, ty: &Type) -> bool {
match ty {
Type::Unit
| Type::U8
| Type::S8
| Type::U16
| Type::S16
| Type::U32
| Type::S32
| Type::U64
| Type::S64
| Type::Float32
| Type::Float64 => true,
Type::Bool | Type::Char | Type::Handle(_) | Type::String => false,
Type::Id(id) => match &self.types[*id].kind {
TypeDefKind::List(_)
| TypeDefKind::Variant(_)
| TypeDefKind::Enum(_)
| TypeDefKind::Option(_)
| TypeDefKind::Expected(_)
| TypeDefKind::Future(_)
| TypeDefKind::Stream(_)
| TypeDefKind::Union(_) => false,
TypeDefKind::Type(t) => self.all_bits_valid(t),
TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)),
TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)),
// FIXME: this could perhaps be `true` for multiples-of-32 but
// seems better to probably leave this as unconditionally
// `false` for now, may want to reconsider later?
TypeDefKind::Flags(_) => false,
},
}
}
pub fn get_variant(&self, ty: &Type) -> Option<&Variant> {
if let Type::Id(id) = ty {
match &self.types[*id].kind {
TypeDefKind::Variant(v) => Some(v),
_ => None,
}
} else {
None
}
}
}
fn load_fs(root: &Path, name: &str) -> Result<(PathBuf, String)> {
let wit = root.join(name).with_extension("wit");
// Attempt to read a ".wit" file.
match fs::read_to_string(&wit) {
Ok(contents) => Ok((wit, contents)),
// If no such file was found, attempt to read a ".wit.md" file.
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
let wit_md = wit.with_extension("wit.md");
match fs::read_to_string(&wit_md) {
Ok(contents) => Ok((wit_md, contents)),
Err(_err) => Err(err.into()),
}
}
Err(err) => return Err(err.into()),
}
}

View File

@@ -0,0 +1,121 @@
use crate::{FlagsRepr, Int, Interface, Type, TypeDef, TypeDefKind};
#[derive(Default)]
pub struct SizeAlign {
map: Vec<(usize, usize)>,
}
impl SizeAlign {
pub fn fill(&mut self, iface: &Interface) {
self.map = vec![(0, 0); iface.types.len()];
for ty in iface.topological_types() {
let pair = self.calculate(&iface.types[ty]);
self.map[ty.index()] = pair;
}
}
fn calculate(&self, ty: &TypeDef) -> (usize, usize) {
match &ty.kind {
TypeDefKind::Type(t) => (self.size(t), self.align(t)),
TypeDefKind::List(_) => (8, 4),
TypeDefKind::Record(r) => self.record(r.fields.iter().map(|f| &f.ty)),
TypeDefKind::Tuple(t) => self.record(t.types.iter()),
TypeDefKind::Flags(f) => match f.repr() {
FlagsRepr::U8 => (1, 1),
FlagsRepr::U16 => (2, 2),
FlagsRepr::U32(n) => (n * 4, 4),
},
TypeDefKind::Variant(v) => self.variant(v.tag(), v.cases.iter().map(|c| &c.ty)),
TypeDefKind::Enum(e) => self.variant(e.tag(), []),
TypeDefKind::Option(t) => self.variant(Int::U8, [&Type::Unit, t]),
TypeDefKind::Expected(e) => self.variant(Int::U8, [&e.ok, &e.err]),
TypeDefKind::Union(u) => self.variant(u.tag(), u.cases.iter().map(|c| &c.ty)),
// A future is represented as an index.
TypeDefKind::Future(_) => (4, 4),
// A stream is represented as an index.
TypeDefKind::Stream(_) => (4, 4),
}
}
pub fn size(&self, ty: &Type) -> usize {
match ty {
Type::Unit => 0,
Type::Bool | Type::U8 | Type::S8 => 1,
Type::U16 | Type::S16 => 2,
Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) => 4,
Type::U64 | Type::S64 | Type::Float64 | Type::String => 8,
Type::Id(id) => self.map[id.index()].0,
}
}
pub fn align(&self, ty: &Type) -> usize {
match ty {
Type::Unit | Type::Bool | Type::U8 | Type::S8 => 1,
Type::U16 | Type::S16 => 2,
Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) | Type::String => {
4
}
Type::U64 | Type::S64 | Type::Float64 => 8,
Type::Id(id) => self.map[id.index()].1,
}
}
pub fn field_offsets<'a>(&self, types: impl IntoIterator<Item = &'a Type>) -> Vec<usize> {
let mut cur = 0;
types
.into_iter()
.map(|ty| {
let ret = align_to(cur, self.align(ty));
cur = ret + self.size(ty);
ret
})
.collect()
}
pub fn payload_offset<'a>(&self, tag: Int, cases: impl IntoIterator<Item = &'a Type>) -> usize {
let mut max_align = 1;
for ty in cases {
max_align = max_align.max(self.align(ty));
}
let tag_size = int_size_align(tag).0;
align_to(tag_size, max_align)
}
pub fn record<'a>(&self, types: impl Iterator<Item = &'a Type>) -> (usize, usize) {
let mut size = 0;
let mut align = 1;
for ty in types {
let field_size = self.size(ty);
let field_align = self.align(ty);
size = align_to(size, field_align) + field_size;
align = align.max(field_align);
}
(align_to(size, align), align)
}
fn variant<'a>(&self, tag: Int, types: impl IntoIterator<Item = &'a Type>) -> (usize, usize) {
let (discrim_size, discrim_align) = int_size_align(tag);
let mut size = discrim_size;
let mut align = discrim_align;
for ty in types {
let case_size = self.size(ty);
let case_align = self.align(ty);
align = align.max(case_align);
size = size.max(align_to(discrim_size, case_align) + case_size);
}
(size, align)
}
}
fn int_size_align(i: Int) -> (usize, usize) {
match i {
Int::U8 => (1, 1),
Int::U16 => (2, 2),
Int::U32 => (4, 4),
Int::U64 => (8, 8),
}
}
pub(crate) fn align_to(val: usize, align: usize) -> usize {
(val + align - 1) & !(align - 1)
}

View File

@@ -0,0 +1,344 @@
//! You can run this test suite with:
//!
//! cargo test --test all
//!
//! An argument can be passed as well to filter, based on filename, which test
//! to run
//!
//! cargo test --test all foo.wit
use anyhow::{bail, Context, Result};
use rayon::prelude::*;
use serde::Serialize;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wit_parser::*;
fn main() {
let tests = find_tests();
let filter = std::env::args().nth(1);
let tests = tests
.par_iter()
.filter_map(|test| {
if let Some(filter) = &filter {
if let Some(s) = test.to_str() {
if !s.contains(filter) {
return None;
}
}
}
let contents = fs::read(test).unwrap();
Some((test, contents))
})
.collect::<Vec<_>>();
println!("running {} test files\n", tests.len());
let ntests = AtomicUsize::new(0);
let errors = tests
.par_iter()
.filter_map(|(test, contents)| {
Runner { ntests: &ntests }
.run(test, contents)
.context(format!("test {:?} failed", test))
.err()
})
.collect::<Vec<_>>();
if !errors.is_empty() {
for msg in errors.iter() {
eprintln!("{:?}", msg);
}
panic!("{} tests failed", errors.len())
}
println!(
"test result: ok. {} directives passed\n",
ntests.load(SeqCst)
);
}
/// Recursively finds all tests in a whitelisted set of directories which we
/// then load up and test in parallel.
fn find_tests() -> Vec<PathBuf> {
let mut tests = Vec::new();
find_tests("tests/ui".as_ref(), &mut tests);
tests.sort();
return tests;
fn find_tests(path: &Path, tests: &mut Vec<PathBuf>) {
for f in path.read_dir().unwrap() {
let f = f.unwrap();
if f.file_type().unwrap().is_dir() {
find_tests(&f.path(), tests);
continue;
}
match f.path().extension().and_then(|s| s.to_str()) {
Some("md") => {}
Some("wit") => {}
_ => continue,
}
tests.push(f.path());
}
}
}
struct Runner<'a> {
ntests: &'a AtomicUsize,
}
impl Runner<'_> {
fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> {
let contents = str::from_utf8(contents)?;
let result = Interface::parse_file(test);
let result = if contents.contains("// parse-fail") {
match result {
Ok(_) => bail!("expected test to not parse but it did"),
Err(mut e) => {
if let Some(err) = e.downcast_mut::<io::Error>() {
*err = io::Error::new(
io::ErrorKind::Other,
"some generic platform-agnostic error message",
);
}
normalize(test, &format!("{:?}", e))
}
}
} else {
let instance = result?;
to_json(&instance)
};
// "foo.wit" => "foo.wit.result"
// "foo.wit.md" => "foo.wit.md.result"
let result_file = if test.extension() == Some(OsStr::new("md"))
&& test
.file_stem()
.and_then(|path| Path::new(path).extension())
== Some(OsStr::new("wit"))
{
test.with_extension("md.result")
} else {
test.with_extension("wit.result")
};
if env::var_os("BLESS").is_some() {
fs::write(&result_file, result)?;
} else {
let expected = fs::read_to_string(&result_file).context(format!(
"failed to read test expectation file {:?}\nthis can be fixed with BLESS=1",
result_file
))?;
let expected = normalize(test, &expected);
if expected != result {
bail!(
"failed test: expected `{:?}` but found `{:?}`",
expected,
result
);
}
}
self.bump_ntests();
return Ok(());
fn normalize(test: &Path, s: &str) -> String {
s.replace(
&test.display().to_string(),
&test.display().to_string().replace("\\", "/"),
)
.replace("\\parse-fail\\", "/parse-fail/")
.replace("\r\n", "\n")
}
}
fn bump_ntests(&self) {
self.ntests.fetch_add(1, SeqCst);
}
}
fn to_json(i: &Interface) -> String {
#[derive(Serialize)]
struct Interface {
#[serde(skip_serializing_if = "Vec::is_empty")]
resources: Vec<Resource>,
#[serde(skip_serializing_if = "Vec::is_empty")]
types: Vec<TypeDef>,
#[serde(skip_serializing_if = "Vec::is_empty")]
functions: Vec<Function>,
#[serde(skip_serializing_if = "Vec::is_empty")]
globals: Vec<Global>,
}
#[derive(Serialize)]
struct Resource {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
supertype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
foreign_module: Option<String>,
}
#[derive(Serialize)]
struct TypeDef {
idx: usize,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(flatten)]
ty: Type,
#[serde(skip_serializing_if = "Option::is_none")]
foreign_module: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
enum Type {
Primitive(String),
Record { fields: Vec<(String, String)> },
Flags { flags: Vec<String> },
Enum { cases: Vec<String> },
Variant { cases: Vec<(String, String)> },
Tuple { types: Vec<String> },
Option(String),
Expected { ok: String, err: String },
Future(String),
Stream { element: String, end: String },
List(String),
Union { cases: Vec<String> },
}
#[derive(Serialize)]
struct Function {
name: String,
#[serde(rename = "async", skip_serializing_if = "Option::is_none")]
is_async: Option<bool>,
params: Vec<String>,
result: String,
}
#[derive(Serialize)]
struct Global {
name: String,
ty: String,
}
let resources = i
.resources
.iter()
.map(|(_, r)| Resource {
name: r.name.clone(),
supertype: r.supertype.as_ref().map(|supertype| supertype.clone()),
foreign_module: r.foreign_module.clone(),
})
.collect::<Vec<_>>();
let types = i
.types
.iter()
.map(|(i, r)| TypeDef {
idx: i.index(),
name: r.name.clone(),
ty: translate_typedef(r),
foreign_module: r.foreign_module.clone(),
})
.collect::<Vec<_>>();
let functions = i
.functions
.iter()
.map(|f| Function {
name: f.name.clone(),
is_async: if f.is_async { Some(f.is_async) } else { None },
params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(),
result: translate_type(&f.result),
})
.collect::<Vec<_>>();
let globals = i
.globals
.iter()
.map(|g| Global {
name: g.name.clone(),
ty: translate_type(&g.ty),
})
.collect::<Vec<_>>();
let iface = Interface {
resources,
types,
functions,
globals,
};
return serde_json::to_string_pretty(&iface).unwrap();
fn translate_typedef(ty: &wit_parser::TypeDef) -> Type {
match &ty.kind {
TypeDefKind::Type(t) => Type::Primitive(translate_type(t)),
TypeDefKind::Record(r) => Type::Record {
fields: r
.fields
.iter()
.map(|f| (f.name.clone(), translate_type(&f.ty)))
.collect(),
},
TypeDefKind::Tuple(t) => Type::Tuple {
types: t.types.iter().map(|ty| translate_type(ty)).collect(),
},
TypeDefKind::Flags(r) => Type::Flags {
flags: r.flags.iter().map(|f| f.name.clone()).collect(),
},
TypeDefKind::Enum(r) => Type::Enum {
cases: r.cases.iter().map(|f| f.name.clone()).collect(),
},
TypeDefKind::Variant(v) => Type::Variant {
cases: v
.cases
.iter()
.map(|f| (f.name.clone(), translate_type(&f.ty)))
.collect(),
},
TypeDefKind::Option(t) => Type::Option(translate_type(t)),
TypeDefKind::Expected(e) => Type::Expected {
ok: translate_type(&e.ok),
err: translate_type(&e.err),
},
TypeDefKind::Future(t) => Type::Future(translate_type(t)),
TypeDefKind::Stream(s) => Type::Stream {
element: translate_type(&s.element),
end: translate_type(&s.end),
},
TypeDefKind::List(ty) => Type::List(translate_type(ty)),
TypeDefKind::Union(u) => Type::Union {
cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(),
},
}
}
fn translate_type(ty: &wit_parser::Type) -> String {
use wit_parser::Type;
match ty {
Type::Unit => format!("unit"),
Type::Bool => format!("bool"),
Type::U8 => format!("u8"),
Type::U16 => format!("u16"),
Type::U32 => format!("u32"),
Type::U64 => format!("u64"),
Type::S8 => format!("s8"),
Type::S16 => format!("s16"),
Type::S32 => format!("s32"),
Type::S64 => format!("s64"),
Type::Float32 => format!("float32"),
Type::Float64 => format!("float64"),
Type::Char => format!("char"),
Type::String => format!("string"),
Type::Handle(resource) => format!("handle-{}", resource.index()),
Type::Id(id) => format!("type-{}", id.index()),
}
}
}

View File

@@ -0,0 +1,9 @@
a: async func()
b: async func(x: s32)
c: async func() -> u32
resource y {
a: async func()
b: async func(x: s32)
c: async func() -> u32
}

View File

@@ -0,0 +1,60 @@
{
"resources": [
{
"name": "y"
}
],
"types": [
{
"idx": 0,
"primitive": "handle-0"
}
],
"functions": [
{
"name": "a",
"async": true,
"params": [],
"result": "unit"
},
{
"name": "b",
"async": true,
"params": [
"s32"
],
"result": "unit"
},
{
"name": "c",
"async": true,
"params": [],
"result": "u32"
},
{
"name": "y::a",
"async": true,
"params": [
"handle-0"
],
"result": "unit"
},
{
"name": "y::b",
"async": true,
"params": [
"handle-0",
"s32"
],
"result": "unit"
},
{
"name": "y::c",
"async": true,
"params": [
"handle-0"
],
"result": "u32"
}
]
}

View File

@@ -0,0 +1,19 @@
// hello
// world
// why, yes
// this is a comment
/* this too */ /* is a comment */
/* this /* is /* a */ nested */ comment */
type /* foo */ bar /* baz */ = //
handle //
//
//
x
resource /* x */ x // ...

View File

@@ -0,0 +1,18 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "bar",
"primitive": "handle-0"
},
{
"idx": 1,
"primitive": "handle-0"
}
]
}

View File

@@ -0,0 +1,24 @@
# A Markdown file!
containing stuff, and also some code blocks, wit and other.
```wit
x: func()
```
Intervening content, including a non-wit codeblock:
```js
function func() {}
```
```wit
y: func()
```
## A new section
In which, another wit code block!
```wit
z: func()
```

View File

@@ -0,0 +1,19 @@
{
"functions": [
{
"name": "x",
"params": [],
"result": "unit"
},
{
"name": "y",
"params": [],
"result": "unit"
},
{
"name": "z",
"params": [],
"result": "unit"
}
]
}

View File

@@ -0,0 +1,7 @@
f1: func()
f2: func(a: u32)
f3: func(a: u32,)
f4: func() -> u32
f6: func() -> tuple<u32, u32>
f7: func(a: float32, b: float32) -> tuple<u32, u32>
f8: func(a: option<u32>) -> expected<u32, float32>

View File

@@ -0,0 +1,70 @@
{
"types": [
{
"idx": 0,
"tuple": {
"types": [
"u32",
"u32"
]
}
},
{
"idx": 1,
"option": "u32"
},
{
"idx": 2,
"expected": {
"ok": "u32",
"err": "float32"
}
}
],
"functions": [
{
"name": "f1",
"params": [],
"result": "unit"
},
{
"name": "f2",
"params": [
"u32"
],
"result": "unit"
},
{
"name": "f3",
"params": [
"u32"
],
"result": "unit"
},
{
"name": "f4",
"params": [],
"result": "u32"
},
{
"name": "f6",
"params": [],
"result": "type-0"
},
{
"name": "f7",
"params": [
"float32",
"float32"
],
"result": "type-0"
},
{
"name": "f8",
"params": [
"type-1"
],
"result": "type-2"
}
]
}

View File

@@ -0,0 +1,28 @@
# Title
This file is like import-me.wit, but it's a Markdown file with embedded wit
code blocks.
## `foo`
```wit
/// This is foo.
type foo = u32
```
## `x`
```wit
/// This is x.
resource x
```
## `handle`
```wit
/// This is handle.
type %handle = handle x
```
## `some-record`
```wit
/// This is some-record.
type some-record = tuple<u32, u64, float32>
```

View File

@@ -0,0 +1,34 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32"
},
{
"idx": 1,
"primitive": "handle-0"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
}
}
]
}

View File

@@ -0,0 +1,7 @@
type foo = u32
resource x
type %handle = handle x
type some-record = tuple<u32, u64, float32>

View File

@@ -0,0 +1,34 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32"
},
{
"idx": 1,
"primitive": "handle-0"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
}
}
]
}

View File

@@ -0,0 +1,18 @@
// This test is like imports.wit, but uses import-me-too, which is a markdown
// file instead of a plain wit file.
use { foo } from import-me-too
use { foo as bar } from import-me-too
use { x as import-me-x } from import-me-too
type x = foo
type y = bar
type z1 = import-me-x
type z2 = handle import-me-x
use { %handle } from import-me-too
resource xyz
type my-handle = handle xyz
type my-handle2 = xyz
use { some-record } from import-me-too

View File

@@ -0,0 +1,76 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me-too"
},
{
"name": "xyz"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me-too"
},
{
"idx": 1,
"primitive": "handle-0",
"foreign_module": "import-me-too"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me-too"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me-too"
},
{
"idx": 4,
"name": "x",
"primitive": "type-0"
},
{
"idx": 5,
"name": "y",
"primitive": "type-0"
},
{
"idx": 6,
"name": "z1",
"primitive": "type-1"
},
{
"idx": 7,
"name": "z2",
"primitive": "handle-0"
},
{
"idx": 8,
"primitive": "handle-1"
},
{
"idx": 9,
"name": "my-handle",
"primitive": "handle-1"
},
{
"idx": 10,
"name": "my-handle2",
"primitive": "type-8"
}
]
}

View File

@@ -0,0 +1,15 @@
use { foo } from import-me
use { foo as bar } from import-me
use { x as import-me-x } from import-me
type x = foo
type y = bar
type z1 = import-me-x
type z2 = handle import-me-x
use { %handle } from import-me
resource xyz
type my-handle = handle xyz
type my-handle2 = xyz
use { some-record } from import-me

View File

@@ -0,0 +1,76 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me"
},
{
"name": "xyz"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me"
},
{
"idx": 1,
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me"
},
{
"idx": 4,
"name": "x",
"primitive": "type-0"
},
{
"idx": 5,
"name": "y",
"primitive": "type-0"
},
{
"idx": 6,
"name": "z1",
"primitive": "type-1"
},
{
"idx": 7,
"name": "z2",
"primitive": "handle-0"
},
{
"idx": 8,
"primitive": "handle-1"
},
{
"idx": 9,
"name": "my-handle",
"primitive": "handle-1"
},
{
"idx": 10,
"name": "my-handle2",
"primitive": "type-8"
}
]
}

View File

@@ -0,0 +1,4 @@
use * from import-me
type my-handle = handle x
type my-handle2 = x

View File

@@ -0,0 +1,49 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me"
},
{
"idx": 1,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 2,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me"
},
{
"idx": 3,
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 4,
"name": "my-handle",
"primitive": "handle-0"
},
{
"idx": 5,
"name": "my-handle2",
"primitive": "type-3"
}
]
}

View File

@@ -0,0 +1,2 @@
// parse-fail
a: async

View File

@@ -0,0 +1,5 @@
expected keyword `func`, found eof
--> tests/ui/parse-fail/async.wit:3:1
|
3 |
| ^

View File

@@ -0,0 +1,3 @@
// parse-fail
a: async()

View File

@@ -0,0 +1,5 @@
expected keyword `func`, found '('
--> tests/ui/parse-fail/async1.wit:2:9
|
2 | a: async()
| ^

View File

@@ -0,0 +1,5 @@
// parse-fail
type x = list<u32
type y = u32

View File

@@ -0,0 +1,5 @@
expected '>', found keyword `type`
--> tests/ui/parse-fail/bad-list.wit:5:1
|
5 | type y = u32
| ^

View File

@@ -0,0 +1,5 @@
// parse-fail
resource x {
x: s32
}

View File

@@ -0,0 +1,5 @@
globals not allowed in resources
--> tests/ui/parse-fail/bad-resource.wit:4:3
|
4 | x: s32
| ^

View File

@@ -0,0 +1,6 @@
// parse-fail
resource x {
x: func()
x: func()
}

View File

@@ -0,0 +1,5 @@
"x" defined twice in this resource
--> tests/ui/parse-fail/bad-resource2.wit:5:3
|
5 | x: func()
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
use {} from foo

View File

@@ -0,0 +1,5 @@
expected an identifier or string, found '}'
--> tests/ui/parse-fail/bad-use.wit:2:6
|
2 | use {} from foo
| ^

View File

@@ -0,0 +1,4 @@
// parse-fail
use { a } from
type foo = u32

View File

@@ -0,0 +1,5 @@
expected an identifier or string, found keyword `type`
--> tests/ui/parse-fail/bad-use2.wit:4:1
|
4 | type foo = u32
| ^---

View File

@@ -0,0 +1,2 @@
// parse-fail
use * from type

View File

@@ -0,0 +1,5 @@
expected an identifier or string, found keyword `type`
--> tests/ui/parse-fail/bad-use3.wit:2:12
|
2 | use * from type
| ^---

View File

@@ -0,0 +1,2 @@
// parse-fail
use { foo } from bar:

View File

@@ -0,0 +1,5 @@
expected ':', found whitespace
--> tests/ui/parse-fail/bad-use4.wit:2:22
|
2 | use { foo } from bar:
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
use { foo } from bar:baz

View File

@@ -0,0 +1,5 @@
expected ':', found an identifier
--> tests/ui/parse-fail/bad-use5.wit:2:22
|
2 | use { foo } from bar:baz
| ^

View File

@@ -0,0 +1,3 @@
// parse-fail
use { foo } from import-me::bar

View File

@@ -0,0 +1,5 @@
`bar` not defined in `import-me`
--> tests/ui/parse-fail/bad-use6.wit:3:29
|
3 | use { foo } from import-me::bar
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
use from import-me

View File

@@ -0,0 +1,5 @@
expected '{', found keyword `from`
--> tests/ui/parse-fail/bad-use7.wit:3:5
|
3 | use from import-me
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
type foo = foo

View File

@@ -0,0 +1,5 @@
type can recursively refer to itself
--> tests/ui/parse-fail/cycle.wit:2:6
|
2 | type foo = foo
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
type foo = bar
type bar = foo

View File

@@ -0,0 +1,5 @@
type can recursively refer to itself
--> tests/ui/parse-fail/cycle2.wit:2:6
|
2 | type foo = bar
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
type foo = bar
type bar = option<foo>

View File

@@ -0,0 +1,5 @@
type can recursively refer to itself
--> tests/ui/parse-fail/cycle3.wit:2:6
|
2 | type foo = bar
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
type foo = bar
record bar { x: foo }

View File

@@ -0,0 +1,5 @@
type can recursively refer to itself
--> tests/ui/parse-fail/cycle4.wit:2:6
|
2 | type foo = bar
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
type foo = bar
type bar = list<foo>

View File

@@ -0,0 +1,5 @@
type can recursively refer to itself
--> tests/ui/parse-fail/cycle5.wit:2:6
|
2 | type foo = bar
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
type

View File

@@ -0,0 +1,5 @@
expected an identifier or string, found eof
--> tests/ui/parse-fail/dangling-type.wit:4:1
|
4 |
| ^

View File

@@ -0,0 +1,4 @@
// parse-fail
foo: func()
foo: func()

View File

@@ -0,0 +1,5 @@
"foo" defined twice
--> tests/ui/parse-fail/duplicate-functions.wit:4:1
|
4 | foo: func()
| ^--

View File

@@ -0,0 +1,4 @@
// parse-fail
resource a
resource a

View File

@@ -0,0 +1,5 @@
resource "a" defined twice
--> tests/ui/parse-fail/duplicate-resource.wit:4:10
|
4 | resource a
| ^

View File

@@ -0,0 +1,4 @@
// parse-fail
type foo = s32
type foo = s32

View File

@@ -0,0 +1,5 @@
type "foo" defined twice
--> tests/ui/parse-fail/duplicate-type.wit:4:6
|
4 | type foo = s32
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
a: s32
a: u32

View File

@@ -0,0 +1,5 @@
"a" defined twice
--> tests/ui/parse-fail/duplicate-value.wit:3:1
|
3 | a: u32
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
enum t {}

View File

@@ -0,0 +1,5 @@
empty enum
--> tests/ui/parse-fail/empty-enum.wit:2:6
|
2 | enum t {}
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
union t {}

View File

@@ -0,0 +1,5 @@
empty union
--> tests/ui/parse-fail/empty-union.wit:2:7
|
2 | union t {}
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
variant t {}

View File

@@ -0,0 +1,5 @@
empty variant
--> tests/ui/parse-fail/empty-variant1.wit:2:9
|
2 | variant t {}
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
type foo = handle foo

View File

@@ -0,0 +1,5 @@
no resource named `foo`
--> tests/ui/parse-fail/handle-no-resource.wit:2:19
|
2 | type foo = handle foo
| ^--

View File

@@ -0,0 +1,2 @@
// parse-fail
use { nonexistent } from import-me

View File

@@ -0,0 +1,5 @@
name not defined in submodule
--> tests/ui/parse-fail/import-bad.wit:2:7
|
2 | use { nonexistent } from import-me
| ^----------

View File

@@ -0,0 +1,3 @@
// parse-fail
use { foo } from import-me
use { foo } from import-me

View File

@@ -0,0 +1,5 @@
type "foo" defined twice
--> tests/ui/parse-fail/import-bad2.wit:3:7
|
3 | use { foo } from import-me
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
use { bar } from import-me
use { bar } from import-me

View File

@@ -0,0 +1,5 @@
resource "bar" defined twice
--> tests/ui/parse-fail/import-bad3.wit:3:7
|
3 | use { bar } from import-me
| ^--

View File

@@ -0,0 +1,2 @@
// parse-fail
use { bar, bar } from import-me

View File

@@ -0,0 +1,5 @@
resource "bar" defined twice
--> tests/ui/parse-fail/import-bad4.wit:2:12
|
2 | use { bar, bar } from import-me
| ^--

View File

@@ -0,0 +1,3 @@
// parse-fail
use { foo } from import-me
use * from import-me

View File

@@ -0,0 +1,5 @@
type "foo" defined twice
--> tests/ui/parse-fail/import-bad5.wit:3:12
|
3 | use * from import-me
| ^--------

View File

@@ -0,0 +1,2 @@
// parse-fail
use { foo } from import-cycle2-v2

View File

@@ -0,0 +1 @@
file `tests/ui/parse-fail/import-cycle2-v1.wit` recursively imports itself

View File

@@ -0,0 +1,2 @@
// parse-fail
use { foo } from import-cycle2-v1

View File

@@ -0,0 +1 @@
file `tests/ui/parse-fail/import-cycle2-v2.wit` recursively imports itself

View File

@@ -0,0 +1,2 @@
type foo = u32
resource bar

View File

@@ -0,0 +1,18 @@
{
"resources": [
{
"name": "bar"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32"
},
{
"idx": 1,
"primitive": "handle-0"
}
]
}

View File

@@ -0,0 +1,2 @@
// parse-fail
use { foo } from import1

View File

@@ -0,0 +1 @@
file `tests/ui/parse-fail/import1.wit` recursively imports itself

View File

@@ -0,0 +1,7 @@
// parse-fail
hello
```wit
type foo = bar
```

Some files were not shown because too many files have changed in this diff Show More