feat(interface-types) Add the Vec1 type to represent a non-empty vector.

`Vec1` is used by `RecordType` to ensure that a record have at least 1
field. Then an `InterfaceValue::Record` is ensured to get at least one
value with the type-checking pass.
This commit is contained in:
Ivan Enderlin 2020-04-03 16:13:44 +02:00
parent 643659801c
commit 419dcc6415
12 changed files with 136 additions and 39 deletions

View File

@ -1,7 +1,7 @@
//! Represents the WIT language as a tree. This is the central
//! representation of the language.
use crate::interpreter::Instruction;
use crate::{interpreter::Instruction, vec1::Vec1};
use std::str;
/// Represents the types supported by WIT.
@ -57,7 +57,7 @@ pub enum InterfaceType {
#[derive(PartialEq, Debug, Clone)]
pub struct RecordType {
/// Types representing the fields.
pub fields: Vec<InterfaceType>,
pub fields: Vec1<InterfaceType>,
}
/// Represents the kind of type.

View File

@ -1,6 +1,6 @@
//! Parse the WIT binary representation into an [AST](crate::ast).
use crate::{ast::*, interpreter::Instruction};
use crate::{ast::*, interpreter::Instruction, vec1::Vec1};
use nom::{
error::{make_error, ErrorKind, ParseError},
Err, IResult,
@ -112,7 +112,12 @@ fn record_type<'input, E: ParseError<&'input [u8]>>(
) -> IResult<&'input [u8], RecordType, E> {
let (output, fields) = list(input, ty)?;
Ok((output, RecordType { fields }))
Ok((
output,
RecordType {
fields: Vec1::new(fields).expect("Record must have at least one field, zero given."),
},
))
}
/// Parse a UTF-8 string.
@ -640,7 +645,7 @@ mod tests {
InterfaceType::I32,
InterfaceType::I64,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::S32],
fields: vec1![InterfaceType::S32],
}),
],
));
@ -670,16 +675,16 @@ mod tests {
&[0x01][..],
vec![
RecordType {
fields: vec![InterfaceType::String],
fields: vec1![InterfaceType::String],
},
RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: vec1![InterfaceType::String, InterfaceType::I32],
},
RecordType {
fields: vec![
fields: vec1![
InterfaceType::String,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
fields: vec1![InterfaceType::I32, InterfaceType::I32],
}),
InterfaceType::F64,
],
@ -864,7 +869,7 @@ mod tests {
outputs: vec![InterfaceType::S32],
},
Type::Record(RecordType {
fields: vec![InterfaceType::S32, InterfaceType::S32],
fields: vec1![InterfaceType::S32, InterfaceType::S32],
}),
],
));

View File

@ -1,6 +1,6 @@
//! Parse the WIT textual representation into an [AST](crate::ast).
use crate::{ast::*, interpreter::Instruction};
use crate::{ast::*, interpreter::Instruction, vec1::Vec1};
pub use wast::parser::ParseBuffer as Buffer;
use wast::parser::{self, Cursor, Parse, Parser, Peek, Result};
@ -151,7 +151,9 @@ impl Parse<'_> for RecordType {
})?);
}
Ok(RecordType { fields })
Ok(RecordType {
fields: Vec1::new(fields).expect("Record must have at least one field, zero given."),
})
}
}
@ -681,7 +683,7 @@ mod tests {
InterfaceType::I32,
InterfaceType::I64,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::String],
fields: vec1![InterfaceType::String],
}),
];
@ -704,16 +706,16 @@ mod tests {
];
let outputs = vec![
RecordType {
fields: vec![InterfaceType::String],
fields: vec1![InterfaceType::String],
},
RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: vec1![InterfaceType::String, InterfaceType::I32],
},
RecordType {
fields: vec![
fields: vec1![
InterfaceType::String,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
fields: vec1![InterfaceType::I32, InterfaceType::I32],
}),
InterfaceType::F64,
],
@ -874,7 +876,7 @@ mod tests {
fn test_type_record() {
let input = buffer(r#"(@interface type (record (field string) (field i32)))"#);
let output = Interface::Type(Type::Record(RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: vec1![InterfaceType::String, InterfaceType::I32],
}));
assert_eq!(parser::parse::<Interface>(&input).unwrap(), output);

View File

@ -445,7 +445,7 @@ mod tests {
assert_to_bytes!(InterfaceType::I64, &[0x0d]);
assert_to_bytes!(
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::String]
fields: vec1![InterfaceType::String]
}),
&[0x0e, 0x01, 0x0a]
);
@ -455,7 +455,7 @@ mod tests {
fn test_record_type() {
assert_to_bytes!(
RecordType {
fields: vec![InterfaceType::String]
fields: vec1![InterfaceType::String]
},
&[
0x01, // 1 field
@ -464,7 +464,7 @@ mod tests {
);
assert_to_bytes!(
RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32]
fields: vec1![InterfaceType::String, InterfaceType::I32]
},
&[
0x02, // 2 fields
@ -474,10 +474,10 @@ mod tests {
);
assert_to_bytes!(
RecordType {
fields: vec![
fields: vec1![
InterfaceType::String,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
fields: vec1![InterfaceType::I32, InterfaceType::I32],
}),
InterfaceType::F64,
],
@ -542,7 +542,7 @@ mod tests {
fn test_type_record() {
assert_to_bytes!(
Type::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I64],
fields: vec1![InterfaceType::I32, InterfaceType::I64],
}),
&[
0x01, // record type

View File

@ -368,7 +368,7 @@ mod tests {
(&InterfaceType::I32).to_string(),
(&InterfaceType::I64).to_string(),
(&InterfaceType::Record(RecordType {
fields: vec![InterfaceType::String],
fields: vec1![InterfaceType::String],
}))
.to_string(),
];
@ -397,18 +397,18 @@ mod tests {
fn test_record_type() {
let inputs = vec![
(&RecordType {
fields: vec![InterfaceType::String],
fields: vec1![InterfaceType::String],
})
.to_string(),
(&RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: vec1![InterfaceType::String, InterfaceType::I32],
})
.to_string(),
(&RecordType {
fields: vec![
fields: vec1![
InterfaceType::String,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
fields: vec1![InterfaceType::I32, InterfaceType::I32],
}),
InterfaceType::F64,
],
@ -539,7 +539,7 @@ mod tests {
})
.to_string(),
(&Type::Record(RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: vec1![InterfaceType::String, InterfaceType::I32],
}))
.to_string(),
];

View File

@ -326,10 +326,10 @@ pub(crate) mod tests {
},
memory: Memory::new(vec![Cell::new(0); 128]),
wit_types: vec![ast::Type::Record(ast::RecordType {
fields: vec![
fields: vec1![
InterfaceType::I32,
InterfaceType::Record(ast::RecordType {
fields: vec![InterfaceType::String, InterfaceType::F32],
fields: vec1![InterfaceType::String, InterfaceType::F32],
}),
InterfaceType::I64,
],

View File

@ -293,7 +293,7 @@ mod tests {
let mut instance = Instance::new();
instance.wit_types.push(
Type::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::I32],
fields: vec1![InterfaceType::I32, InterfaceType::I32],
})
);

View File

@ -1,7 +1,7 @@
//! Defines WIT values and associated operations.
pub use crate::ast::{InterfaceType, RecordType};
use crate::errors::WasmValueNativeCastError;
use crate::{errors::WasmValueNativeCastError, vec1::Vec1};
use std::{convert::TryFrom, slice::Iter};
#[cfg(feature = "serde")]
@ -85,7 +85,8 @@ impl Default for InterfaceValue {
impl From<&Vec<InterfaceValue>> for RecordType {
fn from(values: &Vec<InterfaceValue>) -> Self {
RecordType {
fields: values.iter().map(Into::into).collect(),
fields: Vec1::new(values.iter().map(Into::into).collect())
.expect("Record must have at least one field, zero given."),
}
}
}
@ -225,7 +226,7 @@ mod tests {
InterfaceValue::S8(2)
])),
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::I32, InterfaceType::S8]
fields: vec1![InterfaceType::I32, InterfaceType::S8]
})
);
@ -239,10 +240,10 @@ mod tests {
InterfaceValue::S8(2)
])),
InterfaceType::Record(RecordType {
fields: vec![
fields: vec1![
InterfaceType::I32,
InterfaceType::Record(RecordType {
fields: vec![InterfaceType::String, InterfaceType::F64]
fields: vec1![InterfaceType::String, InterfaceType::F64]
}),
InterfaceType::S8
]

View File

@ -57,3 +57,4 @@ pub mod decoders;
pub mod encoders;
pub mod errors;
pub mod interpreter;
pub mod vec1;

View File

@ -1,3 +1,28 @@
/// This macro creates a `Vec1` by checking at compile-time that its
/// invariant holds.
#[allow(unused)]
macro_rules! vec1 {
($item:expr; 0) => {
compile_error!("Cannot create an empty `Vec1`, it violates its invariant.")
};
() => {
compile_error!("Cannot create an empty `Vec1`, it violates its invariant.")
};
($item:expr; $length:expr) => {
{
crate::vec1::Vec1::new(vec![$item; $length]).unwrap()
}
};
($($item:expr),+ $(,)?) => {
{
crate::vec1::Vec1::new(vec![$($item),*]).unwrap()
}
};
}
/// This macro runs a parser, extracts the next input and the parser
/// output, and positions the next input on `$input`.
macro_rules! consume {

62
src/vec1.rs Normal file
View File

@ -0,0 +1,62 @@
//! `Vec1<T>` represents a non-empty `Vec<T>`.
use std::{
error,
fmt::{self, Debug},
ops,
};
/// `Vec1<T>` represents a non-empty `Vec<T>`. It derefs to `Vec<T>`
/// directly.
#[derive(Clone, PartialEq)]
pub struct Vec1<T>(Vec<T>)
where
T: Debug;
/// Represents the only error that can be emitted by `Vec1`, i.e. when
/// the number of items is zero.
#[derive(Debug)]
pub struct EmptyVec;
impl error::Error for EmptyVec {}
impl fmt::Display for EmptyVec {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Vec1 must as least contain one item, zero given")
}
}
impl<T> Vec1<T>
where
T: Debug,
{
/// Creates a new non-empty vector, based on an inner `Vec<T>`. If
/// the inner vector is empty, a `EmptyVec` error is returned.
pub fn new(items: Vec<T>) -> Result<Self, EmptyVec> {
if items.len() == 0 {
Err(EmptyVec)
} else {
Ok(Self(items))
}
}
}
impl<T> fmt::Debug for Vec1<T>
where
T: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{:?}", self.0)
}
}
impl<T> ops::Deref for Vec1<T>
where
T: Debug,
{
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -1,5 +1,6 @@
use wasmer_interface_types::{
ast::*, decoders::binary::parse, encoders::binary::ToBytes, interpreter::Instruction,
vec1::Vec1,
};
/// Tests an AST to binary, then binary to AST roundtrip.
@ -16,7 +17,7 @@ fn test_binary_encoding_decoding_roundtrip() {
outputs: vec![InterfaceType::S32],
},
Type::Record(RecordType {
fields: vec![InterfaceType::String, InterfaceType::I32],
fields: Vec1::new(vec![InterfaceType::String, InterfaceType::I32]).unwrap(),
}),
],
imports: vec![Import {