mirror of
https://github.com/fluencelabs/interface-types
synced 2024-12-04 15:20:20 +00:00
Merge pull request #3 from fluencelabs/record_refactoring
Refactor Record and Function IT types
This commit is contained in:
commit
05c2d9ddda
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -47,6 +47,15 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
@ -186,8 +195,9 @@ checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-interface-types-fl"
|
||||
version = "0.17.2"
|
||||
version = "0.17.5"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nom",
|
||||
"safe-transmute",
|
||||
"serde",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "wasmer-interface-types-fl"
|
||||
version = "0.17.2"
|
||||
version = "0.17.5"
|
||||
description = "WebAssembly Interface Types library for Wasmer"
|
||||
license = "MIT"
|
||||
authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
|
||||
@ -17,6 +17,7 @@ wast = "8.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = "1.0"
|
||||
safe-transmute = "0.11.0"
|
||||
log = "0.4.11"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
|
21
src/ast.rs
21
src/ast.rs
@ -17,6 +17,16 @@ pub enum TypeKind {
|
||||
Record,
|
||||
}
|
||||
|
||||
/// Represents the function argument type.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct FunctionArg {
|
||||
/// A function argument name.
|
||||
pub name: String,
|
||||
|
||||
/// A function argument type.
|
||||
pub ty: InterfaceType,
|
||||
}
|
||||
|
||||
/// Represents a type.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum Type {
|
||||
@ -26,15 +36,8 @@ pub enum Type {
|
||||
/// (@interface type (func (param i32 i32) (result string)))
|
||||
/// ```
|
||||
Function {
|
||||
/// Name of a function.
|
||||
name: String,
|
||||
|
||||
/// Types for the parameters (`(param …)`).
|
||||
arg_types: Vec<InterfaceType>,
|
||||
|
||||
/// Name of function argument types.
|
||||
// TODO: introduce a struct combines name and type of a field
|
||||
arg_names: Vec<String>,
|
||||
/// Types for the parameters (`(param (name i32))`).
|
||||
arguments: Vec<FunctionArg>,
|
||||
|
||||
/// Types for the results (`(result …)`).
|
||||
output_types: Vec<InterfaceType>,
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Parse the WIT binary representation into an [AST](crate::ast).
|
||||
|
||||
use crate::{ast::*, interpreter::Instruction, types::*, vec1::Vec1};
|
||||
use crate::{ast::*, interpreter::Instruction, types::*};
|
||||
use nom::{
|
||||
error::{make_error, ErrorKind, ParseError},
|
||||
Err, IResult,
|
||||
@ -70,6 +70,32 @@ fn uleb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'i
|
||||
))
|
||||
}
|
||||
|
||||
fn record_field<'input, E: ParseError<&'input [u8]>>(
|
||||
mut input: &'input [u8],
|
||||
) -> IResult<&'input [u8], RecordFieldType, E> {
|
||||
if input.is_empty() {
|
||||
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
||||
}
|
||||
|
||||
consume!((input, name) = owned_string(input)?);
|
||||
consume!((input, ty) = ty(input)?);
|
||||
|
||||
Ok((input, RecordFieldType { name, ty }))
|
||||
}
|
||||
|
||||
fn function_arg<'input, E: ParseError<&'input [u8]>>(
|
||||
mut input: &'input [u8],
|
||||
) -> IResult<&'input [u8], FunctionArg, E> {
|
||||
if input.is_empty() {
|
||||
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
||||
}
|
||||
|
||||
consume!((input, name) = owned_string(input)?);
|
||||
consume!((input, ty) = ty(input)?);
|
||||
|
||||
Ok((input, FunctionArg { name, ty }))
|
||||
}
|
||||
|
||||
/// Parse an interface type.
|
||||
fn ty<'input, E: ParseError<&'input [u8]>>(
|
||||
mut input: &'input [u8],
|
||||
@ -97,9 +123,9 @@ fn ty<'input, E: ParseError<&'input [u8]>>(
|
||||
0x0c => InterfaceType::I32,
|
||||
0x0d => InterfaceType::I64,
|
||||
0x0e => {
|
||||
consume!((input, record_type) = record_type(input)?);
|
||||
consume!((input, record_id) = uleb(input)?);
|
||||
|
||||
InterfaceType::Record(record_type)
|
||||
InterfaceType::Record(record_id)
|
||||
}
|
||||
_ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
|
||||
};
|
||||
@ -111,11 +137,15 @@ fn ty<'input, E: ParseError<&'input [u8]>>(
|
||||
fn record_type<'input, E: ParseError<&'input [u8]>>(
|
||||
input: &'input [u8],
|
||||
) -> IResult<&'input [u8], RecordType, E> {
|
||||
let (output, fields) = list(input, ty)?;
|
||||
use crate::vec1::Vec1;
|
||||
|
||||
let (output, name) = owned_string(input)?;
|
||||
let (output, fields) = list(output, record_field)?;
|
||||
|
||||
Ok((
|
||||
output,
|
||||
RecordType {
|
||||
name,
|
||||
fields: Vec1::new(fields).expect("Record must have at least one field, zero given."),
|
||||
},
|
||||
))
|
||||
@ -262,34 +292,35 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
|
||||
0x38 => (input, Instruction::ByteArrayLowerMemory),
|
||||
0x39 => (input, Instruction::ByteArraySize),
|
||||
|
||||
0x25 => {
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
/*
|
||||
0x25 => {
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLift {
|
||||
type_index: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
0x26 => {
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLower {
|
||||
type_index: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLift {
|
||||
type_index: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
0x26 => {
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLower {
|
||||
type_index: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
*/
|
||||
0x3A => {
|
||||
consume!((input, argument_0) = uleb(input)?);
|
||||
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLiftMemory {
|
||||
type_index: argument_0 as u32,
|
||||
record_type_id: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -299,7 +330,7 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
|
||||
(
|
||||
input,
|
||||
Instruction::RecordLowerMemory {
|
||||
type_index: argument_0 as u32,
|
||||
record_type_id: argument_0 as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -328,15 +359,11 @@ fn types<'input, E: ParseError<&'input [u8]>>(
|
||||
|
||||
match type_kind {
|
||||
TypeKind::Function => {
|
||||
consume!((input, name) = string(input)?);
|
||||
consume!((input, arg_types) = list(input, ty)?);
|
||||
consume!((input, arg_names) = list(input, owned_string)?);
|
||||
consume!((input, arguments) = list(input, function_arg)?);
|
||||
consume!((input, output_types) = list(input, ty)?);
|
||||
|
||||
types.push(Type::Function {
|
||||
name: String::from(name),
|
||||
arg_types,
|
||||
arg_names,
|
||||
arguments,
|
||||
output_types,
|
||||
});
|
||||
}
|
||||
@ -861,8 +888,11 @@ mod tests {
|
||||
Instruction::StringLiftMemory,
|
||||
Instruction::StringLowerMemory,
|
||||
Instruction::StringSize,
|
||||
/*
|
||||
Instruction::RecordLift { type_index: 1 },
|
||||
Instruction::RecordLower { type_index: 1 },
|
||||
|
||||
*/
|
||||
],
|
||||
));
|
||||
|
||||
|
@ -17,6 +17,10 @@ mod keyword {
|
||||
custom_keyword!(record);
|
||||
custom_keyword!(field);
|
||||
|
||||
// Special symbols
|
||||
custom_keyword!(comma = ",");
|
||||
custom_keyword!(colon = ":");
|
||||
|
||||
// New types.
|
||||
custom_keyword!(s8);
|
||||
custom_keyword!(s16);
|
||||
@ -154,22 +158,31 @@ impl Parse<'_> for RecordType {
|
||||
fn parse(parser: Parser<'_>) -> Result<Self> {
|
||||
parser.parse::<keyword::record>()?;
|
||||
|
||||
parser.parse::<keyword::string>()?;
|
||||
let record_name = parser.parse()?;
|
||||
|
||||
let mut fields = vec![];
|
||||
|
||||
while !parser.is_empty() {
|
||||
fields.push(parser.parens(|parser| {
|
||||
parser.parse::<keyword::field>()?;
|
||||
parser.parse::<keyword::string>()?;
|
||||
let name = parser.parse()?;
|
||||
|
||||
parser.parse()
|
||||
parser.parse::<keyword::field>()?;
|
||||
let ty = parser.parse()?;
|
||||
|
||||
Ok(RecordFieldType { name, ty })
|
||||
})?);
|
||||
}
|
||||
|
||||
Ok(RecordType {
|
||||
name: record_name,
|
||||
fields: Vec1::new(fields).expect("Record must have at least one field, zero given."),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::suspicious_else_formatting)]
|
||||
impl<'a> Parse<'a> for Instruction {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn parse(parser: Parser<'a>) -> Result<Self> {
|
||||
@ -339,7 +352,9 @@ impl<'a> Parse<'a> for Instruction {
|
||||
parser.parse::<keyword::byte_array_size>()?;
|
||||
|
||||
Ok(Instruction::ByteArraySize)
|
||||
} else if lookahead.peek::<keyword::record_lift>() {
|
||||
}
|
||||
/*
|
||||
else if lookahead.peek::<keyword::record_lift>() {
|
||||
parser.parse::<keyword::record_lift>()?;
|
||||
|
||||
Ok(Instruction::RecordLift {
|
||||
@ -351,17 +366,19 @@ impl<'a> Parse<'a> for Instruction {
|
||||
Ok(Instruction::RecordLower {
|
||||
type_index: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::record_lift_memory>() {
|
||||
}
|
||||
*/
|
||||
else if lookahead.peek::<keyword::record_lift_memory>() {
|
||||
parser.parse::<keyword::record_lift_memory>()?;
|
||||
|
||||
Ok(Instruction::RecordLiftMemory {
|
||||
type_index: parser.parse()?,
|
||||
record_type_id: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::record_lower_memory>() {
|
||||
parser.parse::<keyword::record_lower_memory>()?;
|
||||
|
||||
Ok(Instruction::RecordLowerMemory {
|
||||
type_index: parser.parse()?,
|
||||
record_type_id: parser.parse()?,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::dup>() {
|
||||
parser.parse::<keyword::dup>()?;
|
||||
@ -403,7 +420,7 @@ impl Parse<'_> for AtInterface {
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum FunctionType {
|
||||
Header(String, Vec<String>, Vec<InterfaceType>),
|
||||
Header(Vec<FunctionArg>),
|
||||
Output(Vec<InterfaceType>),
|
||||
}
|
||||
|
||||
@ -414,17 +431,34 @@ impl Parse<'_> for FunctionType {
|
||||
|
||||
if lookahead.peek::<keyword::param>() {
|
||||
parser.parse::<keyword::param>()?;
|
||||
let func_name = parser.parse()?;
|
||||
|
||||
let mut names = vec![];
|
||||
let mut types = vec![];
|
||||
let mut arguments = vec![];
|
||||
|
||||
while !parser.is_empty() {
|
||||
names.push(parser.parse()?);
|
||||
types.push(parser.parse()?);
|
||||
let arg_name = parser.step(|cursor| {
|
||||
cursor
|
||||
.id()
|
||||
.ok_or_else(|| cursor.error("expecting argument identifier"))
|
||||
})?;
|
||||
|
||||
if !arg_name.ends_with(':') {
|
||||
parser.step(|cursor| {
|
||||
if let Some((":", rest)) = cursor.reserved() {
|
||||
return Ok(("", rest));
|
||||
}
|
||||
Err(cursor.error("expected : between an argument and a type"))
|
||||
})?;
|
||||
}
|
||||
|
||||
let arg_type: InterfaceType = parser.parse()?;
|
||||
|
||||
arguments.push(FunctionArg {
|
||||
name: arg_name.trim_end_matches(':').to_string(),
|
||||
ty: arg_type,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(FunctionType::Header(func_name, names, types))
|
||||
Ok(FunctionType::Header(arguments))
|
||||
} else if lookahead.peek::<keyword::result>() {
|
||||
parser.parse::<keyword::result>()?;
|
||||
|
||||
@ -491,38 +525,22 @@ impl<'a> Parse<'a> for Type {
|
||||
if lookahead.peek::<keyword::func>() {
|
||||
parser.parse::<keyword::func>()?;
|
||||
|
||||
let mut arg_types = vec![];
|
||||
let mut arg_names = vec![];
|
||||
let mut arguments = vec![];
|
||||
let mut output_types = vec![];
|
||||
let mut name: Option<String> = None;
|
||||
|
||||
while !parser.is_empty() {
|
||||
let function_type = parser.parse::<FunctionType>()?;
|
||||
|
||||
match function_type {
|
||||
FunctionType::Header(func_name, mut names, mut types) => {
|
||||
name = Some(func_name);
|
||||
arg_names.append(&mut names);
|
||||
arg_types.append(&mut types);
|
||||
},
|
||||
FunctionType::Header(mut func_arguments) => {
|
||||
arguments.append(&mut func_arguments);
|
||||
}
|
||||
FunctionType::Output(mut outputs) => output_types.append(&mut outputs),
|
||||
}
|
||||
}
|
||||
|
||||
if name.is_none() {
|
||||
return Err(parser.error("Malformed wast: function doesn't contain name"));
|
||||
}
|
||||
|
||||
if arg_types.len() != arg_names.len() {
|
||||
return Err(parser.error("Malformed wast: function argument types count should be equal to argument names count"));
|
||||
}
|
||||
|
||||
// It's has been already checked for None.
|
||||
let name = name.unwrap();
|
||||
Ok(Type::Function {
|
||||
name,
|
||||
arg_types,
|
||||
arg_names,
|
||||
arguments,
|
||||
output_types,
|
||||
})
|
||||
} else if lookahead.peek::<keyword::record>() {
|
||||
@ -878,8 +896,10 @@ mod tests {
|
||||
Instruction::StringLiftMemory,
|
||||
Instruction::StringLowerMemory,
|
||||
Instruction::StringSize,
|
||||
/*
|
||||
Instruction::RecordLift { type_index: 42 },
|
||||
Instruction::RecordLower { type_index: 42 },
|
||||
*/
|
||||
];
|
||||
|
||||
assert_eq!(inputs.len(), outputs.len());
|
||||
|
@ -127,20 +127,32 @@ where
|
||||
InterfaceType::Anyref => 0x0b_u8.to_bytes(writer),
|
||||
InterfaceType::I32 => 0x0c_u8.to_bytes(writer),
|
||||
InterfaceType::I64 => 0x0d_u8.to_bytes(writer),
|
||||
InterfaceType::Record(record_type) => {
|
||||
InterfaceType::Record(record_id) => {
|
||||
0x0e_u8.to_bytes(writer)?;
|
||||
record_type.to_bytes(writer)
|
||||
record_id.to_bytes(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a `RecordType` into bytes.
|
||||
impl<W> ToBytes<W> for RecordFieldType
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
|
||||
self.name.as_str().to_bytes(writer)?;
|
||||
self.ty.to_bytes(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a `RecordType` into bytes.
|
||||
impl<W> ToBytes<W> for RecordType
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
|
||||
self.name.as_str().to_bytes(writer)?;
|
||||
self.fields.to_bytes(writer)
|
||||
}
|
||||
}
|
||||
@ -174,6 +186,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> ToBytes<W> for FunctionArg
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
|
||||
self.name.to_bytes(writer)?;
|
||||
self.ty.to_bytes(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a `Type` into bytes.
|
||||
///
|
||||
/// Decoder is in `decoders::binary::types`.
|
||||
@ -184,15 +206,11 @@ where
|
||||
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
Type::Function {
|
||||
name,
|
||||
arg_types,
|
||||
arg_names,
|
||||
arguments,
|
||||
output_types,
|
||||
} => {
|
||||
TypeKind::Function.to_bytes(writer)?;
|
||||
name.to_bytes(writer)?;
|
||||
arg_types.to_bytes(writer)?;
|
||||
arg_names.to_bytes(writer)?;
|
||||
arguments.to_bytes(writer)?;
|
||||
output_types.to_bytes(writer)?;
|
||||
}
|
||||
|
||||
@ -363,7 +381,7 @@ where
|
||||
Instruction::ByteArrayLiftMemory => 0x37_u8.to_bytes(writer)?,
|
||||
Instruction::ByteArrayLowerMemory => 0x38_u8.to_bytes(writer)?,
|
||||
Instruction::ByteArraySize => 0x39_u8.to_bytes(writer)?,
|
||||
|
||||
/*
|
||||
Instruction::RecordLift { type_index } => {
|
||||
0x25_u8.to_bytes(writer)?;
|
||||
(*type_index as u64).to_bytes(writer)?
|
||||
@ -372,11 +390,16 @@ where
|
||||
0x26_u8.to_bytes(writer)?;
|
||||
(*type_index as u64).to_bytes(writer)?
|
||||
}
|
||||
Instruction::RecordLiftMemory { type_index } => {
|
||||
*/
|
||||
Instruction::RecordLiftMemory {
|
||||
record_type_id: type_index,
|
||||
} => {
|
||||
0x3A_u8.to_bytes(writer)?;
|
||||
(*type_index as u64).to_bytes(writer)?
|
||||
}
|
||||
Instruction::RecordLowerMemory { type_index } => {
|
||||
Instruction::RecordLowerMemory {
|
||||
record_type_id: type_index,
|
||||
} => {
|
||||
0x3B_u8.to_bytes(writer)?;
|
||||
(*type_index as u64).to_bytes(writer)?
|
||||
}
|
||||
|
@ -85,13 +85,18 @@ impl ToString for &InterfaceType {
|
||||
impl ToString for &RecordType {
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"record{fields}",
|
||||
"record {} {{\n{fields}}}",
|
||||
self.name,
|
||||
fields = self
|
||||
.fields
|
||||
.iter()
|
||||
.fold(String::new(), |mut accumulator, interface_type| {
|
||||
.fold(String::new(), |mut accumulator, field_type| {
|
||||
accumulator.push(' ');
|
||||
accumulator.push_str(&format!("(field {})", &interface_type.to_string()));
|
||||
accumulator.push_str(&format!(
|
||||
"{}: {}\n",
|
||||
field_type.name,
|
||||
(&field_type.ty).to_string()
|
||||
));
|
||||
accumulator
|
||||
}),
|
||||
)
|
||||
@ -142,14 +147,16 @@ impl ToString for &Instruction {
|
||||
Instruction::ByteArrayLiftMemory => "byte_array.lift_memory".into(),
|
||||
Instruction::ByteArrayLowerMemory => "byte_array.lower_memory".into(),
|
||||
Instruction::ByteArraySize => "byte_array.size".into(),
|
||||
/*
|
||||
Instruction::RecordLift { type_index } => format!("record.lift {}", type_index),
|
||||
Instruction::RecordLower { type_index } => format!("record.lower {}", type_index),
|
||||
Instruction::RecordLiftMemory { type_index } => {
|
||||
format!("record.lift_memory {}", type_index)
|
||||
}
|
||||
Instruction::RecordLowerMemory { type_index } => {
|
||||
format!("record.lower_memory {}", type_index)
|
||||
}
|
||||
*/
|
||||
Instruction::RecordLiftMemory {
|
||||
record_type_id: type_index,
|
||||
} => format!("record.lift_memory {}", type_index),
|
||||
Instruction::RecordLowerMemory {
|
||||
record_type_id: type_index,
|
||||
} => format!("record.lower_memory {}", type_index),
|
||||
Instruction::Dup => "dup".into(),
|
||||
Instruction::Swap2 => "swap2".into(),
|
||||
}
|
||||
@ -158,17 +165,17 @@ impl ToString for &Instruction {
|
||||
|
||||
/// Encode a list of `InterfaceType` representing inputs into a
|
||||
/// string.
|
||||
fn encode_arguments(arg_types: &[InterfaceType], arg_names: &[String]) -> String {
|
||||
fn encode_function_arguments(arguments: &[FunctionArg]) -> String {
|
||||
// here we know that arg_names and arg_types have the same length
|
||||
if arg_names.is_empty() {
|
||||
if arguments.is_empty() {
|
||||
String::from("")
|
||||
} else {
|
||||
format!(
|
||||
"\n (param{})",
|
||||
arg_names.iter().zip(arg_types.iter()).fold(
|
||||
arguments.iter().fold(
|
||||
String::new(),
|
||||
|mut accumulator, (name, ty)| {
|
||||
accumulator.push(' ');
|
||||
|mut accumulator, FunctionArg { name, ty }| {
|
||||
accumulator.push_str(" $");
|
||||
accumulator.push_str(name);
|
||||
accumulator.push_str(": ");
|
||||
accumulator.push_str(&ty.to_string());
|
||||
@ -203,14 +210,11 @@ impl<'input> ToString for &Type {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Type::Function {
|
||||
name,
|
||||
arg_types,
|
||||
arg_names,
|
||||
arguments,
|
||||
output_types,
|
||||
} => format!(
|
||||
r#"(@interface type (func {name} {args}{output_types}))"#,
|
||||
name = name,
|
||||
args = encode_arguments(arg_types, arg_names),
|
||||
r#"(@interface type (func {args} {output_types}))"#,
|
||||
args = encode_function_arguments(arguments),
|
||||
output_types = output_types_to_result(&output_types),
|
||||
),
|
||||
|
||||
@ -279,14 +283,16 @@ impl<'input> ToString for &Interfaces<'input> {
|
||||
fn to_string(&self) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
let types = self
|
||||
.types
|
||||
.iter()
|
||||
.fold(String::new(), |mut accumulator, ty| {
|
||||
accumulator.push('\n');
|
||||
accumulator.push_str(&ty.to_string());
|
||||
accumulator
|
||||
});
|
||||
let types =
|
||||
self.types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut accumulator, (id, ty)| {
|
||||
accumulator.push('\n');
|
||||
accumulator.push_str(&ty.to_string());
|
||||
accumulator.push_str(&format!(" ;; {}", id));
|
||||
accumulator
|
||||
});
|
||||
|
||||
let imports = self
|
||||
.imports
|
||||
@ -485,8 +491,10 @@ mod tests {
|
||||
(&Instruction::StringLiftMemory).to_string(),
|
||||
(&Instruction::StringLowerMemory).to_string(),
|
||||
(&Instruction::StringSize).to_string(),
|
||||
/*
|
||||
(&Instruction::RecordLift { type_index: 42 }).to_string(),
|
||||
(&Instruction::RecordLower { type_index: 42 }).to_string(),
|
||||
*/
|
||||
];
|
||||
let outputs = vec![
|
||||
"arg.get 7",
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! The error module contains all the data structures that represent
|
||||
//! an error.
|
||||
|
||||
use crate::values::InterfaceValue;
|
||||
use crate::{ast::TypeKind, interpreter::Instruction, types::InterfaceType};
|
||||
use std::{
|
||||
error::Error,
|
||||
@ -21,7 +22,7 @@ pub type InterpreterResult<T> = Result<T, InstructionError>;
|
||||
#[derive(Debug)]
|
||||
pub struct WasmValueNativeCastError {
|
||||
/// The initial type.
|
||||
pub from: InterfaceType,
|
||||
pub from: InterfaceValue,
|
||||
|
||||
/// The targeted type.
|
||||
///
|
||||
@ -98,7 +99,7 @@ pub enum InstructionErrorKind {
|
||||
expected_type: InterfaceType,
|
||||
|
||||
/// The received type.
|
||||
received_type: InterfaceType,
|
||||
received_value: InterfaceValue,
|
||||
},
|
||||
|
||||
/// Need to read some values from the stack, but it doesn't
|
||||
@ -163,6 +164,15 @@ pub enum InstructionErrorKind {
|
||||
type_index: u32,
|
||||
},
|
||||
|
||||
/// The searched by name type doesn't exist.
|
||||
RecordTypeByNameIsMissing {
|
||||
/// The record type name.
|
||||
record_type_id: u64,
|
||||
},
|
||||
|
||||
/// Corrupted record's been popped from the stack.
|
||||
CorruptedRecord(String),
|
||||
|
||||
/// Read a type that has an unexpected type.
|
||||
InvalidTypeKind {
|
||||
/// The expected kind.
|
||||
@ -199,11 +209,11 @@ impl Display for InstructionErrorKind {
|
||||
|
||||
Self::InvalidValueOnTheStack {
|
||||
expected_type,
|
||||
received_type,
|
||||
received_value,
|
||||
} => write!(
|
||||
formatter,
|
||||
"read a value of type `{:?}` from the stack, but the type `{:?}` was expected",
|
||||
received_type, expected_type,
|
||||
"read a value `{:?}` from the stack, that can't be converted to `{:?}`",
|
||||
received_value, expected_type,
|
||||
),
|
||||
|
||||
Self::StackIsTooSmall { needed } => write!(
|
||||
@ -261,6 +271,19 @@ impl Display for InstructionErrorKind {
|
||||
"read a type of kind `{:?}`, but the kind `{:?}` was expected",
|
||||
received_kind, expected_kind
|
||||
),
|
||||
|
||||
Self::RecordTypeByNameIsMissing { record_type_id: type_name } => write!(
|
||||
formatter,
|
||||
"type with `{}` is missing in a Wasm binary",
|
||||
type_name
|
||||
),
|
||||
|
||||
Self::CorruptedRecord(err) => write!(
|
||||
formatter,
|
||||
"{}",
|
||||
err
|
||||
),
|
||||
|
||||
Self::SerdeError(err) => write!(
|
||||
formatter,
|
||||
"serde error: {}", err,
|
||||
|
@ -15,6 +15,8 @@ executable_instruction!(
|
||||
));
|
||||
}
|
||||
|
||||
log::trace!("arg.get: pushing {:?} on the stack", invocation_inputs[index as usize]);
|
||||
|
||||
runtime.stack.push(invocation_inputs[index as usize].clone());
|
||||
|
||||
Ok(())
|
||||
|
@ -59,6 +59,8 @@ executable_instruction!(
|
||||
.map(Cell::get)
|
||||
.collect();
|
||||
|
||||
log::trace!("bytearray.lift_memory: pushing {:?} on the stack", data);
|
||||
|
||||
runtime.stack.push(InterfaceValue::ByteArray(data));
|
||||
|
||||
Ok(())
|
||||
@ -104,6 +106,8 @@ executable_instruction!(
|
||||
memory_view[byte_array_pointer as usize + nth].set(*byte);
|
||||
}
|
||||
|
||||
log::trace!("bytearray.lower_memory: pushing {}, {} on the stack", byte_array_pointer, byte_array_length);
|
||||
|
||||
runtime.stack.push(InterfaceValue::I32(byte_array_pointer as i32));
|
||||
runtime.stack.push(InterfaceValue::I32(byte_array_length));
|
||||
|
||||
@ -127,7 +131,7 @@ executable_instruction!(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: InterfaceType::ByteArray,
|
||||
received_type: (&value).into(),
|
||||
received_value: value,
|
||||
},
|
||||
)),
|
||||
|
||||
|
@ -2,13 +2,12 @@ use crate::{
|
||||
errors::{InstructionError, InstructionErrorKind},
|
||||
interpreter::wasm::structures::{FunctionIndex, TypedIndex},
|
||||
interpreter::Instruction,
|
||||
types::InterfaceType,
|
||||
};
|
||||
|
||||
executable_instruction!(
|
||||
call_core(function_index: u32, instruction: Instruction) -> _ {
|
||||
move |runtime| -> _ {
|
||||
let instance = &mut runtime.wasm_instance;
|
||||
let instance = &runtime.wasm_instance;
|
||||
let index = FunctionIndex::new(function_index as usize);
|
||||
|
||||
let local_or_import = instance.local_or_import(index).ok_or_else(|| {
|
||||
@ -29,21 +28,10 @@ executable_instruction!(
|
||||
},
|
||||
)
|
||||
})?;
|
||||
let input_types = inputs
|
||||
.iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<InterfaceType>>();
|
||||
|
||||
if input_types != local_or_import.inputs() {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::LocalOrImportSignatureMismatch {
|
||||
function_index,
|
||||
expected: (local_or_import.inputs().to_vec(), vec![]),
|
||||
received: (input_types, vec![]),
|
||||
},
|
||||
));
|
||||
}
|
||||
super::check_function_signature(&**instance, local_or_import, &inputs, instruction)?;
|
||||
|
||||
log::trace!("call-core: calling {} with arguments: {:?}", function_index, inputs);
|
||||
|
||||
let outputs = local_or_import.call(&inputs).map_err(|_| {
|
||||
InstructionError::new(
|
||||
@ -54,7 +42,7 @@ executable_instruction!(
|
||||
)
|
||||
})?;
|
||||
|
||||
|
||||
log::trace!("call-core: call to {} succeeded with result {:?}", function_index, outputs);
|
||||
|
||||
for output in outputs.into_iter() {
|
||||
runtime.stack.push(output)
|
||||
|
@ -14,6 +14,7 @@ executable_instruction!(
|
||||
})?;
|
||||
|
||||
let value = value.clone();
|
||||
log::trace!("dup: duplication {:?} value on the stack", value);
|
||||
runtime.stack.push(value);
|
||||
|
||||
Ok(())
|
||||
|
@ -7,6 +7,9 @@ mod records;
|
||||
mod strings;
|
||||
mod swap2;
|
||||
|
||||
use crate::interpreter::wasm;
|
||||
use crate::types::InterfaceType;
|
||||
use crate::vec1::Vec1;
|
||||
use crate::{
|
||||
errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError},
|
||||
values::{InterfaceValue, NativeType},
|
||||
@ -153,6 +156,7 @@ pub enum Instruction {
|
||||
/// The `string.size` instruction.
|
||||
ByteArraySize,
|
||||
|
||||
/*
|
||||
/// The `record.lift` instruction.
|
||||
RecordLift {
|
||||
/// The type index of the record.
|
||||
@ -165,16 +169,17 @@ pub enum Instruction {
|
||||
type_index: u32,
|
||||
},
|
||||
|
||||
*/
|
||||
/// The `record.lift_memory` instruction.
|
||||
RecordLiftMemory {
|
||||
/// The type index of the record.
|
||||
type_index: u32,
|
||||
record_type_id: u32,
|
||||
},
|
||||
|
||||
/// The `record.lower_memory` instruction.
|
||||
RecordLowerMemory {
|
||||
/// The type index of the record.
|
||||
type_index: u32,
|
||||
record_type_id: u32,
|
||||
},
|
||||
|
||||
/// The `dup` instructions.
|
||||
@ -197,6 +202,143 @@ where
|
||||
.map_err(|error| InstructionError::new(instruction, InstructionErrorKind::ToNative(error)))
|
||||
}
|
||||
|
||||
pub(crate) fn check_function_signature<
|
||||
'instance,
|
||||
Instance,
|
||||
Export,
|
||||
LocalImport,
|
||||
Memory,
|
||||
MemoryView,
|
||||
>(
|
||||
instance: &'instance Instance,
|
||||
local_import: &LocalImport,
|
||||
values: &[InterfaceValue],
|
||||
instruction: Instruction,
|
||||
) -> Result<(), InstructionError>
|
||||
where
|
||||
Export: wasm::structures::Export + 'instance,
|
||||
LocalImport: wasm::structures::LocalImport + 'instance,
|
||||
Memory: wasm::structures::Memory<MemoryView> + 'instance,
|
||||
MemoryView: wasm::structures::MemoryView,
|
||||
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
|
||||
{
|
||||
let func_inputs = local_import.arguments();
|
||||
|
||||
for (func_input_arg, value) in func_inputs.iter().zip(values.iter()) {
|
||||
is_value_compatible_to_type(instance, &func_input_arg.ty, value, instruction)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether the provided value could be a value of the provided type.
|
||||
pub(crate) fn is_value_compatible_to_type<
|
||||
'instance,
|
||||
Instance,
|
||||
Export,
|
||||
LocalImport,
|
||||
Memory,
|
||||
MemoryView,
|
||||
>(
|
||||
instance: &'instance Instance,
|
||||
interface_type: &InterfaceType,
|
||||
interface_value: &InterfaceValue,
|
||||
instruction: Instruction,
|
||||
) -> Result<(), InstructionError>
|
||||
where
|
||||
Export: wasm::structures::Export + 'instance,
|
||||
LocalImport: wasm::structures::LocalImport + 'instance,
|
||||
Memory: wasm::structures::Memory<MemoryView> + 'instance,
|
||||
MemoryView: wasm::structures::MemoryView,
|
||||
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
|
||||
{
|
||||
match (&interface_type, interface_value) {
|
||||
(InterfaceType::S8, InterfaceValue::S8(_)) => Ok(()),
|
||||
(InterfaceType::S16, InterfaceValue::S16(_)) => Ok(()),
|
||||
(InterfaceType::S32, InterfaceValue::S32(_)) => Ok(()),
|
||||
(InterfaceType::S64, InterfaceValue::S64(_)) => Ok(()),
|
||||
(InterfaceType::U8, InterfaceValue::U8(_)) => Ok(()),
|
||||
(InterfaceType::U16, InterfaceValue::U16(_)) => Ok(()),
|
||||
(InterfaceType::U32, InterfaceValue::U32(_)) => Ok(()),
|
||||
(InterfaceType::U64, InterfaceValue::U64(_)) => Ok(()),
|
||||
(InterfaceType::I32, InterfaceValue::I32(_)) => Ok(()),
|
||||
(InterfaceType::I64, InterfaceValue::I64(_)) => Ok(()),
|
||||
(InterfaceType::F32, InterfaceValue::F32(_)) => Ok(()),
|
||||
(InterfaceType::F64, InterfaceValue::F64(_)) => Ok(()),
|
||||
(InterfaceType::String, InterfaceValue::String(_)) => Ok(()),
|
||||
(InterfaceType::ByteArray, InterfaceValue::ByteArray(_)) => Ok(()),
|
||||
(InterfaceType::Record(ref record_type_id), InterfaceValue::Record(record_fields)) => {
|
||||
is_record_fields_compatible_to_type(
|
||||
instance,
|
||||
*record_type_id,
|
||||
record_fields,
|
||||
instruction,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: interface_type.clone(),
|
||||
received_value: interface_value.clone(),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_record_fields_compatible_to_type<
|
||||
'instance,
|
||||
Instance,
|
||||
Export,
|
||||
LocalImport,
|
||||
Memory,
|
||||
MemoryView,
|
||||
>(
|
||||
instance: &'instance Instance,
|
||||
record_type_id: u64,
|
||||
record_fields: &[InterfaceValue],
|
||||
instruction: Instruction,
|
||||
) -> Result<(), InstructionError>
|
||||
where
|
||||
Export: wasm::structures::Export + 'instance,
|
||||
LocalImport: wasm::structures::LocalImport + 'instance,
|
||||
Memory: wasm::structures::Memory<MemoryView> + 'instance,
|
||||
MemoryView: wasm::structures::MemoryView,
|
||||
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
|
||||
{
|
||||
let record_type = instance.wit_record_by_id(record_type_id).ok_or_else(|| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::RecordTypeByNameIsMissing { record_type_id },
|
||||
)
|
||||
})?;
|
||||
|
||||
if record_fields.len() != record_type.fields.len() {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: InterfaceType::Record(record_type_id),
|
||||
// unwrap is safe here - len's been already checked
|
||||
received_value: InterfaceValue::Record(Vec1::new(record_fields.to_vec()).unwrap()),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
for (record_type_field, record_value_field) in
|
||||
record_type.fields.iter().zip(record_fields.iter())
|
||||
{
|
||||
is_value_compatible_to_type(
|
||||
instance,
|
||||
&record_type_field.ty,
|
||||
record_value_field,
|
||||
instruction,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use crate::{ast::*, interpreter::wasm, types::*, values::*};
|
||||
@ -217,7 +359,7 @@ pub(crate) mod tests {
|
||||
self.outputs.len()
|
||||
}
|
||||
|
||||
fn inputs(&self) -> &[InterfaceType] {
|
||||
fn arguments(&self) -> &[InterfaceType] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
@ -245,7 +387,7 @@ pub(crate) mod tests {
|
||||
self.outputs.len()
|
||||
}
|
||||
|
||||
fn inputs(&self) -> &[InterfaceType] {
|
||||
fn arguments(&self) -> &[InterfaceType] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
@ -353,12 +495,32 @@ pub(crate) mod tests {
|
||||
},
|
||||
memory: Memory::new(vec![Cell::new(0); 128]),
|
||||
wit_types: vec![Type::Record(RecordType {
|
||||
name: String::from("RecordType0"),
|
||||
fields: vec1![
|
||||
InterfaceType::I32,
|
||||
InterfaceType::Record(RecordType {
|
||||
fields: vec1![InterfaceType::String, InterfaceType::F32],
|
||||
}),
|
||||
InterfaceType::I64,
|
||||
RecordFieldType {
|
||||
name: String::from("field_0"),
|
||||
ty: InterfaceType::I32,
|
||||
},
|
||||
RecordFieldType {
|
||||
name: String::from("field_1"),
|
||||
ty: InterfaceType::Record(RecordType {
|
||||
name: String::from("RecordType1"),
|
||||
fields: vec1![
|
||||
RecordFieldType {
|
||||
name: String::from("field_0"),
|
||||
ty: InterfaceType::String,
|
||||
},
|
||||
RecordFieldType {
|
||||
name: String::from("field1"),
|
||||
ty: InterfaceType::F32
|
||||
}
|
||||
],
|
||||
}),
|
||||
},
|
||||
RecordFieldType {
|
||||
name: String::from("field_2"),
|
||||
ty: InterfaceType::I64,
|
||||
}
|
||||
],
|
||||
})],
|
||||
}
|
||||
@ -381,8 +543,12 @@ pub(crate) mod tests {
|
||||
Some(&self.memory)
|
||||
}
|
||||
|
||||
fn wit_type(&self, index: u32) -> Option<&Type> {
|
||||
fn wit_type_by_id(&self, index: u32) -> Option<&Type> {
|
||||
self.wit_types.get(index as usize)
|
||||
}
|
||||
|
||||
fn wit_record_by_id(&self, index: u64) -> Option<&RecordType> {
|
||||
self.wit_types.get(index as _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,25 +15,30 @@ macro_rules! lowering_lifting {
|
||||
Some(InterfaceValue::$from_variant(value)) => {
|
||||
runtime
|
||||
.stack
|
||||
.push(InterfaceValue::$to_variant(value.try_into().map_err(
|
||||
|_| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::LoweringLifting {
|
||||
from: InterfaceType::$from_variant,
|
||||
to: InterfaceType::$to_variant
|
||||
},
|
||||
)
|
||||
},
|
||||
)?))
|
||||
}
|
||||
.push({
|
||||
let converted_value = InterfaceValue::$to_variant(value.try_into().map_err(
|
||||
|_| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::LoweringLifting {
|
||||
from: InterfaceType::$from_variant,
|
||||
to: InterfaceType::$to_variant
|
||||
},
|
||||
)
|
||||
},
|
||||
)?);
|
||||
|
||||
log::trace!("{}: converting {:?} to {:?}", $instruction_name, value, converted_value);
|
||||
|
||||
converted_value
|
||||
})
|
||||
}
|
||||
Some(wrong_value) => {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: InterfaceType::$from_variant,
|
||||
received_type: (&wrong_value).into(),
|
||||
received_value: wrong_value,
|
||||
}
|
||||
))
|
||||
},
|
||||
|
@ -3,24 +3,19 @@ mod utils;
|
||||
use utils::read_from_instance_mem;
|
||||
use utils::write_to_instance_mem;
|
||||
|
||||
// use crate::interpreter::wasm;
|
||||
|
||||
use crate::interpreter::instructions::to_native;
|
||||
use crate::interpreter::instructions::{is_record_fields_compatible_to_type, to_native};
|
||||
use crate::{
|
||||
ast::{Type, TypeKind},
|
||||
errors::{InstructionError, InstructionErrorKind},
|
||||
interpreter::{
|
||||
stack::{Stack, Stackable},
|
||||
Instruction,
|
||||
},
|
||||
interpreter::Instruction,
|
||||
types::{InterfaceType, RecordType},
|
||||
values::{FlattenInterfaceValueIterator, InterfaceValue},
|
||||
values::InterfaceValue,
|
||||
vec1::Vec1,
|
||||
};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryInto;
|
||||
|
||||
/*
|
||||
/// Build an `InterfaceValue::Record` based on values on the stack.
|
||||
///
|
||||
/// To fill a record, every field `field_1` to `field_n` must get its
|
||||
@ -105,10 +100,11 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
fn record_lift_memory_<'instance, Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
instance: &'instance mut Instance,
|
||||
record_type: RecordType,
|
||||
instance: &'instance Instance,
|
||||
record_type: &RecordType,
|
||||
offset: usize,
|
||||
instruction: Instruction,
|
||||
) -> Result<InterfaceValue, InstructionError>
|
||||
@ -123,8 +119,8 @@ where
|
||||
fn record_size(record_type: &RecordType) -> usize {
|
||||
let mut record_size = 0;
|
||||
|
||||
for ty in record_type.fields.iter() {
|
||||
let params_count = match ty {
|
||||
for field_type in record_type.fields.iter() {
|
||||
let params_count = match field_type.ty {
|
||||
InterfaceType::String | InterfaceType::ByteArray => 2,
|
||||
_ => 1,
|
||||
};
|
||||
@ -137,16 +133,16 @@ where
|
||||
|
||||
let length = record_type.fields.len();
|
||||
let mut values = VecDeque::with_capacity(length);
|
||||
let size = record_size(&record_type);
|
||||
let size = record_size(record_type);
|
||||
let data = read_from_instance_mem(instance, instruction, offset, size)?;
|
||||
// TODO: add error handling
|
||||
let data =
|
||||
safe_transmute::transmute_many::<u64, safe_transmute::SingleManyGuard>(&data).unwrap();
|
||||
|
||||
let mut field_id = 0;
|
||||
for field in record_type.fields.into_vec() {
|
||||
for field in (*record_type.fields).iter() {
|
||||
let value = data[field_id];
|
||||
match field {
|
||||
match &field.ty {
|
||||
InterfaceType::S8 => {
|
||||
values.push_back(InterfaceValue::S8(value as _));
|
||||
}
|
||||
@ -199,7 +195,7 @@ where
|
||||
let string = String::from_utf8(string_mem).unwrap();
|
||||
values.push_back(InterfaceValue::String(string));
|
||||
} else {
|
||||
values.push_back(InterfaceValue::String("".to_string()));
|
||||
values.push_back(InterfaceValue::String(String::new()));
|
||||
}
|
||||
}
|
||||
InterfaceType::ByteArray => {
|
||||
@ -220,9 +216,18 @@ where
|
||||
values.push_back(InterfaceValue::ByteArray(vec![]));
|
||||
}
|
||||
}
|
||||
InterfaceType::Record(record_type) => {
|
||||
InterfaceType::Record(record_type_id) => {
|
||||
let offset = value;
|
||||
|
||||
let record_type = instance.wit_record_by_id(*record_type_id).ok_or_else(|| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::RecordTypeByNameIsMissing {
|
||||
record_type_id: *record_type_id,
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
values.push_back(record_lift_memory_(
|
||||
instance,
|
||||
record_type,
|
||||
@ -243,7 +248,7 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn record_lift_memory<Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
type_index: u32,
|
||||
record_type_id: u64,
|
||||
instruction: Instruction,
|
||||
) -> crate::interpreter::ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView>
|
||||
where
|
||||
@ -271,26 +276,17 @@ where
|
||||
.map_err(|k| InstructionError::new(instruction, k))?;
|
||||
|
||||
// TODO: size = 0
|
||||
let instance = &mut runtime.wasm_instance;
|
||||
let record_type = match instance.wit_type(type_index).ok_or_else(|| {
|
||||
let instance = &runtime.wasm_instance;
|
||||
let record_type = instance.wit_record_by_id(record_type_id).ok_or_else(|| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::TypeIsMissing { type_index },
|
||||
InstructionErrorKind::RecordTypeByNameIsMissing { record_type_id },
|
||||
)
|
||||
})? {
|
||||
Type::Record(record_type) => record_type.clone(),
|
||||
Type::Function { .. } => {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidTypeKind {
|
||||
expected_kind: TypeKind::Record,
|
||||
received_kind: TypeKind::Function,
|
||||
},
|
||||
))
|
||||
}
|
||||
};
|
||||
})?;
|
||||
|
||||
let record = record_lift_memory_(*instance, record_type, offset, instruction)?;
|
||||
let record = record_lift_memory_(&**instance, record_type, offset, instruction)?;
|
||||
|
||||
log::trace!("record.lift_memory: pushing {:?} on the stack", record);
|
||||
runtime.stack.push(record);
|
||||
|
||||
Ok(())
|
||||
@ -349,9 +345,10 @@ where
|
||||
result.push(value.len() as _);
|
||||
}
|
||||
|
||||
InterfaceValue::Record(record) => {
|
||||
let record_ptr = record_lower_memory_(instance, instruction, record)?;
|
||||
InterfaceValue::Record(values) => {
|
||||
let record_ptr = record_lower_memory_(instance, instruction, values)?;
|
||||
|
||||
log::trace!("record.lower_memory: pushing {:?} on the stack", record_ptr);
|
||||
result.push(record_ptr as _);
|
||||
}
|
||||
}
|
||||
@ -364,7 +361,7 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn record_lower_memory<Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
type_index: u32,
|
||||
record_type_id: u64,
|
||||
instruction: Instruction,
|
||||
) -> crate::interpreter::ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView>
|
||||
where
|
||||
@ -380,41 +377,16 @@ where
|
||||
Box::new({
|
||||
move |runtime| -> _ {
|
||||
let instance = &mut runtime.wasm_instance;
|
||||
let record_type = match instance.wit_type(type_index).ok_or_else(|| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::TypeIsMissing { type_index },
|
||||
)
|
||||
})? {
|
||||
Type::Record(record_type) => record_type,
|
||||
Type::Function { .. } => {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidTypeKind {
|
||||
expected_kind: TypeKind::Record,
|
||||
received_kind: TypeKind::Function,
|
||||
},
|
||||
))
|
||||
}
|
||||
};
|
||||
match runtime.stack.pop1() {
|
||||
Some(InterfaceValue::Record(record_values))
|
||||
if record_type == &(&*record_values).into() =>
|
||||
{
|
||||
/*
|
||||
let value: Vec<u8> = crate::serde::de::from_interface_values(&record_values)
|
||||
.map_err(|e| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::SerdeError(e.to_string()),
|
||||
)
|
||||
})?;
|
||||
|
||||
let value_pointer = write_to_instance_mem(*instance, instruction, &value)?;
|
||||
runtime.stack.push(InterfaceValue::I32(value_pointer));
|
||||
runtime.stack.push(InterfaceValue::I32(value.len() as _));
|
||||
*/
|
||||
let offset = record_lower_memory_(*instance, instruction, record_values)?;
|
||||
match runtime.stack.pop1() {
|
||||
Some(InterfaceValue::Record(record_fields)) => {
|
||||
is_record_fields_compatible_to_type(
|
||||
&**instance,
|
||||
record_type_id,
|
||||
&record_fields,
|
||||
instruction,
|
||||
)?;
|
||||
let offset = record_lower_memory_(*instance, instruction, record_fields)?;
|
||||
runtime.stack.push(InterfaceValue::I32(offset));
|
||||
|
||||
Ok(())
|
||||
@ -422,8 +394,8 @@ where
|
||||
Some(value) => Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: InterfaceType::Record(record_type.clone()),
|
||||
received_type: (&value).into(),
|
||||
expected_type: InterfaceType::Record(record_type_id),
|
||||
received_value: value,
|
||||
},
|
||||
)),
|
||||
None => Err(InstructionError::new(
|
||||
@ -435,6 +407,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn record_lower<Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
type_index: u32,
|
||||
instruction: Instruction,
|
||||
@ -494,3 +467,4 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
@ -42,7 +42,7 @@ where
|
||||
}
|
||||
|
||||
pub(super) fn write_to_instance_mem<'instance, Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
instance: &'instance mut Instance,
|
||||
instance: &'instance Instance,
|
||||
instruction: Instruction,
|
||||
bytes: &[u8],
|
||||
) -> Result<i32, InstructionError>
|
||||
@ -74,7 +74,7 @@ where
|
||||
}
|
||||
|
||||
pub(super) fn allocate<'instance, Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
instance: &'instance mut Instance,
|
||||
instance: &'instance Instance,
|
||||
instruction: Instruction,
|
||||
size: i32,
|
||||
) -> Result<i32, InstructionError>
|
||||
@ -105,7 +105,7 @@ where
|
||||
}
|
||||
|
||||
pub(super) fn deallocate<'instance, Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
instance: &'instance mut Instance,
|
||||
instance: &'instance Instance,
|
||||
instruction: Instruction,
|
||||
mem_ptr: i32,
|
||||
size: i32,
|
||||
@ -128,7 +128,7 @@ where
|
||||
}
|
||||
|
||||
fn call_core<'instance, Instance, Export, LocalImport, Memory, MemoryView>(
|
||||
instance: &'instance mut Instance,
|
||||
instance: &'instance Instance,
|
||||
function_index: u32,
|
||||
instruction: Instruction,
|
||||
inputs: Vec<InterfaceValue>,
|
||||
@ -147,20 +147,14 @@ where
|
||||
InstructionErrorKind::LocalOrImportIsMissing { function_index },
|
||||
)
|
||||
})?;
|
||||
let input_types = inputs
|
||||
.iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<InterfaceType>>();
|
||||
if input_types != local_or_import.inputs() {
|
||||
return Err(InstructionError::new(
|
||||
instruction,
|
||||
InstructionErrorKind::LocalOrImportSignatureMismatch {
|
||||
function_index,
|
||||
expected: (local_or_import.inputs().to_vec(), vec![]),
|
||||
received: (input_types, vec![]),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
crate::interpreter::instructions::check_function_signature(
|
||||
instance,
|
||||
local_or_import,
|
||||
&inputs,
|
||||
instruction,
|
||||
)?;
|
||||
|
||||
let outputs = local_or_import.call(&inputs).map_err(|_| {
|
||||
InstructionError::new(
|
||||
instruction,
|
||||
|
@ -62,6 +62,7 @@ executable_instruction!(
|
||||
let string = String::from_utf8(data)
|
||||
.map_err(|error| InstructionError::new(instruction, InstructionErrorKind::String(error)))?;
|
||||
|
||||
log::trace!("string.lift_memory: pushing {:?} on the stack", string);
|
||||
runtime.stack.push(InterfaceValue::String(string));
|
||||
|
||||
Ok(())
|
||||
@ -108,6 +109,7 @@ executable_instruction!(
|
||||
memory_view[string_pointer as usize + nth].set(*byte);
|
||||
}
|
||||
|
||||
log::trace!("string.lower_memory: pushing {}, {} on the stack", string_pointer, string_length);
|
||||
runtime.stack.push(InterfaceValue::I32(string_pointer as i32));
|
||||
runtime.stack.push(InterfaceValue::I32(string_length));
|
||||
|
||||
@ -122,6 +124,8 @@ executable_instruction!(
|
||||
match runtime.stack.pop1() {
|
||||
Some(InterfaceValue::String(string)) => {
|
||||
let length = string.len() as i32;
|
||||
|
||||
log::trace!("string.size: pushing {} on the stack", length);
|
||||
runtime.stack.push(InterfaceValue::I32(length));
|
||||
|
||||
Ok(())
|
||||
@ -131,7 +135,7 @@ executable_instruction!(
|
||||
instruction,
|
||||
InstructionErrorKind::InvalidValueOnTheStack {
|
||||
expected_type: InterfaceType::String,
|
||||
received_type: (&value).into(),
|
||||
received_value: (&value).clone(),
|
||||
},
|
||||
)),
|
||||
|
||||
|
@ -13,6 +13,7 @@ executable_instruction!(
|
||||
)
|
||||
})?;
|
||||
|
||||
log::trace!("swap2: swapping {:?}, {:?} values on the stack", values[0], values[1]);
|
||||
runtime.stack.push(values.remove(1));
|
||||
runtime.stack.push(values.remove(0));
|
||||
|
||||
|
@ -245,18 +245,19 @@ where
|
||||
}
|
||||
Instruction::ByteArraySize => instructions::byte_array_size(*instruction),
|
||||
|
||||
/*
|
||||
Instruction::RecordLift { type_index } => {
|
||||
instructions::record_lift(*type_index, *instruction)
|
||||
}
|
||||
Instruction::RecordLower { type_index } => {
|
||||
instructions::record_lower(*type_index, *instruction)
|
||||
}
|
||||
|
||||
Instruction::RecordLiftMemory { type_index } => {
|
||||
instructions::record_lift_memory(*type_index, *instruction)
|
||||
*/
|
||||
Instruction::RecordLiftMemory { record_type_id } => {
|
||||
instructions::record_lift_memory(*record_type_id as _, *instruction)
|
||||
}
|
||||
Instruction::RecordLowerMemory { type_index } => {
|
||||
instructions::record_lower_memory(*type_index, *instruction)
|
||||
Instruction::RecordLowerMemory { record_type_id } => {
|
||||
instructions::record_lower_memory(*record_type_id as _, *instruction)
|
||||
}
|
||||
Instruction::Dup => instructions::dup(*instruction),
|
||||
Instruction::Swap2 => instructions::swap2(*instruction),
|
||||
|
@ -1,6 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::{ast, types::InterfaceType, values::InterfaceValue};
|
||||
use crate::ast::FunctionArg;
|
||||
use crate::types::RecordType;
|
||||
use crate::{types::InterfaceType, values::InterfaceValue};
|
||||
use std::{cell::Cell, ops::Deref};
|
||||
|
||||
pub trait TypedIndex: Copy + Clone {
|
||||
@ -42,7 +44,7 @@ impl LocalImportIndex for FunctionIndex {
|
||||
pub trait Export {
|
||||
fn inputs_cardinality(&self) -> usize;
|
||||
fn outputs_cardinality(&self) -> usize;
|
||||
fn inputs(&self) -> &[InterfaceType];
|
||||
fn arguments(&self) -> &[FunctionArg];
|
||||
fn outputs(&self) -> &[InterfaceType];
|
||||
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
|
||||
}
|
||||
@ -50,7 +52,7 @@ pub trait Export {
|
||||
pub trait LocalImport {
|
||||
fn inputs_cardinality(&self) -> usize;
|
||||
fn outputs_cardinality(&self) -> usize;
|
||||
fn inputs(&self) -> &[InterfaceType];
|
||||
fn arguments(&self) -> &[FunctionArg];
|
||||
fn outputs(&self) -> &[InterfaceType];
|
||||
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
|
||||
}
|
||||
@ -72,9 +74,9 @@ where
|
||||
MV: MemoryView,
|
||||
{
|
||||
fn export(&self, export_name: &str) -> Option<&E>;
|
||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, index: I) -> Option<&LI>;
|
||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&self, index: I) -> Option<&LI>;
|
||||
fn memory(&self, index: usize) -> Option<&M>;
|
||||
fn wit_type(&self, index: u32) -> Option<&ast::Type>;
|
||||
fn wit_record_by_id(&self, index: u64) -> Option<&RecordType>;
|
||||
}
|
||||
|
||||
impl Export for () {
|
||||
@ -86,7 +88,7 @@ impl Export for () {
|
||||
0
|
||||
}
|
||||
|
||||
fn inputs(&self) -> &[InterfaceType] {
|
||||
fn arguments(&self) -> &[FunctionArg] {
|
||||
&[]
|
||||
}
|
||||
|
||||
@ -108,7 +110,7 @@ impl LocalImport for () {
|
||||
0
|
||||
}
|
||||
|
||||
fn inputs(&self) -> &[InterfaceType] {
|
||||
fn arguments(&self) -> &[FunctionArg] {
|
||||
&[]
|
||||
}
|
||||
|
||||
@ -154,11 +156,11 @@ where
|
||||
None
|
||||
}
|
||||
|
||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, _index: I) -> Option<&LI> {
|
||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&self, _index: I) -> Option<&LI> {
|
||||
None
|
||||
}
|
||||
|
||||
fn wit_type(&self, _index: u32) -> Option<&ast::Type> {
|
||||
fn wit_record_by_id(&self, _index: u64) -> Option<&RecordType> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ macro_rules! next {
|
||||
|
||||
Some(wrong_value) => Err(DeserializeError::TypeMismatch {
|
||||
expected_type: InterfaceType::$variant,
|
||||
received_type: (*wrong_value).into(),
|
||||
received_value: (*wrong_value).clone(),
|
||||
}),
|
||||
|
||||
None => Err(DeserializeError::InputEmpty),
|
||||
@ -122,7 +122,7 @@ impl<'de> Deserializer<'de> {
|
||||
|
||||
Some(wrong_value) => Err(DeserializeError::TypeMismatch {
|
||||
expected_type: InterfaceType::String,
|
||||
received_type: (*wrong_value).into(),
|
||||
received_value: (*wrong_value).clone(),
|
||||
}),
|
||||
|
||||
None => Err(DeserializeError::InputEmpty),
|
||||
@ -139,7 +139,7 @@ impl<'de> Deserializer<'de> {
|
||||
|
||||
Some(wrong_value) => Err(DeserializeError::TypeMismatch {
|
||||
expected_type: InterfaceType::ByteArray,
|
||||
received_type: (*wrong_value).into(),
|
||||
received_value: (*wrong_value).clone(),
|
||||
}),
|
||||
|
||||
None => Err(DeserializeError::InputEmpty),
|
||||
@ -165,7 +165,7 @@ pub enum DeserializeError {
|
||||
expected_type: InterfaceType,
|
||||
|
||||
/// The received type.
|
||||
received_type: InterfaceType,
|
||||
received_value: InterfaceValue,
|
||||
},
|
||||
|
||||
/// Arbitrary message.
|
||||
@ -186,11 +186,11 @@ impl Display for DeserializeError {
|
||||
Self::InputEmpty => write!(formatter, "Unexpected end of input"),
|
||||
Self::TypeMismatch {
|
||||
ref expected_type,
|
||||
ref received_type,
|
||||
ref received_value,
|
||||
} => write!(
|
||||
formatter,
|
||||
"Type mismatch detected, expected `{:?}` but received `{:?}`",
|
||||
expected_type, received_type
|
||||
"Type mismatch detected: `{:?}` can't be converted to `{:?}`",
|
||||
received_value, expected_type,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -220,7 +220,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
||||
Some(InterfaceValue::ByteArray(_)) => self.deserialize_bytes(visitor),
|
||||
Some(InterfaceValue::I32(_)) => self.deserialize_i32(visitor),
|
||||
Some(InterfaceValue::I64(_)) => self.deserialize_i64(visitor),
|
||||
Some(InterfaceValue::Record(_)) => unreachable!("Records should have been flattened."), // already flattened
|
||||
Some(InterfaceValue::Record(..)) => unreachable!("Records should have been flattened."), // already flattened
|
||||
None => Err(DeserializeError::InputEmpty),
|
||||
}
|
||||
}
|
||||
|
39
src/types.rs
39
src/types.rs
@ -4,7 +4,7 @@ use crate::vec1::Vec1;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents the types supported by WIT.
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
|
||||
pub enum InterfaceType {
|
||||
/// A 8-bits signed integer.
|
||||
S8,
|
||||
@ -51,15 +51,42 @@ pub enum InterfaceType {
|
||||
/// A 64-bits integer (as defiend in WebAssembly core).
|
||||
I64,
|
||||
|
||||
/// A record.
|
||||
Record(RecordType),
|
||||
/// A record contains record index from interfaces AST.
|
||||
Record(u64),
|
||||
}
|
||||
|
||||
/// Represents a record field type.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
|
||||
pub struct RecordFieldType {
|
||||
// TODO: make name optional to support structures with anonymous fields in Rust
|
||||
/// A field name.
|
||||
pub name: String,
|
||||
|
||||
/// A field type.
|
||||
pub ty: InterfaceType,
|
||||
}
|
||||
|
||||
/// Represents a record type.
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
|
||||
pub struct RecordType {
|
||||
/// Types representing the fields.
|
||||
/// A record name.
|
||||
pub name: String,
|
||||
|
||||
/// Types and names representing the fields.
|
||||
/// A record must have at least one field, hence the
|
||||
/// [`Vec1`][crate::vec1::Vec1].
|
||||
pub fields: Vec1<InterfaceType>,
|
||||
pub fields: Vec1<RecordFieldType>,
|
||||
}
|
||||
|
||||
impl Default for RecordType {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
fields: Vec1::new(vec![RecordFieldType {
|
||||
name: String::new(),
|
||||
ty: InterfaceType::S8,
|
||||
}])
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
//! Defines WIT values and associated operations.
|
||||
|
||||
use crate::{
|
||||
errors::WasmValueNativeCastError,
|
||||
types::{InterfaceType, RecordType},
|
||||
vec1::Vec1,
|
||||
};
|
||||
use crate::{errors::WasmValueNativeCastError, types::InterfaceType, vec1::Vec1};
|
||||
use std::{convert::TryFrom, slice::Iter};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
@ -60,44 +56,12 @@ pub enum InterfaceValue {
|
||||
Record(Vec1<InterfaceValue>),
|
||||
}
|
||||
|
||||
impl From<&InterfaceValue> for InterfaceType {
|
||||
fn from(value: &InterfaceValue) -> Self {
|
||||
match value {
|
||||
InterfaceValue::S8(_) => Self::S8,
|
||||
InterfaceValue::S16(_) => Self::S16,
|
||||
InterfaceValue::S32(_) => Self::S32,
|
||||
InterfaceValue::S64(_) => Self::S64,
|
||||
InterfaceValue::U8(_) => Self::U8,
|
||||
InterfaceValue::U16(_) => Self::U16,
|
||||
InterfaceValue::U32(_) => Self::U32,
|
||||
InterfaceValue::U64(_) => Self::U64,
|
||||
InterfaceValue::F32(_) => Self::F32,
|
||||
InterfaceValue::F64(_) => Self::F64,
|
||||
InterfaceValue::String(_) => Self::String,
|
||||
InterfaceValue::ByteArray(_) => Self::ByteArray,
|
||||
//InterfaceValue::Anyref(_) => Self::Anyref,
|
||||
InterfaceValue::I32(_) => Self::I32,
|
||||
InterfaceValue::I64(_) => Self::I64,
|
||||
InterfaceValue::Record(values) => Self::Record((&**values).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InterfaceValue {
|
||||
fn default() -> Self {
|
||||
Self::I32(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<InterfaceValue>> for RecordType {
|
||||
fn from(values: &Vec<InterfaceValue>) -> Self {
|
||||
RecordType {
|
||||
fields: Vec1::new(values.iter().map(Into::into).collect())
|
||||
.expect("Record must have at least one field, zero given."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a native type supported by WIT.
|
||||
pub trait NativeType {
|
||||
/// The associated interface type that maps to the native type.
|
||||
@ -123,7 +87,7 @@ macro_rules! native {
|
||||
match w {
|
||||
InterfaceValue::$variant(n) => Ok(n.clone()),
|
||||
_ => Err(WasmValueNativeCastError {
|
||||
from: w.into(),
|
||||
from: w.clone(),
|
||||
to: <$native_type>::INTERFACE_TYPE,
|
||||
}),
|
||||
}
|
||||
@ -173,8 +137,8 @@ impl<'a> Iterator for FlattenInterfaceValueIterator<'a> {
|
||||
}
|
||||
|
||||
// Recursively iterate over the record.
|
||||
Some(InterfaceValue::Record(values)) => {
|
||||
self.iterators.push(values.iter());
|
||||
Some(InterfaceValue::Record(fields)) => {
|
||||
self.iterators.push(fields.iter());
|
||||
self.next()
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use std::{
|
||||
|
||||
/// `Vec1<T>` represents a non-empty `Vec<T>`. It derefs to `Vec<T>`
|
||||
/// directly.
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Hash, Deserialize, Default)]
|
||||
pub struct Vec1<T>(Vec<T>)
|
||||
where
|
||||
T: Debug;
|
||||
|
Loading…
Reference in New Issue
Block a user