From 19a0464a64a2be4329e29abdef58eaf6f100eb02 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 10 Nov 2023 20:42:39 +0300 Subject: [PATCH 1/9] MIR: add typed-arena to dependencies --- contrib/mir/Cargo.lock | 7 +++++++ contrib/mir/Cargo.toml | 1 + 2 files changed, 8 insertions(+) diff --git a/contrib/mir/Cargo.lock b/contrib/mir/Cargo.lock index 020747567373..05cb64602870 100644 --- a/contrib/mir/Cargo.lock +++ b/contrib/mir/Cargo.lock @@ -691,6 +691,7 @@ dependencies = [ "logos", "tezos_crypto_rs", "thiserror", + "typed-arena", ] [[package]] @@ -1290,6 +1291,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.17.0" diff --git a/contrib/mir/Cargo.toml b/contrib/mir/Cargo.toml index f27d8eceaf38..6d63c3b427b5 100644 --- a/contrib/mir/Cargo.toml +++ b/contrib/mir/Cargo.toml @@ -13,6 +13,7 @@ thiserror = "1.0" logos = "0.13" hex = "0.4" tezos_crypto_rs = "0.5" +typed-arena = "2" [[bin]] name = "tzt_runner" -- GitLab From bf247c42c35bbd9728a86ba97030615663f1cd06 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 10 Nov 2023 21:43:22 +0300 Subject: [PATCH 2/9] MIR: add Micheline type --- contrib/mir/src/ast.rs | 2 + contrib/mir/src/ast/micheline.rs | 261 +++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 contrib/mir/src/ast/micheline.rs diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 633d6b65d90c..0c6ee25c0be9 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -6,12 +6,14 @@ /******************************************************************************/ pub mod comparable; +pub mod micheline; pub mod michelson_address; pub mod michelson_list; pub mod or; pub mod parsed; pub mod typechecked; +pub use micheline::Micheline; use std::collections::BTreeMap; pub use tezos_crypto_rs::hash::ChainId; diff --git a/contrib/mir/src/ast/micheline.rs b/contrib/mir/src/ast/micheline.rs new file mode 100644 index 000000000000..07fd2ed1a05a --- /dev/null +++ b/contrib/mir/src/ast/micheline.rs @@ -0,0 +1,261 @@ +/******************************************************************************/ +/* */ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) [2023] Serokell */ +/* */ +/******************************************************************************/ + +use typed_arena::Arena; + +use crate::lexer::Prim; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Micheline<'a> { + Int(i128), + String(String), + Bytes(Vec), + /// Application of a Micheline primitive to some arguments with optional + /// annotations. The primitive is the first field, arguments are the second + /// field, annotations are the last field. + App(Prim, &'a [Micheline<'a>], Vec<&'a str>), + Seq(&'a [Micheline<'a>]), +} + +impl<'a> Micheline<'a> { + pub fn prim0(prim: Prim) -> Self { + Micheline::App(prim, &[], vec![]) + } + + pub fn prim1(arena: &'a Arena>, prim: Prim, arg: Micheline<'a>) -> Self { + Micheline::App(prim, arena.alloc_extend([arg]), vec![]) + } + + pub fn prim2( + arena: &'a Arena>, + prim: Prim, + arg1: Micheline<'a>, + arg2: Micheline<'a>, + ) -> Self { + Micheline::App(prim, arena.alloc_extend([arg1, arg2]), vec![]) + } +} + +impl<'a> From for Micheline<'a> { + fn from(x: i128) -> Self { + Micheline::Int(x) + } +} + +impl<'a> From for Micheline<'a> { + fn from(x: String) -> Self { + Micheline::String(x) + } +} + +impl<'a> From> for Micheline<'a> { + fn from(x: Vec) -> Self { + Micheline::Bytes(x) + } +} + +impl<'a> From for Micheline<'a> { + fn from(x: bool) -> Self { + Micheline::prim0(if x { Prim::True } else { Prim::False }) + } +} + +impl<'a> From<&str> for Micheline<'a> { + fn from(s: &str) -> Self { + Micheline::from(s.to_owned()) + } +} + +/// Pattern synonym matching all type primitive applications. Useful for total +/// matches. +macro_rules! micheline_types { + () => { + Micheline::App( + Prim::int + | Prim::nat + | Prim::bool + | Prim::mutez + | Prim::string + | Prim::operation + | Prim::unit + | Prim::address + | Prim::chain_id + | Prim::pair + | Prim::or + | Prim::option + | Prim::list + | Prim::contract + | Prim::map, + .., + ) + }; +} + +/// Pattern synonym matching all Micheline literals. Useful for total +/// matches. +macro_rules! micheline_literals { + () => { + Micheline::Int(..) | Micheline::String(..) | Micheline::Bytes(..) + }; +} + +/// Pattern synonym matching all field primitive applications. Useful for total +/// matches. +macro_rules! micheline_fields { + () => { + Micheline::App(Prim::parameter | Prim::storage | Prim::code, ..) + }; +} + +/// Pattern synonym matching all instruction primitive applications. Useful for total +/// matches. +macro_rules! micheline_instructions { + () => { + Micheline::App( + Prim::PUSH + | Prim::INT + | Prim::GT + | Prim::LOOP + | Prim::DIP + | Prim::ADD + | Prim::DROP + | Prim::IF + | Prim::IF_CONS + | Prim::IF_LEFT + | Prim::IF_NONE + | Prim::FAILWITH + | Prim::DUP + | Prim::UNIT + | Prim::CAR + | Prim::CDR + | Prim::PAIR + | Prim::SOME + | Prim::COMPARE + | Prim::AMOUNT + | Prim::NIL + | Prim::GET + | Prim::UPDATE + | Prim::UNPAIR + | Prim::CONS + | Prim::ITER + | Prim::CHAIN_ID + | Prim::SELF + | Prim::SWAP, + .., + ) + }; +} + +/// Pattern synonym matching all value constructor primitive applications. +/// Useful for total matches. +macro_rules! micheline_values { + () => { + Micheline::App( + Prim::True + | Prim::False + | Prim::Unit + | Prim::None + | Prim::Pair + | Prim::Some + | Prim::Elt + | Prim::Left + | Prim::Right, + .., + ) + }; +} + +pub(crate) use { + micheline_fields, micheline_instructions, micheline_literals, micheline_types, micheline_values, +}; + +#[cfg(test)] +pub mod test_helpers { + + /// Helper to reduce syntactic noise when constructing Micheline applications in tests. + /// + /// See the test below for examples. + macro_rules! app { + ($prim:ident [$($args:expr),* $(,)*]) => { + $crate::ast::micheline::Micheline::App( + $crate::lexer::Prim::$prim, &[$($crate::ast::micheline::Micheline::from($args)),*], + vec![], + ) + }; + ($prim:ident) => { + $crate::ast::micheline::Micheline::App($crate::lexer::Prim::$prim, &[], vec![]) + }; + } + + #[test] + fn test_app() { + use super::*; + assert_eq!(app!(True), Micheline::App(Prim::True, &[], vec![])); + assert_eq!( + app!(DUP[3]), + Micheline::App(Prim::DUP, &[Micheline::Int(3)], vec![]) + ); + assert_eq!( + app!(DIP[3, seq!{ app!(DROP) }]), + Micheline::App( + Prim::DIP, + &[ + Micheline::Int(3), + Micheline::Seq(&[Micheline::App(Prim::DROP, &[], vec![])]) + ], + vec![] + ) + ); + } + + /// Helper to reduce syntactic noise when constructing Micheline sequences in tests. + /// + /// See the test below for examples. + macro_rules! seq { + {$($elt:expr);* $(;)*} => { + $crate::ast::micheline::Micheline::Seq(&[$($crate::ast::micheline::Micheline::from($elt)),*]) + } + } + + #[test] + fn test_seq() { + use super::*; + assert_eq!(seq! {}, Micheline::Seq(&[])); + assert_eq!( + seq! { app!(CAR) }, + Micheline::Seq(&[Micheline::App(Prim::CAR, &[], vec![])]) + ); + assert_eq!( + seq! { app!(CAR); app!(DUP); }, + Micheline::Seq(&[ + Micheline::App(Prim::CAR, &[], vec![]), + Micheline::App(Prim::DUP, &[], vec![]), + ]) + ); + } + + pub(crate) use {app, seq}; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(dead_code)] + /// Static test to check that `micheline_*` pattern synonyms cover all + /// constructors except Seq. + fn pattern_synonym_coverage(micheline: Micheline) { + match micheline { + micheline_fields!() + | micheline_instructions!() + | micheline_literals!() + | micheline_types!() + | micheline_values!() + | Micheline::Seq(..) => (), + } + } +} -- GitLab From a9c88ebe090e4bd2e81883d824f5bfb8685d212b Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 10 Nov 2023 23:04:04 +0300 Subject: [PATCH 3/9] MIR: Make typed_value_to_value_optimized convert to Micheline --- contrib/mir/src/ast.rs | 65 +++++++++++++----------------- contrib/mir/src/tzt/expectation.rs | 4 +- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 0c6ee25c0be9..ef0ef83c1ad2 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -16,6 +16,9 @@ pub mod typechecked; pub use micheline::Micheline; use std::collections::BTreeMap; pub use tezos_crypto_rs::hash::ChainId; +use typed_arena::Arena; + +use crate::lexer::Prim; pub use michelson_address::*; pub use michelson_list::MichelsonList; @@ -167,53 +170,41 @@ pub enum TypedValue { Contract(Address), } -pub fn typed_value_to_value_optimized(tv: TypedValue) -> Value { +pub fn typed_value_to_value_optimized<'a>( + arena: &'a Arena>, + tv: TypedValue, +) -> Micheline<'a> { + use Micheline as V; use TypedValue as TV; - use Value as V; + let go = |x| typed_value_to_value_optimized(arena, x); match tv { - TV::Int(i) => V::Number(i), - TV::Nat(u) => V::Number(u.try_into().unwrap()), - TV::Mutez(u) => V::Number(u.try_into().unwrap()), - TV::Bool(b) => V::Boolean(b), + TV::Int(i) => V::Int(i), + TV::Nat(u) => V::Int(u.try_into().unwrap()), + TV::Mutez(u) => V::Int(u.try_into().unwrap()), + TV::Bool(true) => V::prim0(Prim::True), + TV::Bool(false) => V::prim0(Prim::False), TV::String(s) => V::String(s), - TV::Unit => V::Unit, + TV::Unit => V::prim0(Prim::Unit), // This transformation for pairs deviates from the optimized representation of the // reference implementation, because reference implementation optimizes the size of combs // and uses an untyped representation that is the shortest. - TV::Pair(b) => V::new_pair( - typed_value_to_value_optimized(b.0), - typed_value_to_value_optimized(b.1), - ), - TV::List(l) => V::Seq(l.into_iter().map(typed_value_to_value_optimized).collect()), + TV::Pair(b) => V::prim2(arena, Prim::Pair, go(b.0), go(b.1)), + TV::List(l) => V::Seq(arena.alloc_extend(l.into_iter().map(go))), TV::Map(m) => V::Seq( - m.into_iter() - .map(|(key, val)| { - V::new_elt( - typed_value_to_value_optimized(key), - typed_value_to_value_optimized(val), - ) - }) - .collect(), + arena.alloc_extend( + m.into_iter() + .map(|(key, val)| V::prim2(arena, Prim::Elt, go(key), go(val))), + ), ), - TV::Option(None) => V::Option(None), - TV::Option(Some(r)) => V::new_option(Some(typed_value_to_value_optimized(*r))), - TV::Or(x) => V::new_or(x.map(typed_value_to_value_optimized)), + TV::Option(None) => V::prim0(Prim::None), + TV::Option(Some(x)) => V::prim1(arena, Prim::Some, go(*x)), + TV::Or(or) => match *or { + Or::Left(x) => V::prim1(arena, Prim::Left, go(x)), + Or::Right(x) => V::prim1(arena, Prim::Right, go(x)), + }, TV::Address(x) => V::Bytes(x.to_bytes_vec()), TV::ChainId(x) => V::Bytes(x.into()), - TV::Contract(x) => typed_value_to_value_optimized(TV::Address(x)), - } -} - -// Note that there are more than one way to do this conversion. Here we use the optimized untyped -// representation as the target, since that is what the typed to untyped conversion during a -// FAILWITH call does in the reference implementation, and this logic is primarily used in the -// corresponding section of MIR now. -// -// TODO: This implementation will be moved to interpreter in the context of issue, -// https://gitlab.com/tezos/tezos/-/issues/6504 -impl From for Value { - fn from(tv: TypedValue) -> Self { - typed_value_to_value_optimized(tv) + TV::Contract(x) => go(TV::Address(x)), } } diff --git a/contrib/mir/src/tzt/expectation.rs b/contrib/mir/src/tzt/expectation.rs index f76928a5e963..e2258afc9507 100644 --- a/contrib/mir/src/tzt/expectation.rs +++ b/contrib/mir/src/tzt/expectation.rs @@ -51,7 +51,9 @@ fn unify_interpreter_error( // context of the interpreter, though here we have full type information for // both values being compared, so it is probably safe to compare typed // representation as well. - Value::from(exp_typed_val) == Value::from(failed_typed_value.clone()) + let arena = typed_arena::Arena::new(); + typed_value_to_value_optimized(&arena, exp_typed_val) + == typed_value_to_value_optimized(&arena, failed_typed_value.clone()) } Err(_) => false, } -- GitLab From 86dcb4178e8ad44ac76944bb9952cecf4318b378 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 10 Nov 2023 21:49:41 +0300 Subject: [PATCH 4/9] MIR: rewire parser to parse Micheline typechecker and e2e tests in `lib` are expected to fail --- contrib/mir/src/lib.rs | 114 +++++------- contrib/mir/src/parser.rs | 272 ++++++++++------------------- contrib/mir/src/syntax.lalrpop | 242 +++++-------------------- contrib/mir/src/typechecker.rs | 31 +++- contrib/mir/src/tzt.rs | 88 +++++----- contrib/mir/src/tzt/expectation.rs | 14 +- contrib/mir/tzt_runner/main.rs | 15 +- 7 files changed, 279 insertions(+), 497 deletions(-) diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index 373236d5f05d..66c6d59227a6 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -23,8 +23,7 @@ mod tests { use crate::context::Ctx; use crate::gas::Gas; use crate::interpreter; - use crate::parser; - use crate::parser::parse_contract_script; + use crate::parser::test_helpers::{parse, parse_contract_script}; use crate::stack::{stk, tc_stk}; use crate::typechecker; @@ -38,7 +37,7 @@ mod tests { #[test] fn interpret_test_expect_success() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); @@ -49,7 +48,7 @@ mod tests { #[test] fn interpret_mutez_push_add() { - let ast = parser::parse("{ PUSH mutez 100; PUSH mutez 500; ADD }").unwrap(); + let ast = parse("{ PUSH mutez 100; PUSH mutez 500; ADD }").unwrap(); let mut ctx = Ctx::default(); let ast = ast.typecheck(&mut ctx, None, &mut tc_stk![]).unwrap(); let mut istack = stk![]; @@ -59,7 +58,7 @@ mod tests { #[test] fn interpret_test_gas_consumption() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); @@ -73,7 +72,7 @@ mod tests { #[test] fn interpret_test_gas_out_of_gas() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); @@ -90,7 +89,7 @@ mod tests { #[test] fn typecheck_test_expect_success() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; assert!(ast.typecheck(&mut Ctx::default(), None, &mut stack).is_ok()); assert_eq!(stack, tc_stk![Type::Int]) @@ -98,7 +97,7 @@ mod tests { #[test] fn typecheck_gas() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; let mut ctx = Ctx::default(); let start_milligas = ctx.gas.milligas(); @@ -110,7 +109,7 @@ mod tests { #[test] fn typecheck_out_of_gas() { - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; let mut ctx = Ctx { gas: Gas::new(1000), @@ -125,7 +124,7 @@ mod tests { #[test] fn typecheck_test_expect_fail() { use typechecker::{NoMatchingOverloadReason, TcError}; - let ast = parser::parse(FIBONACCI_ILLTYPED_SRC).unwrap(); + let ast = parse(FIBONACCI_ILLTYPED_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; assert_eq!( ast.typecheck(&mut Ctx::default(), None, &mut stack), @@ -139,80 +138,59 @@ mod tests { #[test] fn parser_test_expect_success() { - use Instruction::*; - use Value::*; + use crate::ast::micheline::test_helpers::*; - let ast = parser::parse(FIBONACCI_SRC).unwrap(); + let ast = parse(FIBONACCI_SRC).unwrap(); // use built in pretty printer to validate the expected AST. assert_eq!( ast, - Instruction::Seq(vec![ - Int, - Push((Type::Int, Number(0))), - Dup(Some(2)), - Gt, - If( - vec![ - Dip(None, vec![Push((Type::Int, Number(-1))), Add(())]), - Push((Type::Int, Number(1))), - Dup(Some(3)), - Gt, - Loop(vec![ - Swap, - Dup(Some(2)), - Add(()), - Dip(Some(2), vec![Push((Type::Int, Number(-1))), Add(())]), - Dup(Some(3)), - Gt, - ]), - Dip(None, vec![Drop(Some(2))]), - ], - vec![Dip(None, vec![Drop(None)])], - ), - ]) + seq! { + app!(INT); + app!(PUSH[app!(int), 0]); + app!(DUP[2]); + app!(GT); + app!(IF[ + seq!{ + app!(DIP[seq!{app!(PUSH[app!(int), -1]); app!(ADD) }]); + app!(PUSH[app!(int), 1]); + app!(DUP[3]); + app!(GT); + app!(LOOP[seq!{ + app!(SWAP); + app!(DUP[2]); + app!(ADD); + app!(DIP[2, seq!{ + app!(PUSH[app!(int), -1]); + app!(ADD) + }]); + app!(DUP[3]); + app!(GT); + }]); + app!(DIP[seq!{app!(DROP[2])}]); + }, + seq!{ + app!(DIP[seq!{ app!(DROP) }]) + }, + ]); + } ); } #[test] fn parser_test_expect_fail() { assert_eq!( - &parser::parse(FIBONACCI_MALFORMED_SRC) - .unwrap_err() - .to_string(), + &parse(FIBONACCI_MALFORMED_SRC).unwrap_err().to_string(), "Unrecognized token `GT` found at 133:135\nExpected one of \";\" or \"}\"" ); } #[test] fn parser_test_dip_dup_drop_args() { - use Instruction::{Dip, Drop, Dup}; - - assert_eq!(parser::parse("DROP 1023"), Ok(Drop(Some(1023)))); - assert_eq!(parser::parse("DIP 1023 {}"), Ok(Dip(Some(1023), vec![]))); - assert_eq!(parser::parse("DUP 1023"), Ok(Dup(Some(1023)))); + use crate::ast::micheline::test_helpers::*; - // failures - assert_eq!( - parser::parse("{ DROP 1025 }") - .unwrap_err() - .to_string() - .as_str(), - "expected a natural from 0 to 1023 inclusive, but got 1025" - ); - assert_eq!( - parser::parse("{ DIP 1024 {} }") - .unwrap_err() - .to_string() - .as_str(), - "expected a natural from 0 to 1023 inclusive, but got 1024" - ); - assert_eq!( - parser::parse("{ DUP 65536 }") - .unwrap_err() - .to_string() - .as_str(), - "expected a natural from 0 to 1023 inclusive, but got 65536" - ); + assert_eq!(parse("DROP 1023"), Ok(app!(DROP[1023]))); + assert_eq!(parse("DIP 1023 {}"), Ok(app!(DIP[1023, seq!{}]))); + assert_eq!(parse("DUP 1023"), Ok(app!(DUP[1023]))); } #[test] @@ -223,7 +201,7 @@ mod tests { }; let interp_res = parse_contract_script(VOTE_SRC) .unwrap() - .typecheck(&mut ctx) + .typecheck_script(&mut ctx) .unwrap() .interpret( &mut ctx, diff --git a/contrib/mir/src/parser.rs b/contrib/mir/src/parser.rs index dd9221f4990f..8dcae1ec5bb1 100644 --- a/contrib/mir/src/parser.rs +++ b/contrib/mir/src/parser.rs @@ -6,10 +6,11 @@ /******************************************************************************/ use crate::ast::*; -use crate::lexer::{LexerError, Prim, Tok}; +use crate::lexer::{LexerError, Tok}; use crate::syntax; use lalrpop_util::ParseError; use logos::Logos; +use typed_arena::Arena; #[derive(Debug, PartialEq, thiserror::Error)] pub enum ParserError { @@ -17,58 +18,34 @@ pub enum ParserError { ExpectedU10(i128), #[error(transparent)] LexerError(#[from] LexerError), - #[error("no {0} field")] - NoField(Prim), - #[error("duplicate {0} field")] - DuplicateField(Prim), } -pub fn parse(src: &str) -> Result> { - syntax::InstructionParser::new().parse(spanned_lexer(src)) +pub struct Parser<'a> { + pub arena: Arena>, } -pub fn parse_contract_script( - src: &str, -) -> Result, ParseError> { - syntax::ContractScriptParser::new().parse(spanned_lexer(src)) -} - -/// Helper type to parse contract fields -pub enum ContractScriptEntity { - Parameter(Type), - Storage(Type), - Code(ParsedInstruction), +impl Default for Parser<'_> { + fn default() -> Self { + Parser::new() + } } -impl TryFrom> for ContractScript { - type Error = crate::parser::ParserError; - fn try_from(value: Vec) -> Result { - use crate::lexer::Prim as P; - use crate::parser::ParserError as Err; - use ContractScriptEntity as CE; - let mut param: Option = None; - let mut storage: Option = None; - let mut code: Option = None; - fn set_if_none(x: &mut Option, y: T, e: P) -> Result<(), Err> { - if x.is_none() { - *x = Some(y); - Ok(()) - } else { - Err(Err::DuplicateField(e)) - } +impl<'a> Parser<'a> { + pub fn new() -> Self { + Parser { + arena: Arena::new(), } - for i in value { - match i { - CE::Parameter(p) => set_if_none(&mut param, p, P::parameter), - CE::Storage(p) => set_if_none(&mut storage, p, P::storage), - CE::Code(p) => set_if_none(&mut code, p, P::code), - }?; - } - Ok(ContractScript { - parameter: param.ok_or(Err::NoField(P::parameter))?, - storage: storage.ok_or(Err::NoField(P::storage))?, - code: code.ok_or(Err::NoField(P::code))?, - }) + } + + pub fn parse(&'a self, src: &str) -> Result> { + syntax::MichelineNakedParser::new().parse(&self.arena, spanned_lexer(src)) + } + + pub fn parse_top_level( + &'a self, + src: &str, + ) -> Result> { + syntax::MichelineTopLevelParser::new().parse(&self.arena, spanned_lexer(src)) } } @@ -83,38 +60,43 @@ pub fn spanned_lexer( }) } -/// Validate a number is a 10-bit unsigned integer. -pub fn validate_u10(n: i128) -> Result { - let res = u16::try_from(n).map_err(|_| ParserError::ExpectedU10(n))?; - if res >= 1024 { - return Err(ParserError::ExpectedU10(n)); +#[cfg(test)] +pub mod test_helpers { + use super::*; + + pub fn parse(s: &str) -> Result> { + let parser = Box::leak(Box::new(Parser::new())); + parser.parse(s) + } + + pub fn parse_contract_script( + s: &str, + ) -> Result> { + let parser = Box::leak(Box::new(Parser::new())); + parser.parse_top_level(s) } - Ok(res) } #[cfg(test)] mod tests { - use super::*; + use super::test_helpers::*; + use crate::ast::micheline::test_helpers::{app, seq}; #[test] fn pair_type() { assert_eq!( parse("PUSH (pair int nat) Unit").unwrap(), - Instruction::Push((Type::new_pair(Type::Int, Type::Nat), Value::Unit)) + app!(PUSH[app!(pair[app!(int), app!(nat)]), app!(Unit)]) ); assert_eq!( parse("PUSH (pair int nat unit) Unit").unwrap(), - Instruction::Push(( - Type::new_pair(Type::Int, Type::new_pair(Type::Nat, Type::Unit)), - Value::Unit - )) + app!(PUSH[app!(pair[app!(int), app!(nat), app!(unit)]), app!(Unit)]) ); assert_eq!( parse("PUSH (pair (pair int nat) unit) Unit").unwrap(), - Instruction::Push(( - Type::new_pair(Type::new_pair(Type::Int, Type::Nat), Type::Unit), - Value::Unit - )) + app!(PUSH[ + app!(pair[app!(pair[app!(int), app!(nat)]), app!(unit)]), app!(Unit) + ]) ); } @@ -122,14 +104,7 @@ mod tests { fn or_type() { assert_eq!( parse("PUSH (or int nat) Unit").unwrap(), - Instruction::Push((Type::new_or(Type::Int, Type::Nat), Value::Unit)) - ); - // unlike for pairs, there's no linearized syntax for `or` - assert_eq!( - parse("{ PUSH (or int nat unit) Unit }") - .unwrap_err() - .to_string(), - "Unrecognized token `unit` found at 19:23\nExpected one of \")\"" + app!(PUSH[app!(or[app!(int), app!(nat)]), app!(Unit)]) ); } @@ -137,49 +112,35 @@ mod tests { fn pair_value() { assert_eq!( parse("PUSH unit (Pair 3 4)").unwrap(), - Instruction::Push(( - Type::Unit, - Value::new_pair(Value::Number(3), Value::Number(4)), - )) + app!(PUSH[app!(unit), app!(Pair[3, 4])]) ); assert_eq!( parse("PUSH unit (Pair 3 4 5)").unwrap(), - Instruction::Push(( - Type::Unit, - Value::new_pair( - Value::Number(3), - Value::new_pair(Value::Number(4), Value::Number(5)), - ), - )) + app!(PUSH[app!(unit), app!(Pair[3, 4, 5])]) ); assert_eq!( parse("PUSH unit (Pair (Pair 3 4) 5)").unwrap(), - Instruction::Push(( - Type::Unit, - Value::new_pair( - Value::new_pair(Value::Number(3), Value::Number(4)), - Value::Number(5), - ), + app!(PUSH[app!(unit), app!(Pair[app!(Pair[3, 4]), 5])]) + ); + assert_eq!( + parse("PUSH pair unit unit Pair Unit Unit"), + Ok(app!( + PUSH[app!(pair), app!(unit), app!(unit), app!(Pair), app!(Unit), app!(Unit)] + )) + ); + assert_eq!( + parse("PUSH (pair unit unit) Pair Unit Unit"), + Ok(app!( + PUSH[app!(pair[app!(unit), app!(unit)]), app!(Pair), app!(Unit), app!(Unit)] )) ); - assert!(parse("{ PUSH pair unit unit Pair Unit Unit }") - .unwrap_err() - .to_string() - .starts_with("Unrecognized token `pair` found at 7:11")); - assert!(parse("{ PUSH (pair unit unit) Pair Unit Unit }") - .unwrap_err() - .to_string() - .starts_with("Unrecognized token `Pair` found at 24:28\n")); } #[test] fn or_value() { assert_eq!( parse("PUSH (or int unit) (Left 3)").unwrap(), - Instruction::Push(( - Type::new_or(Type::Int, Type::Unit), - Value::new_or(Or::Left(Value::Number(3))), - )) + app!(PUSH[app!(or[app!(int), app!(unit)]), app!(Left[3])]), ); } @@ -187,56 +148,49 @@ mod tests { fn value_parens() { assert_eq!( parse("PUSH unit (Unit)").unwrap(), - Instruction::Push((Type::Unit, Value::Unit)) + app!(PUSH[app!(unit), app!(Unit)]) ); } #[test] fn type_anns() { - use Type as T; - use Type::*; - macro_rules! parse_type { - ($s:expr) => { - crate::syntax::TypeParser::new().parse(spanned_lexer($s)) - }; - } - assert_eq!(parse_type!("(int :p)"), Ok(Int)); + assert_eq!(parse("(int :p)"), Ok(app!(int))); assert_eq!( - parse_type!("(pair :point (int :x_pos) (int :y_pos))"), - Ok(T::new_pair(Int, Int)) + parse("(pair :point (int :x_pos) (int :y_pos))"), + Ok(app!(pair[app!(int), app!(int)])) ); - assert_eq!(parse_type!("(string %foo)"), Ok(String)); - assert_eq!(parse_type!("(string %foo :bar @baz)"), Ok(String)); - assert_eq!(parse_type!("(string @foo)"), Ok(String)); + assert_eq!(parse("(string %foo)"), Ok(app!(string))); + assert_eq!(parse("(string %foo :bar @baz)"), Ok(app!(string))); + assert_eq!(parse("(string @foo)"), Ok(app!(string))); assert_eq!( - parse_type!("(pair %a (int %b) (int %c))"), - Ok(T::new_pair(Int, Int)) + parse("(pair %a (int %b) (int %c))"), + Ok(app!(pair[app!(int), app!(int)])) ); assert_eq!( - parse_type!("(or %a (int %b) (int %c))"), - Ok(T::new_or(Int, Int)) + parse("(or %a (int %b) (int %c))"), + Ok(app!(or[app!(int), app!(int)])) ); assert_eq!( - parse_type!("(option %a int %b)") + parse("(option %a int %b)") .unwrap_err() .to_string() - .as_str(), - "Unrecognized token `` found at 15:17\nExpected one of \")\"" + .as_str() + .lines() + .next() + .unwrap(), + "Unrecognized token `` found at 15:17" ); } #[test] fn instr_anns() { - use Instruction::*; - use Type as T; - use Value as V; assert_eq!( parse("PUSH @var :ty %field int 1").unwrap(), - Push((T::Int, V::Number(1))), + app!(PUSH[app!(int), 1]), ); assert_eq!( parse("CAR @var :ty %field :ty.2 @var.2 %field.2").unwrap(), - Car, + app!(CAR), ); } @@ -250,64 +204,33 @@ mod tests { #[test] fn parse_contract_script_test() { - use crate::lexer::Prim::{code, parameter, storage}; - use Instruction::*; - use ParserError as Err; - use Type as T; - assert_eq!( parse_contract_script("parameter unit; storage unit; code FAILWITH"), - Ok(ContractScript { - parameter: T::Unit, - storage: T::Unit, - code: Failwith(()) + Ok(seq! { + app!(parameter[app!(unit)]); + app!(storage[app!(unit)]); + app!(code[app!(FAILWITH)]); }) ); // non-atomic arguments to code must be wrapped in braces assert_eq!( - parse_contract_script("parameter unit; storage unit; code NIL unit") - .unwrap_err() - .to_string() - .lines() - .next(), - Some("Unrecognized token `NIL` found at 35:38") + parse_contract_script("parameter unit; storage unit; code NIL unit"), + Ok(seq! { + app!(parameter[app!(unit)]); + app!(storage[app!(unit)]); + app!(code[app!(NIL), app!(unit)]); + }) ); // or parentheses assert_eq!( parse_contract_script("parameter unit; storage unit; code (NIL unit)"), - Ok(ContractScript { - parameter: T::Unit, - storage: T::Unit, - code: Nil(T::Unit), + Ok(seq! { + app!(parameter[app!(unit)]); + app!(storage[app!(unit)]); + app!(code[app!(NIL[app!(unit)])]); }) ); - // duplicate - assert_eq!( - parse_contract_script("parameter unit; parameter int; storage unit; code FAILWITH"), - Err(Err::DuplicateField(parameter).into()) - ); - assert_eq!( - parse_contract_script("parameter unit; storage unit; storage int; code FAILWITH"), - Err(Err::DuplicateField(storage).into()) - ); - assert_eq!( - parse_contract_script("code INT; parameter unit; storage unit; code FAILWITH"), - Err(Err::DuplicateField(code).into()) - ); - // missing - assert_eq!( - parse_contract_script("storage unit; code FAILWITH"), - Err(Err::NoField(parameter).into()) - ); - assert_eq!( - parse_contract_script("parameter unit; code FAILWITH"), - Err(Err::NoField(storage).into()) - ); - assert_eq!( - parse_contract_script("parameter unit; storage unit"), - Err(Err::NoField(code).into()) - ); } #[test] @@ -317,7 +240,7 @@ mod tests { fn contract_ty_push() { assert_eq!( parse("PUSH (contract :ct %foo unit) Unit").unwrap(), - Instruction::Push((Type::new_contract(Type::Unit), Value::Unit)) + app!(PUSH[app!(contract[app!(unit)]), app!(Unit)]) ); } @@ -325,7 +248,7 @@ mod tests { fn bytes_push() { assert_eq!( parse("PUSH unit 0xdeadf00d").unwrap(), - Instruction::Push((Type::Unit, Value::Bytes(vec![0xde, 0xad, 0xf0, 0x0d]))) + app!(PUSH[app!(unit), vec![0xde, 0xad, 0xf0, 0x0d]]) ); } @@ -333,10 +256,7 @@ mod tests { fn address_ty_push() { assert_eq!( parse("PUSH address \"tz1Nw5nr152qddEjKT2dKBH8XcBMDAg72iLw\"").unwrap(), - Instruction::Push(( - Type::Address, - Value::String("tz1Nw5nr152qddEjKT2dKBH8XcBMDAg72iLw".to_owned()) - )) + app!(PUSH[app!(address), "tz1Nw5nr152qddEjKT2dKBH8XcBMDAg72iLw"]) ); } } diff --git a/contrib/mir/src/syntax.lalrpop b/contrib/mir/src/syntax.lalrpop index dcaadbb00d23..7d8dc5095e03 100644 --- a/contrib/mir/src/syntax.lalrpop +++ b/contrib/mir/src/syntax.lalrpop @@ -10,14 +10,15 @@ #![cfg(not(tarpaulin_include))] use crate::ast::*; -use crate::parser::{ParserError, ContractScriptEntity, validate_u10}; +use crate::parser::ParserError; use crate::lexer::{LexerError, Prim, PrimWithTzt, TztPrim as TzP, Tok}; use crate::typechecker as TC; use crate::tzt::*; use PrimWithTzt::*; use PrimWithTzt as PT; +use typed_arena::Arena; -grammar; +grammar<'a>(arena: &'a Arena>); extern { type Error = ParserError; @@ -37,62 +38,12 @@ extern { string => Tok::String(), bytes => Tok::Bytes(>), ann => Tok::Annotation, + // parameter, code and chain_id lexemes are special-cased because + // they're also used in the tzt syntax "parameter" => Tok::Prim(PT::Prim(Prim::parameter)), - "storage" => Tok::Prim(PT::Prim(Prim::storage)), "code" => Tok::Prim(PT::Prim(Prim::code)), - "int" => Tok::Prim(PT::Prim(Prim::int)), - "nat" => Tok::Prim(PT::Prim(Prim::nat)), - "bool" => Tok::Prim(PT::Prim(Prim::bool)), - "mutez" => Tok::Prim(PT::Prim(Prim::mutez)), - "string" => Tok::Prim(PT::Prim(Prim::string)), - "unit" => Tok::Prim(PT::Prim(Prim::unit)), - "operation" => Tok::Prim(PT::Prim(Prim::operation)), - "pair" => Tok::Prim(PT::Prim(Prim::pair)), - "option" => Tok::Prim(PT::Prim(Prim::option)), - "list" => Tok::Prim(PT::Prim(Prim::list)), - "map" => Tok::Prim(PT::Prim(Prim::map)), - "or" => Tok::Prim(PT::Prim(Prim::or)), - "contract" => Tok::Prim(PT::Prim(Prim::contract)), - "address" => Tok::Prim(PT::Prim(Prim::address)), "chain_id" => Tok::Prim(PT::Prim(Prim::chain_id)), - "True" => Tok::Prim(PT::Prim(Prim::True)), - "False" => Tok::Prim(PT::Prim(Prim::False)), - "Unit" => Tok::Prim(PT::Prim(Prim::Unit)), - "None" => Tok::Prim(PT::Prim(Prim::None)), - "Pair" => Tok::Prim(PT::Prim(Prim::Pair)), - "Some" => Tok::Prim(PT::Prim(Prim::Some)), - "Elt" => Tok::Prim(PT::Prim(Prim::Elt)), - "Left" => Tok::Prim(PT::Prim(Prim::Left)), - "Right" => Tok::Prim(PT::Prim(Prim::Right)), - "PUSH" => Tok::Prim(PT::Prim(Prim::PUSH)), - "INT" => Tok::Prim(PT::Prim(Prim::INT)), - "GT" => Tok::Prim(PT::Prim(Prim::GT)), - "LOOP" => Tok::Prim(PT::Prim(Prim::LOOP)), - "DIP" => Tok::Prim(PT::Prim(Prim::DIP)), - "ADD" => Tok::Prim(PT::Prim(Prim::ADD)), - "DROP" => Tok::Prim(PT::Prim(Prim::DROP)), - "SWAP" => Tok::Prim(PT::Prim(Prim::SWAP)), - "IF" => Tok::Prim(PT::Prim(Prim::IF)), - "DUP" => Tok::Prim(PT::Prim(Prim::DUP)), - "FAILWITH" => Tok::Prim(PT::Prim(Prim::FAILWITH)), - "UNIT" => Tok::Prim(PT::Prim(Prim::UNIT)), - "CAR" => Tok::Prim(PT::Prim(Prim::CAR)), - "CDR" => Tok::Prim(PT::Prim(Prim::CDR)), - "PAIR" => Tok::Prim(PT::Prim(Prim::PAIR)), - "IF_NONE" => Tok::Prim(PT::Prim(Prim::IF_NONE)), - "SOME" => Tok::Prim(PT::Prim(Prim::SOME)), - "COMPARE" => Tok::Prim(PT::Prim(Prim::COMPARE)), - "AMOUNT" => Tok::Prim(PT::Prim(Prim::AMOUNT)), - "NIL" => Tok::Prim(PT::Prim(Prim::NIL)), - "GET" => Tok::Prim(PT::Prim(Prim::GET)), - "UPDATE" => Tok::Prim(PT::Prim(Prim::UPDATE)), - "UNPAIR" => Tok::Prim(PT::Prim(Prim::UNPAIR)), - "CONS" => Tok::Prim(PT::Prim(Prim::CONS)), - "IF_CONS" => Tok::Prim(PT::Prim(Prim::IF_CONS)), - "ITER" => Tok::Prim(PT::Prim(Prim::ITER)), - "IF_LEFT" => Tok::Prim(PT::Prim(Prim::IF_LEFT)), - "CHAIN_ID" => Tok::Prim(PT::Prim(Prim::CHAIN_ID)), - "SELF" => Tok::Prim(PT::Prim(Prim::SELF)), + prim => Tok::Prim(PT::Prim()), "(" => Tok::LParen, ")" => Tok::RParen, "{" => Tok::LBrace, @@ -102,127 +53,43 @@ extern { } } -u10: u16 = number =>? validate_u10(<>).map_err(Into::into); - -anns: () = ann* => (); - -atomic_type: Type = { - "int" => Type::Int, - "nat" => Type::Nat, - "bool" => Type::Bool, - "mutez" => Type::Mutez, - "string" => Type::String, - "unit" => Type::Unit, - "operation" => Type::Operation, - "address" => Type::Address, - "chain_id" => Type::ChainId, -} - -pair_args: Type = { - type_expr => <>, - type_expr pair_args => Type::new_pair(<>) -} - -composite_type: Type = { - "pair" anns => Type::new_pair(<>), - "option" anns => Type::new_option(<>), - "list" anns => Type::new_list(<>), - "map" anns => Type::new_map(<>), - "or" anns => Type::new_or(<>), - "contract" anns => Type::new_contract(<>), -} - -type_expr: Type = { - => <>, - "(" ann anns ")" => <>, - "(" ")" => <>, - "(" ")" => <>, +Prim: Prim = { + "parameter" => Prim::parameter, + "code" => Prim::code, + "chain_id" => Prim::chain_id, + prim => <>, } -boolean: bool = { - "True" => true, - "False" => false, +MichelineAtomic: Micheline<'a> = { + number => Micheline::Int(<>), + string => Micheline::String(<>), + bytes => Micheline::Bytes(<>), + Prim => Micheline::prim0(<>), } -atomic_value: Value = { - => Value::Number(n), - => Value::Boolean(b), - string => Value::String(<>), - bytes => Value::Bytes(<>), - "Unit" => Value::Unit, - "None" => Value::Option(None), +MichelineComplex: Micheline<'a> = { + ann+ => Micheline::App(prim, &[], vec![]), + ann* => Micheline::App(prim, arena.alloc_extend(args), vec![]), } -pair_val_args: Value = { - value_expr => <>, - value_expr pair_val_args => Value::new_pair(<>) +pub MichelineNaked: Micheline<'a> = { + MichelineComplex, + Micheline, } -composite_value: Value = { - "Pair" => Value::new_pair(<>), - "Some" => Value::new_option(Some(<>)), - "Elt" => Value::new_elt(<>), - "Left" => Value::new_or(Or::Left(<>)), - "Right" => Value::new_or(Or::Right(<>)), +pub Micheline: Micheline<'a> = { + MichelineAtomic, + "(" ")", + "{" "}", } -value_expr: Value = { - atomic_value => <>, - "(" ")" => <>, - "{" "}" => Value::Seq(<>), -} +MichelineNakedSeq: Micheline<'a> = + semicolonSepSeq => Micheline::Seq(arena.alloc_extend(<>)); -value_expr_naked: Value = { - composite_value => <>, - value_expr => <>, +pub MichelineTopLevel: Micheline<'a> = { + MichelineNakedSeq, } -value_seq: Vec = semicolonSepSeq; - -use Instruction::*; - -#[inline] -atomic_instruction: ParsedInstruction = { - "ADD" => Add(()), - "INT" => Int, - "GT" => Gt, - "SWAP" => Swap, - "FAILWITH" => Failwith(()), - "UNIT" => Unit, - "CAR" => Car, - "CDR" => Cdr, - "PAIR" => Pair, - "SOME" => ISome, - "COMPARE" => Compare, - "AMOUNT" => Instruction::Amount, - "GET" => Get(()), - "UPDATE" => Update(()), - "DROP" => Drop(None), - "DUP" => Dup(None), - "UNPAIR" => Unpair, - "CONS" => Cons, - "CHAIN_ID" => Instruction::ChainId, - "SELF" => ISelf, -} - -instruction: ParsedInstruction = { - anns => <>, - "PUSH" anns => Push((<>)), - "LOOP" anns => Loop(ib), - "DIP" anns => Dip(n, ib), - "DROP" anns => Drop(Some(<>)), - "IF" anns => If(t, f), - "DUP" anns => Dup(Some(<>)), - "IF_NONE" anns => IfNone(<>), - "NIL" anns => Nil(<>), - "IF_CONS" anns => IfCons(<>), - "ITER" anns => Iter((), <>), - "IF_LEFT" anns => IfLeft(<>), - instructionBlock => Instruction::Seq(<>), -} - -instructionSeq = semicolonSepSeq; - semicolonSepSeq: Vec = { ";")*> => { // A sequence of T-followed-by-a-semicolon matched by @@ -240,47 +107,18 @@ semicolonSepSeq: Vec = { } } -instructionBlock: ParsedInstructionBlock = { - "{" "}" => is, -} - -// synonym for consistent type naming -pub Instruction = instruction; - -// synonym for consistent type naming -pub Type: Type = type_expr; - -code_content: ParsedInstruction = { - atomic_instruction => <>, - "(" ")" => <>, - instructionBlock => Instruction::Seq(<>), -} - -ContractScriptEntity: ContractScriptEntity = { - "parameter" => ContractScriptEntity::Parameter(<>), - "storage" => ContractScriptEntity::Storage(<>), - "code" => ContractScriptEntity::Code(<>), -} - -ContractScriptEntitySeq = semicolonSepSeq; - -pub ContractScript: ContractScript = { - ContractScriptEntitySeq =>? <>.try_into().map_err(Into::into), - "{" "}" => <>, -} - // For parsing TZT Tests use TztEntity::*; use TztOutput::*; -tztStackElt : (Type, Value) = { - "stack_elt" => (t, v) +tztStackElt : (Micheline<'a>, Micheline<'a>) = { + "stack_elt" => (t, v) } tztStackEltSeq = semicolonSepSeq; -tztStack : Vec<(Type, Value)> = { +tztStack : Vec<(Micheline<'a>, Micheline<'a>)> = { "{" "}" => s } @@ -290,19 +128,19 @@ mutezAmount : i64 = use ErrorExpectation::*; use InterpreterErrorExpectation::*; -tztEntity : TztEntity = { - "code" => Code(<>), +tztEntity : TztEntity<'a> = { + "code" => Code(<>), "input" => Input(s), "output" => Output(TztSuccess(s)), - "output" "(" "failed" ")" => Output(TztError(InterpreterError(FailedWith(v)))), + "output" "(" "failed" ")" => Output(TztError(InterpreterError(FailedWith(v)))), "output" "(" "mutezOverflow" ")" => Output(TztError(InterpreterError(MutezOverflow(a1, a2)))), "output" "(" "generalOverflow" ")" => Output(TztError(InterpreterError(GeneralOverflow(a1, a2)))), "output" "(" "StaticError" ")" => Output(TztError(TypecheckerError(Some(s)))), "output" "(" "StaticError" "_" ")" => Output(TztError(TypecheckerError(None))), "amount" => TztEntity::Amount(m), - "chain_id" => TztEntity::ChainId(<>), - "parameter" => TztEntity::Parameter(<>), - "self" => TztEntity::SelfAddr(<>), + "chain_id" => TztEntity::ChainId(<>), + "parameter" => TztEntity::Parameter(<>), + "self" => TztEntity::SelfAddr(<>), } -pub tztTestEntities : Vec = semicolonSepSeq; +pub tztTestEntities : Vec> = semicolonSepSeq; diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index ab3381409afd..217497e9a8d6 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -115,6 +115,32 @@ pub enum StacksNotEqualReason { #[error("types not equal: {0:?} != {1:?}")] pub struct TypesNotEqual(Type, Type); +impl Micheline<'_> { + pub fn typecheck_value(&self, ctx: &mut Ctx, ty: &Type) -> Result { + todo!(); + } + + pub fn typecheck( + &self, + ctx: &mut Ctx, + self_type: Option<&Type>, + stack: &mut FailingTypeStack, + ) -> Result { + todo!(); + } + + pub fn typecheck_ty(&self, ctx: &mut Ctx) -> Result { + todo!(); + } + + pub fn typecheck_script( + &self, + ctx: &mut Ctx, + ) -> Result, TcError> { + todo!(); + } +} + impl ContractScript { /// Typecheck the contract script. Validates the script's types, then /// typechecks the code and checks the result stack is as expected. Returns @@ -821,10 +847,13 @@ fn ensure_ty_eq(ctx: &mut Ctx, ty1: &Type, ty2: &Type) -> Result<(), TcError> { #[cfg(test)] mod typecheck_tests { use crate::gas::Gas; - use crate::parser::*; use crate::typechecker::*; use Instruction::*; + fn parse(s: &str) -> Result { + todo!() + } + /// hack to simplify syntax in tests fn typecheck_instruction( i: ParsedInstruction, diff --git a/contrib/mir/src/tzt.rs b/contrib/mir/src/tzt.rs index 1ae8c6d6eff2..49f565b3d425 100644 --- a/contrib/mir/src/tzt.rs +++ b/contrib/mir/src/tzt.rs @@ -15,6 +15,7 @@ use crate::context::*; use crate::interpreter::*; use crate::irrefutable_match::irrefutable_match; use crate::parser::spanned_lexer; +use crate::parser::Parser; use crate::stack::*; use crate::syntax::tztTestEntitiesParser; use crate::typechecker::*; @@ -23,14 +24,14 @@ use crate::tzt::expectation::*; pub type TestStack = Vec<(Type, TypedValue)>; #[derive(PartialEq, Eq, Clone, Debug)] -pub enum TztTestError { +pub enum TztTestError<'a> { StackMismatch((FailingTypeStack, IStack), (FailingTypeStack, IStack)), UnexpectedError(TestError), - UnexpectedSuccess(ErrorExpectation, IStack), - ExpectedDifferentError(ErrorExpectation, TestError), + UnexpectedSuccess(ErrorExpectation<'a>, IStack), + ExpectedDifferentError(ErrorExpectation<'a>, TestError), } -impl fmt::Display for TztTestError { +impl fmt::Display for TztTestError<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use TztTestError::*; match self { @@ -60,29 +61,32 @@ impl fmt::Display for TztTestError { /// Represent one Tzt test. #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TztTest { - pub code: ParsedInstruction, +pub struct TztTest<'a> { + pub code: Micheline<'a>, pub input: TestStack, - pub output: TestExpectation, + pub output: TestExpectation<'a>, pub amount: Option, pub chain_id: Option, pub parameter: Option, pub self_addr: Option, } -fn typecheck_stack(stk: Vec<(Type, Value)>) -> Result, TcError> { +fn typecheck_stack(stk: Vec<(Micheline, Micheline)>) -> Result, TcError> { stk.into_iter() .map(|(t, v)| { - let tc_val = v.typecheck(&mut Default::default(), &t)?; + let t = t.typecheck_ty(&mut Ctx::default())?; + let tc_val = v.typecheck_value(&mut Default::default(), &t)?; Ok((t, tc_val)) }) .collect() } -pub fn parse_tzt_test(src: &str) -> Result> { - tztTestEntitiesParser::new() - .parse(spanned_lexer(src))? - .try_into() +impl<'a> Parser<'a> { + pub fn parse_tzt_test(&'a self, src: &str) -> Result> { + tztTestEntitiesParser::new() + .parse(&self.arena, spanned_lexer(src))? + .try_into() + } } // Check if the option argument value is none, and raise an error if it is not. @@ -98,19 +102,19 @@ fn set_tzt_field(field_name: &str, t: &mut Option, v: T) -> Result<(), Str } use std::error::Error; -impl TryFrom> for TztTest { +impl<'a> TryFrom>> for TztTest<'a> { type Error = Box; - fn try_from(tzt: Vec) -> Result { + fn try_from(tzt: Vec>) -> Result { use TestExpectation::*; use TztEntity::*; use TztOutput::*; - let mut m_code: Option = None; + let mut m_code: Option = None; let mut m_input: Option = None; let mut m_output: Option = None; let mut m_amount: Option = None; - let mut m_chain_id: Option = None; - let mut m_parameter: Option = None; - let mut m_self: Option = None; + let mut m_chain_id: Option = None; + let mut m_parameter: Option = None; + let mut m_self: Option = None; for e in tzt { match e { @@ -139,17 +143,19 @@ impl TryFrom> for TztTest { chain_id: m_chain_id .map(|v| { Ok::<_, TcError>(irrefutable_match!( - v.typecheck(&mut Ctx::default(), &Type::ChainId)?; + v.typecheck_value(&mut Ctx::default(), &Type::ChainId)?; TypedValue::ChainId )) }) .transpose()?, - parameter: m_parameter, + parameter: m_parameter + .map(|v| v.typecheck_ty(&mut Ctx::default())) + .transpose()?, self_addr: m_self .map(|v| { Ok::<_, TcError>( irrefutable_match!( - v.typecheck(&mut Ctx::default(), &Type::Address)?; + v.typecheck_value(&mut Ctx::default(), &Type::Address)?; TypedValue::Address ) .hash, @@ -173,18 +179,18 @@ pub enum TestError { /// This represents the outcome that we expect from interpreting /// the code in a test. #[derive(Debug, PartialEq, Eq, Clone)] -pub enum TestExpectation { +pub enum TestExpectation<'a> { ExpectSuccess(Vec<(Type, TypedValue)>), - ExpectError(ErrorExpectation), + ExpectError(ErrorExpectation<'a>), } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum ErrorExpectation { +pub enum ErrorExpectation<'a> { TypecheckerError(Option), - InterpreterError(InterpreterErrorExpectation), + InterpreterError(InterpreterErrorExpectation<'a>), } -impl fmt::Display for ErrorExpectation { +impl fmt::Display for ErrorExpectation<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use ErrorExpectation::*; match self { @@ -196,13 +202,13 @@ impl fmt::Display for ErrorExpectation { } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum InterpreterErrorExpectation { +pub enum InterpreterErrorExpectation<'a> { GeneralOverflow(i128, i128), MutezOverflow(i64, i64), - FailedWith(Value), + FailedWith(Micheline<'a>), } -impl fmt::Display for InterpreterErrorExpectation { +impl fmt::Display for InterpreterErrorExpectation<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use InterpreterErrorExpectation::*; match self { @@ -215,24 +221,24 @@ impl fmt::Display for InterpreterErrorExpectation { /// Helper type for use during parsing, represent a single /// line from the test file. -pub enum TztEntity { - Code(ParsedInstruction), - Input(Vec<(Type, Value)>), - Output(TztOutput), +pub enum TztEntity<'a> { + Code(Micheline<'a>), + Input(Vec<(Micheline<'a>, Micheline<'a>)>), + Output(TztOutput<'a>), Amount(i64), - ChainId(Value), - Parameter(Type), - SelfAddr(Value), + ChainId(Micheline<'a>), + Parameter(Micheline<'a>), + SelfAddr(Micheline<'a>), } /// Possible values for the "output" expectation field in a Tzt test -pub enum TztOutput { - TztSuccess(Vec<(Type, Value)>), - TztError(ErrorExpectation), +pub enum TztOutput<'a> { + TztSuccess(Vec<(Micheline<'a>, Micheline<'a>)>), + TztError(ErrorExpectation<'a>), } fn execute_tzt_test_code( - code: ParsedInstruction, + code: Micheline, ctx: &mut Ctx, parameter: &Type, input: Vec<(Type, TypedValue)>, diff --git a/contrib/mir/src/tzt/expectation.rs b/contrib/mir/src/tzt/expectation.rs index e2258afc9507..d16a3444303b 100644 --- a/contrib/mir/src/tzt/expectation.rs +++ b/contrib/mir/src/tzt/expectation.rs @@ -7,11 +7,11 @@ use super::*; -fn check_error_expectation( +fn check_error_expectation<'a>( ctx: &mut Ctx, - err_exp: ErrorExpectation, + err_exp: ErrorExpectation<'a>, err: TestError, -) -> Result<(), TztTestError> { +) -> Result<(), TztTestError<'a>> { use ErrorExpectation as Ex; use TestError as Er; use TztTestError::*; @@ -43,7 +43,7 @@ fn unify_interpreter_error( (FailedWith(value), InterpretError::FailedWith(typ, failed_typed_value)) => { // Here we typecheck the untyped value from the expectation using the // typed of the failed value we get from the interpreter. - match value.clone().typecheck(ctx, typ) { + match value.clone().typecheck_value(ctx, typ) { Ok(exp_typed_val) => { // Then both `Typedvalue`s are untyped and compared to get the result. Here // untyping is done before comparing so that we are not using the Eq trait of @@ -64,11 +64,11 @@ fn unify_interpreter_error( } } -pub fn check_expectation( +pub fn check_expectation<'a>( ctx: &mut Ctx, - expected: TestExpectation, + expected: TestExpectation<'a>, real: Result<(FailingTypeStack, IStack), TestError>, -) -> Result<(), TztTestError> { +) -> Result<(), TztTestError<'a>> { use TestExpectation::*; use TztTestError::*; match (expected, real) { diff --git a/contrib/mir/tzt_runner/main.rs b/contrib/mir/tzt_runner/main.rs index acb215e8c988..c3cf0dccb2fc 100644 --- a/contrib/mir/tzt_runner/main.rs +++ b/contrib/mir/tzt_runner/main.rs @@ -8,11 +8,15 @@ use std::env; use std::fs::read_to_string; +use mir::parser::Parser; use mir::tzt::*; fn run_test(file: &str) -> Result<(), String> { let contents = read_to_string(file).map_err(|e| e.to_string())?; - let tzt_test = parse_tzt_test(&contents).map_err(|e| e.to_string())?; + let parser = Parser::new(); + let tzt_test = parser + .parse_tzt_test(&contents) + .map_err(|e| e.to_string())?; run_tzt_test(tzt_test).map_err(|e| format!("{}", e)) } @@ -41,9 +45,16 @@ fn main() { #[cfg(test)] mod tztrunner_tests { - use mir::tzt::*; + use std::error::Error; + + use mir::{parser::Parser, tzt::*}; use TztTestError::*; + fn parse_tzt_test(s: &str) -> Result> { + let parser = Box::leak(Box::new(Parser::new())); + parser.parse_tzt_test(s) + } + #[test] fn test_runner_success() { let tzt_test = parse_tzt_test(TZT_SAMPLE_ADD).unwrap(); -- GitLab From 2518d64fdfe5d051fa637cabcf51cfa928257553 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 10 Nov 2023 23:01:32 +0300 Subject: [PATCH 5/9] MIR: update typechecker to accept Micheline --- contrib/mir/src/gas.rs | 2 +- contrib/mir/src/interpreter.rs | 13 +- contrib/mir/src/lib.rs | 39 +- contrib/mir/src/typechecker.rs | 1091 ++++++++++++++++++---------- contrib/mir/src/tzt.rs | 12 +- contrib/mir/src/tzt/expectation.rs | 2 +- 6 files changed, 738 insertions(+), 421 deletions(-) diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index 33b740c0b47d..e51af17783cb 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -93,7 +93,7 @@ pub mod tc_cost { pub const VALUE_STEP: u32 = 100; // Corresponds to cost_PARSE_TYPE1 in the Tezos protocol. - pub const VERIFY_TYPE_STEP: u32 = 60; + pub const PARSE_TYPE_STEP: u32 = 60; // Taken to be the same as VERIFY_TYPE_STEP, but that's a guess pub const TYPE_PROP_STEP: u32 = 60; diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 945830fa8557..534913a7a604 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -9,7 +9,9 @@ use crate::ast::*; use crate::context::Ctx; use crate::gas::{interpret_cost, OutOfGas}; use crate::irrefutable_match::irrefutable_match; +use crate::lexer::Prim; use crate::stack::*; +use crate::typechecker::typecheck_value; #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] pub enum InterpretError { @@ -31,17 +33,18 @@ pub enum ContractInterpretError { impl ContractScript { /// Interpret a typechecked contract script using the provided parameter and - /// storage. Parameter and storage are given as untyped `Value`s, as this + /// storage. Parameter and storage are given as `Micheline`, as this /// allows ensuring they satisfy the types expected by the script. pub fn interpret( &self, ctx: &mut crate::context::Ctx, - parameter: Value, - storage: Value, + parameter: Micheline, + storage: Micheline, ) -> Result<(Vec, TypedValue), ContractInterpretError> { let in_ty = Type::new_pair(self.parameter.clone(), self.storage.clone()); - let in_val = Value::new_pair(parameter, storage); - let tc_val = in_val.typecheck(ctx, &in_ty)?; + let in_val = &[parameter, storage]; + let in_val = Micheline::App(Prim::Pair, in_val, vec![]); + let tc_val = typecheck_value(ctx, &in_ty, &in_val)?; let mut stack = stk![tc_val]; self.code.interpret(ctx, &mut stack)?; use TypedValue as V; diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index 66c6d59227a6..edea29d58152 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -19,6 +19,7 @@ pub mod tzt; #[cfg(test)] mod tests { + use crate::ast::micheline::test_helpers::*; use crate::ast::*; use crate::context::Ctx; use crate::gas::Gas; @@ -39,7 +40,7 @@ mod tests { fn interpret_test_expect_success() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(10)]; assert!(ast.interpret(&mut Ctx::default(), &mut istack).is_ok()); @@ -50,7 +51,7 @@ mod tests { fn interpret_mutez_push_add() { let ast = parse("{ PUSH mutez 100; PUSH mutez 500; ADD }").unwrap(); let mut ctx = Ctx::default(); - let ast = ast.typecheck(&mut ctx, None, &mut tc_stk![]).unwrap(); + let ast = ast.typecheck(&mut ctx, None, &[]).unwrap(); let mut istack = stk![]; assert!(ast.interpret(&mut ctx, &mut istack).is_ok()); assert_eq!(istack, stk![TypedValue::Mutez(600)]); @@ -60,21 +61,21 @@ mod tests { fn interpret_test_gas_consumption() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx::default(); report_gas(&mut ctx, |ctx| { assert!(ast.interpret(ctx, &mut istack).is_ok()); }); - assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - 1359); + assert_eq!(Gas::default().milligas() - ctx.gas.milligas(), 1359); } #[test] fn interpret_test_gas_out_of_gas() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx { @@ -91,18 +92,19 @@ mod tests { fn typecheck_test_expect_success() { let ast = parse(FIBONACCI_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; - assert!(ast.typecheck(&mut Ctx::default(), None, &mut stack).is_ok()); + assert!( + typechecker::typecheck_instruction(&ast, &mut Ctx::default(), None, &mut stack).is_ok() + ); assert_eq!(stack, tc_stk![Type::Int]) } #[test] fn typecheck_gas() { let ast = parse(FIBONACCI_SRC).unwrap(); - let mut stack = tc_stk![Type::Nat]; let mut ctx = Ctx::default(); let start_milligas = ctx.gas.milligas(); report_gas(&mut ctx, |ctx| { - assert!(ast.typecheck(ctx, None, &mut stack).is_ok()); + assert!(ast.typecheck(ctx, None, &[app!(nat)]).is_ok()); }); assert_eq!(start_milligas - ctx.gas.milligas(), 12680); } @@ -110,13 +112,12 @@ mod tests { #[test] fn typecheck_out_of_gas() { let ast = parse(FIBONACCI_SRC).unwrap(); - let mut stack = tc_stk![Type::Nat]; let mut ctx = Ctx { gas: Gas::new(1000), ..Ctx::default() }; assert_eq!( - ast.typecheck(&mut ctx, None, &mut stack), + ast.typecheck(&mut ctx, None, &[app!(nat)]), Err(typechecker::TcError::OutOfGas(crate::gas::OutOfGas)) ); } @@ -125,9 +126,8 @@ mod tests { fn typecheck_test_expect_fail() { use typechecker::{NoMatchingOverloadReason, TcError}; let ast = parse(FIBONACCI_ILLTYPED_SRC).unwrap(); - let mut stack = tc_stk![Type::Nat]; assert_eq!( - ast.typecheck(&mut Ctx::default(), None, &mut stack), + ast.typecheck(&mut Ctx::default(), None, &[app!(nat)]), Err(TcError::NoMatchingOverload { instr: crate::lexer::Prim::DUP, stack: stk![Type::Int, Type::Int, Type::Int], @@ -178,9 +178,17 @@ mod tests { #[test] fn parser_test_expect_fail() { + use crate::ast::micheline::test_helpers::app; assert_eq!( - &parse(FIBONACCI_MALFORMED_SRC).unwrap_err().to_string(), - "Unrecognized token `GT` found at 133:135\nExpected one of \";\" or \"}\"" + parse(FIBONACCI_MALFORMED_SRC).unwrap().typecheck( + &mut Ctx::default(), + None, + &[app!(nat)] + ), + Err(typechecker::TcError::UnexpectedMicheline(format!( + "{:?}", + app!(DUP[4, app!(GT)]) + ))) ); } @@ -195,6 +203,7 @@ mod tests { #[test] fn vote_contract() { + use crate::ast::micheline::test_helpers::*; let mut ctx = Ctx { amount: 5_000_000, ..Ctx::default() @@ -206,7 +215,7 @@ mod tests { .interpret( &mut ctx, "foo".into(), - vec![Elt("bar", 0), Elt("baz", 0), Elt("foo", 0)].into(), + seq! {app!(Elt["bar", 0]); app!(Elt["baz", 0]); app!(Elt["foo", 0])}, ); use TypedValue as TV; match interp_res.unwrap() { diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 217497e9a8d6..bf04bdabbbed 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -13,6 +13,9 @@ pub mod type_props; use type_props::TypeProperty; +use crate::ast::micheline::{ + micheline_fields, micheline_instructions, micheline_literals, micheline_types, micheline_values, +}; use crate::ast::michelson_address::AddressHash; use crate::ast::*; use crate::context::Ctx; @@ -39,10 +42,10 @@ pub enum TcError { TypesNotEqual(#[from] TypesNotEqual), #[error("DUP 0 is forbidden")] Dup0, - #[error("value {0:?} is invalid for type {1:?}")] - InvalidValueForType(Value, Type), + #[error("value {0} is invalid for type {1:?}")] + InvalidValueForType(String, Type), #[error("value {0:?} is invalid element for container type {1:?}")] - InvalidEltForMap(Value, Type), + InvalidEltForMap(String, Type), #[error("sequence elements must be in strictly ascending order for type {0:?}")] ElementsNotSorted(Type), #[error("sequence elements must contain no duplicate keys for type {0:?}")] @@ -63,6 +66,14 @@ pub enum TcError { NoSuchEntrypoint(Entrypoint), #[error("unexpected implicit account parameter type: {0:?}")] UnexpectedImplicitAccountType(Type), + #[error("unexpected syntax: {0}")] + UnexpectedMicheline(String), + #[error("duplicate top-level element: {0}")] + DuplicateTopLevelElt(Prim), + #[error("missing top-level element: {0}")] + MissingTopLevelElt(Prim), + #[error("expected a natural between 0 and 1023, but got {0}")] + ExpectedU10(i128), } #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] @@ -116,46 +127,110 @@ pub enum StacksNotEqualReason { pub struct TypesNotEqual(Type, Type); impl Micheline<'_> { - pub fn typecheck_value(&self, ctx: &mut Ctx, ty: &Type) -> Result { - todo!(); + /// Typechecks `Micheline` as a value, given its type (also as `Micheline`). + /// Validates the type. + pub fn typecheck_value( + &self, + ctx: &mut Ctx, + value_type: &Micheline, + ) -> Result { + let ty = parse_ty(ctx, value_type)?; + typecheck_value(ctx, &ty, self) } + /// Typechecks `Micheline` as an instruction (or a sequence of instruction), + /// given its input stack type as a slice of `Micheline`. Last element of + /// the slice is on the top of the stack. + /// Validates the type. + /// + /// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like + /// in lambdas). pub fn typecheck( &self, ctx: &mut Ctx, - self_type: Option<&Type>, - stack: &mut FailingTypeStack, + self_type: Option<&Micheline>, + stack: &[Micheline], ) -> Result { - todo!(); + let self_type = self_type + .map(|ty| { + let ty = parse_ty(ctx, ty)?; + ty.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; + Ok::<_, TcError>(ty) + }) + .transpose()?; + let TopIsLast(checked_stack) = stack + .iter() + .map(|ty| parse_ty(ctx, ty)) + .collect::>()?; + let mut opt_stack = FailingTypeStack::Ok(checked_stack); + typecheck_instruction(self, ctx, self_type.as_ref(), &mut opt_stack) } - pub fn typecheck_ty(&self, ctx: &mut Ctx) -> Result { - todo!(); + /// Parse `Micheline` as a type. Validates the type. + pub fn parse_ty(&self, ctx: &mut Ctx) -> Result { + parse_ty(ctx, self) } + /// Typecheck the contract script. Validates the script's types, then + /// typechecks the code and checks the result stack is as expected. Returns + /// typechecked script. pub fn typecheck_script( &self, ctx: &mut Ctx, ) -> Result, TcError> { - todo!(); - } -} - -impl ContractScript { - /// Typecheck the contract script. Validates the script's types, then - /// typechecks the code and checks the result stack is as expected. Returns - /// typechecked script. - pub fn typecheck( - self, - ctx: &mut crate::context::Ctx, - ) -> Result, crate::typechecker::TcError> { - let ContractScript { - parameter, storage, .. - } = self; + let seq = match self { + // top-level allows one level of nesting + Micheline::Seq([Micheline::Seq(seq)]) => seq, + Micheline::Seq(seq) => seq, + x => return Err(TcError::UnexpectedMicheline(format!("{x:?}"))), + }; + let mut parameter_ty = None; + let mut storage_ty = None; + let mut code = None; + fn set_if_none(elt: Prim, var: &mut Option, value: T) -> Result<(), TcError> { + if var.is_none() { + *var = Some(value); + Ok(()) + } else { + Err(TcError::DuplicateTopLevelElt(elt)) + } + } + for elt in seq.iter() { + match elt { + Micheline::App(Prim::code, [content], anns) if anns.is_empty() => { + set_if_none(Prim::code, &mut code, content)? + } + Micheline::App(Prim::parameter, [content], anns) if anns.is_empty() => { + set_if_none(Prim::parameter, &mut parameter_ty, content)? + } + Micheline::App(Prim::storage, [content], anns) if anns.is_empty() => { + set_if_none(Prim::storage, &mut storage_ty, content)? + } + Micheline::Seq(..) + | micheline_instructions!() + | micheline_literals!() + | micheline_types!() + | micheline_fields!() + | micheline_values!() => { + return Err(TcError::UnexpectedMicheline(format!("{elt:?}"))) + } + } + } + let parameter = parameter_ty + .ok_or(TcError::MissingTopLevelElt(Prim::parameter))? + .parse_ty(ctx)?; + let storage = storage_ty + .ok_or(TcError::MissingTopLevelElt(Prim::storage))? + .parse_ty(ctx)?; parameter.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; storage.ensure_prop(&mut ctx.gas, TypeProperty::Storable)?; let mut stack = tc_stk![Type::new_pair(parameter.clone(), storage.clone())]; - let code = self.code.typecheck(ctx, Some(¶meter), &mut stack)?; + let code = typecheck_instruction( + code.ok_or(TcError::MissingTopLevelElt(Prim::code))?, + ctx, + Some(¶meter), + &mut stack, + )?; unify_stacks( ctx, &mut tc_stk![Type::new_pair( @@ -172,57 +247,89 @@ impl ContractScript { } } -impl ParsedInstruction { - /// Typecheck an individual instruction. Validates the passed stack types. - /// - /// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like - /// in lambdas). - pub fn typecheck( - self, +pub(crate) fn parse_ty(ctx: &mut Ctx, ty: &Micheline) -> Result { + use Micheline::*; + use Prim::*; + ctx.gas.consume(gas::tc_cost::PARSE_TYPE_STEP)?; + fn make_pair( ctx: &mut Ctx, - self_type: Option<&Type>, - opt_stack: &mut FailingTypeStack, - ) -> Result { - if let Ok(stack) = opt_stack.access_mut(()) { - stack.iter().try_for_each(|ty| verify_ty(ctx, ty))?; - } - typecheck_instruction(self, ctx, self_type, opt_stack) + args: (&Micheline, &Micheline, &[Micheline]), + // NB: the tuple models a slice of at least 2 elements + ) -> Result { + Ok(match args { + (ty1, ty2, []) => Type::new_pair(parse_ty(ctx, ty1)?, parse_ty(ctx, ty2)?), + (ty1, ty2, [ty3, rest @ ..]) => { + Type::new_pair(parse_ty(ctx, ty1)?, make_pair(ctx, (ty2, ty3, rest))?) + } + }) } -} + let unexpected = || Err(TcError::UnexpectedMicheline(format!("{ty:?}"))); + let ty = match ty { + App(int, [], _) => Type::Int, + App(int, ..) => unexpected()?, -impl Value { - /// Typecheck a value. Validates the input type. - pub fn typecheck(self, ctx: &mut Ctx, t: &Type) -> Result { - verify_ty(ctx, t)?; - typecheck_value(ctx, t, self) - } -} + App(nat, [], _) => Type::Nat, + App(nat, ..) => unexpected()?, -/// Checks type invariants, e.g. that `map` key is comparable. -fn verify_ty(ctx: &mut Ctx, t: &Type) -> Result<(), TcError> { - use Type::*; - ctx.gas.consume(gas::tc_cost::VERIFY_TYPE_STEP)?; - match t { - Nat | Int | Bool | Mutez | String | Operation | Unit | Address | ChainId => Ok(()), - Pair(tys) | Or(tys) => { - verify_ty(ctx, &tys.0)?; - verify_ty(ctx, &tys.1) - } - Option(ty) | List(ty) => verify_ty(ctx, ty), - Map(tys) => { - tys.0.ensure_prop(&mut ctx.gas, TypeProperty::Comparable)?; - verify_ty(ctx, &tys.0)?; - verify_ty(ctx, &tys.1) - } - Contract(ty) => { + App(bool, [], _) => Type::Bool, + App(bool, ..) => unexpected()?, + + App(mutez, [], _) => Type::Mutez, + App(mutez, ..) => unexpected()?, + + App(string, [], _) => Type::String, + App(string, ..) => unexpected()?, + + App(operation, [], _) => Type::Operation, + App(operation, ..) => unexpected()?, + + App(unit, [], _) => Type::Unit, + App(unit, ..) => unexpected()?, + + App(address, [], _) => Type::Address, + App(address, ..) => unexpected()?, + + App(chain_id, [], _) => Type::ChainId, + App(chain_id, ..) => unexpected()?, + + App(pair, [ty1, ty2, rest @ ..], _) => make_pair(ctx, (ty1, ty2, rest))?, + App(pair, ..) => unexpected()?, + + App(or, [l, r], _) => Type::new_or(parse_ty(ctx, l)?, parse_ty(ctx, r)?), + App(or, ..) => unexpected()?, + + App(option, [t], _) => Type::new_option(parse_ty(ctx, t)?), + App(option, ..) => unexpected()?, + + App(list, [t], _) => Type::new_list(parse_ty(ctx, t)?), + App(list, ..) => unexpected()?, + + App(contract, [t], _) => { + let t = parse_ty(ctx, t)?; // NB: despite `contract` type being duplicable and packable, its // argument doesn't need to be. The only constraint is that it needs // to be passable, as it represents the contract's parameter type. // See https://tezos.gitlab.io/michelson-reference/#type-contract - ty.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; - verify_ty(ctx, ty) + t.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; + Type::new_contract(t) } - } + App(contract, ..) => unexpected()?, + + App(map, [k, v], _) => { + let k = parse_ty(ctx, k)?; + k.ensure_prop(&mut ctx.gas, TypeProperty::Comparable)?; + let v = parse_ty(ctx, v)?; + Type::new_map(k, v) + } + App(map, ..) => unexpected()?, + + Seq(..) + | micheline_fields!() + | micheline_instructions!() + | micheline_literals!() + | micheline_values!() => unexpected()?, + }; + Ok(ty) } /// Typecheck a sequence of instructions. Assumes the passed stack is valid, i.e. @@ -234,22 +341,22 @@ fn verify_ty(ctx: &mut Ctx, t: &Type) -> Result<(), TcError> { /// Self type is carried as an argument, not as part of context, because it has /// to be locally overridden during typechecking. fn typecheck( - ast: ParsedAST, + ast: &[Micheline], ctx: &mut Ctx, self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, ) -> Result { - ast.into_iter() + ast.iter() .map(|i| typecheck_instruction(i, ctx, self_type, opt_stack)) .collect() } macro_rules! nothing_to_none { () => { - None + Option::None }; ($e:expr) => { - Some($e) + Option::Some($e) }; } @@ -261,8 +368,8 @@ macro_rules! nothing_to_none { /// /// Self type is carried as an argument, not as part of context, because it has /// to be locally overridden during typechecking. -fn typecheck_instruction( - i: ParsedInstruction, +pub(crate) fn typecheck_instruction( + i: &Micheline, ctx: &mut Ctx, self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, @@ -305,7 +412,7 @@ fn typecheck_instruction( return Err(TcError::NoMatchingOverload { instr: Prim::$instr, stack: stack.clone(), - reason: Some(NoMatchingOverloadReason::StackTooShort { + reason: Option::Some(NoMatchingOverloadReason::StackTooShort { expected: $expected_len }), }) @@ -322,34 +429,93 @@ fn typecheck_instruction( }; } + /// Pattern synonym matching any `Micheline` except `Seq` + macro_rules! micheline_non_seq { + () => { + micheline_fields!() + | micheline_instructions!() + | micheline_literals!() + | micheline_types!() + | micheline_values!() + }; + } + + /// Pattern synonym matching any number of slice elements _except_ the number + /// passed as the argument. If the number is suffixed with `seq`, the pattern + /// will also match the number of elements equal to the argument iff either of + /// those isn't `Micheline::Seq`. + /// + /// This is useful for the last "catchall" pattern for invalid Micheline to + /// recover some measure of totality. + macro_rules! expect_args { + (0) => { + [_, ..] + }; + (1) => { + [] | [_, _, ..] + }; + (2) => { + [] | [_] | [_, _, _, ..] + }; + (1 seq) => { + expect_args!(1) | [micheline_non_seq!()] + }; + (2 seq) => { + expect_args!(2) + | [micheline_non_seq!(), micheline_non_seq!()] + | [Micheline::Seq(..), micheline_non_seq!()] + | [micheline_non_seq!(), Micheline::Seq(..)] + }; + } + ctx.gas.consume(gas::tc_cost::INSTR_STEP)?; + use Micheline::*; + use Prim::*; + + macro_rules! unexpected_micheline { + () => { + return Err(TcError::UnexpectedMicheline(format!("{i:?}"))) + }; + } + Ok(match (i, stack.as_slice()) { - (I::Add(..), [.., T::Nat, T::Nat]) => { + ( + micheline_types!() | micheline_literals!() | micheline_fields!() | micheline_values!(), + _, + ) => unexpected_micheline!(), + + (App(ADD, [], _), [.., T::Nat, T::Nat]) => { pop!(); I::Add(overloads::Add::NatNat) } - (I::Add(..), [.., T::Int, T::Int]) => { + (App(ADD, [], _), [.., T::Int, T::Int]) => { pop!(); I::Add(overloads::Add::IntInt) } - (I::Add(..), [.., T::Nat, T::Int]) => { + (App(ADD, [], _), [.., T::Nat, T::Int]) => { pop!(); stack[0] = T::Int; I::Add(overloads::Add::IntNat) } - (I::Add(..), [.., T::Int, T::Nat]) => { + (App(ADD, [], _), [.., T::Int, T::Nat]) => { pop!(); I::Add(overloads::Add::NatInt) } - (I::Add(..), [.., T::Mutez, T::Mutez]) => { + (App(ADD, [], _), [.., T::Mutez, T::Mutez]) => { pop!(); I::Add(overloads::Add::MutezMutez) } - (I::Add(..), [.., _, _]) => no_overload!(ADD), - (I::Add(..), [_] | []) => no_overload!(ADD, len 2), - - (I::Dip(opt_height, nested), ..) => { + (App(ADD, [], _), [.., _, _]) => no_overload!(ADD), + (App(ADD, [], _), [_] | []) => no_overload!(ADD, len 2), + (App(ADD, expect_args!(0), _), _) => unexpected_micheline!(), + + (App(DIP, args, _), ..) => { + let (opt_height, nested) = match args { + [Int(height), Seq(nested)] => (Option::Some(validate_u10(*height)?), nested), + [Seq(nested)] => (Option::None, nested), + _ => unexpected_micheline!(), + }; let protected_height = opt_height.unwrap_or(1) as usize; ctx.gas.consume(gas::tc_cost::dip_n(&opt_height)?)?; @@ -365,7 +531,12 @@ fn typecheck_instruction( I::Dip(opt_height, nested) } - (I::Drop(opt_height), ..) => { + (App(DROP, args, _), ..) => { + let opt_height = match args { + [Int(height)] => Option::Some(validate_u10(*height)?), + [] => Option::None, + _ => unexpected_micheline!(), + }; let drop_height: usize = opt_height.unwrap_or(1) as usize; ctx.gas.consume(gas::tc_cost::drop_n(&opt_height)?)?; ensure_stack_len(Prim::DROP, stack, drop_height)?; @@ -374,8 +545,13 @@ fn typecheck_instruction( } // DUP instruction requires an argument that is > 0. - (I::Dup(Some(0)), ..) => return Err(TcError::Dup0), - (I::Dup(opt_height), ..) => { + (App(DUP, [Int(0)], _), ..) => return Err(TcError::Dup0), + (App(DUP, args, _), ..) => { + let opt_height = match args { + [Int(height)] => Option::Some(validate_u10(*height)?), + [] => Option::None, + _ => unexpected_micheline!(), + }; let dup_height: usize = opt_height.unwrap_or(1) as usize; ensure_stack_len(Prim::DUP, stack, dup_height)?; let ty = &stack[dup_height - 1]; @@ -384,14 +560,15 @@ fn typecheck_instruction( I::Dup(opt_height) } - (I::Gt, [.., T::Int]) => { + (App(GT, [], _), [.., T::Int]) => { stack[0] = T::Bool; I::Gt } - (I::Gt, [.., t]) => no_overload!(GT, TypesNotEqual(T::Int, t.clone())), - (I::Gt, []) => no_overload!(GT, len 1), + (App(GT, [], _), [.., t]) => no_overload!(GT, TypesNotEqual(T::Int, t.clone())), + (App(GT, [], _), []) => no_overload!(GT, len 1), + (App(GT, expect_args!(0), _), _) => unexpected_micheline!(), - (I::If(nested_t, nested_f), [.., T::Bool]) => { + (App(IF, [Seq(nested_t), Seq(nested_f)], _), [.., T::Bool]) => { // pop the bool off the stack pop!(); // Clone the stack so that we have a copy to run one branch on. @@ -403,10 +580,13 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, f_opt_stack)?; I::If(nested_t, nested_f) } - (I::If(..), [.., t]) => no_overload!(IF, TypesNotEqual(T::Bool, t.clone())), - (I::If(..), []) => no_overload!(IF, len 1), + (App(IF, [Seq(_), Seq(_)], _), [.., t]) => { + no_overload!(IF, TypesNotEqual(T::Bool, t.clone())) + } + (App(IF, [Seq(_), Seq(_)], _), []) => no_overload!(IF, len 1), + (App(IF, expect_args!(2 seq), _), _) => unexpected_micheline!(), - (I::IfNone(when_none, when_some), [.., T::Option(..)]) => { + (App(IF_NONE, [Seq(when_none), Seq(when_some)], _), [.., T::Option(..)]) => { // Extract option type let ty = pop!(T::Option); // Clone the some_stack as we need to push a type on top of it @@ -419,10 +599,13 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, some_opt_stack)?; I::IfNone(when_none, when_some) } - (I::IfNone(..), [.., t]) => no_overload!(IF_NONE, NMOR::ExpectedOption(t.clone())), - (I::IfNone(..), []) => no_overload!(IF_NONE, len 1), + (App(IF_NONE, [Seq(_), Seq(_)], _), [.., t]) => { + no_overload!(IF_NONE, NMOR::ExpectedOption(t.clone())) + } + (App(IF_NONE, [Seq(_), Seq(_)], _), []) => no_overload!(IF_NONE, len 1), + (App(IF_NONE, expect_args!(2 seq), _), _) => unexpected_micheline!(), - (I::IfCons(when_cons, when_nil), [.., T::List(..)]) => { + (App(IF_CONS, [Seq(when_cons), Seq(when_nil)], _), [.., T::List(..)]) => { // Clone the cons_stack as we need to push a type on top of it let mut cons_stack: TypeStack = stack.clone(); // get the list element type @@ -436,10 +619,13 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, cons_opt_stack)?; I::IfCons(when_cons, when_nil) } - (I::IfCons(..), [.., t]) => no_overload!(IF_CONS, NMOR::ExpectedList(t.clone())), - (I::IfCons(..), []) => no_overload!(IF_CONS, len 1), + (App(IF_CONS, [Seq(_), Seq(_)], _), [.., t]) => { + no_overload!(IF_CONS, NMOR::ExpectedList(t.clone())) + } + (App(IF_CONS, [Seq(_), Seq(_)], _), []) => no_overload!(IF_CONS, len 1), + (App(IF_CONS, expect_args!(2 seq), _), _) => unexpected_micheline!(), - (I::IfLeft(when_left, when_right), [.., T::Or(..)]) => { + (App(IF_LEFT, [Seq(when_left), Seq(when_right)], _), [.., T::Or(..)]) => { // get the list element type let (tl, tr) = *pop!(T::Or); // use main stack as left branch, cloned stack as right @@ -453,17 +639,21 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, opt_right_stack)?; I::IfLeft(when_left, when_right) } - (I::IfLeft(..), [.., t]) => no_overload!(IF_LEFT, NMOR::ExpectedOr(t.clone())), - (I::IfLeft(..), []) => no_overload!(IF_LEFT, len 1), + (App(IF_LEFT, [Seq(_), Seq(_)], _), [.., t]) => { + no_overload!(IF_LEFT, NMOR::ExpectedOr(t.clone())) + } + (App(IF_LEFT, [Seq(_), Seq(_)], _), []) => no_overload!(IF_LEFT, len 1), + (App(IF_LEFT, expect_args!(2 seq), _), _) => unexpected_micheline!(), - (I::Int, [.., T::Nat]) => { + (App(INT, [], _), [.., T::Nat]) => { stack[0] = Type::Int; I::Int } - (I::Int, [.., _]) => no_overload!(INT), - (I::Int, []) => no_overload!(INT, len 1), + (App(INT, [], _), [.., _]) => no_overload!(INT), + (App(INT, [], _), []) => no_overload!(INT, len 1), + (App(INT, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Loop(nested), [.., T::Bool]) => { + (App(LOOP, [Seq(nested)], _), [.., T::Bool]) => { // copy stack for unifying with it later let opt_copy = FailingTypeStack::Ok(stack.clone()); // Pop the bool off the top @@ -476,10 +666,13 @@ fn typecheck_instruction( opt_stack.access_mut(()).ok().map(Stack::pop); I::Loop(nested) } - (I::Loop(..), [.., ty]) => no_overload!(LOOP, TypesNotEqual(T::Bool, ty.clone())), - (I::Loop(..), []) => no_overload!(LOOP, len 1), + (App(LOOP, [Seq(_)], _), [.., ty]) => { + no_overload!(LOOP, TypesNotEqual(T::Bool, ty.clone())) + } + (App(LOOP, [Seq(_)], _), []) => no_overload!(LOOP, len 1), + (App(LOOP, expect_args!(1 seq), _), _) => unexpected_micheline!(), - (I::Iter(.., nested), [.., T::List(..)]) => { + (App(ITER, [Seq(nested)], ..), [.., T::List(..)]) => { // get the list element type let ty = *pop!(T::List); // clone the rest of the stack @@ -492,7 +685,7 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, opt_inner_stack)?; I::Iter(overloads::Iter::List, nested) } - (I::Iter(.., nested), [.., T::Map(..)]) => { + (App(ITER, [Seq(nested)], _), [.., T::Map(..)]) => { // get the map element type let kty_vty_box = pop!(T::Map); // clone the rest of the stack @@ -505,24 +698,27 @@ fn typecheck_instruction( unify_stacks(ctx, opt_stack, opt_inner_stack)?; I::Iter(overloads::Iter::Map, nested) } - (I::Iter(..), [.., _]) => no_overload!(ITER), - (I::Iter(..), []) => no_overload!(ITER, len 1), + (App(ITER, [Seq(_)], _), [.., _]) => no_overload!(ITER), + (App(ITER, [Seq(_)], _), []) => no_overload!(ITER, len 1), + (App(ITER, expect_args!(1 seq), _), _) => unexpected_micheline!(), - (I::Push((t, v)), ..) => { + (App(PUSH, [t, v], _), ..) => { + let t = parse_ty(ctx, t)?; t.ensure_prop(&mut ctx.gas, TypeProperty::Pushable)?; - verify_ty(ctx, &t)?; let v = typecheck_value(ctx, &t, v)?; stack.push(t); I::Push(v) } + (App(PUSH, expect_args!(2), _), _) => unexpected_micheline!(), - (I::Swap, [.., _, _]) => { + (App(SWAP, [], _), [.., _, _]) => { stack.swap(0, 1); I::Swap } - (I::Swap, [] | [_]) => no_overload!(SWAP, len 2), + (App(SWAP, [], _), [] | [_]) => no_overload!(SWAP, len 2), + (App(SWAP, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Failwith(..), [.., _]) => { + (App(FAILWITH, [], _), [.., _]) => { let ty = pop!(); // NB: the docs for the FAILWITH instruction // https://tezos.gitlab.io/michelson-reference/#instr-FAILWITH claim @@ -534,58 +730,65 @@ fn typecheck_instruction( *opt_stack = FailingTypeStack::Failed; I::Failwith(ty) } - (I::Failwith(..), []) => no_overload!(FAILWITH, len 1), + (App(FAILWITH, [], _), []) => no_overload!(FAILWITH, len 1), + (App(FAILWITH, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Unit, ..) => { + (App(UNIT, [], _), ..) => { stack.push(T::Unit); I::Unit } + (App(UNIT, ..), _) => unexpected_micheline!(), - (I::Car, [.., T::Pair(..)]) => { + (App(CAR, [], _), [.., T::Pair(..)]) => { let (l, _) = *pop!(T::Pair); stack.push(l); I::Car } - (I::Car, [.., ty]) => no_overload!(CAR, NMOR::ExpectedPair(ty.clone())), - (I::Car, []) => no_overload!(CAR, len 1), + (App(CAR, [], _), [.., ty]) => no_overload!(CAR, NMOR::ExpectedPair(ty.clone())), + (App(CAR, [], _), []) => no_overload!(CAR, len 1), + (App(CAR, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Cdr, [.., T::Pair(..)]) => { + (App(CDR, [], _), [.., T::Pair(..)]) => { let (_, r) = *pop!(T::Pair); stack.push(r); I::Cdr } - (I::Cdr, [.., ty]) => no_overload!(CDR, NMOR::ExpectedPair(ty.clone())), - (I::Cdr, []) => no_overload!(CDR, len 1), + (App(CDR, [], _), [.., ty]) => no_overload!(CDR, NMOR::ExpectedPair(ty.clone())), + (App(CDR, [], _), []) => no_overload!(CDR, len 1), + (App(CDR, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Pair, [.., _, _]) => { + (App(PAIR, [], _), [.., _, _]) => { let (l, r) = (pop!(), pop!()); stack.push(Type::new_pair(l, r)); I::Pair } - (I::Pair, [] | [_]) => no_overload!(PAIR, len 2), + (App(PAIR, [], _), [] | [_]) => no_overload!(PAIR, len 2), + (App(PAIR, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Unpair, [.., T::Pair(..)]) => { + (App(UNPAIR, [], _), [.., T::Pair(..)]) => { let (l, r) = *pop!(T::Pair); stack.push(r); stack.push(l); I::Unpair } - (I::Unpair, [.., ty]) => no_overload!(UNPAIR, NMOR::ExpectedPair(ty.clone())), - (I::Unpair, []) => no_overload!(UNPAIR, len 1), + (App(UNPAIR, [], _), [.., ty]) => no_overload!(UNPAIR, NMOR::ExpectedPair(ty.clone())), + (App(UNPAIR, [], _), []) => no_overload!(UNPAIR, len 1), + (App(UNPAIR, expect_args!(0), _), _) => unexpected_micheline!(), - (I::ISome, [.., _]) => { + (App(SOME, [], _), [.., _]) => { let ty = pop!(); stack.push(T::new_option(ty)); I::ISome } - (I::ISome, []) => no_overload!(SOME, len 1), + (App(SOME, [], _), []) => no_overload!(SOME, len 1), + (App(SOME, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Compare, [.., u, t]) => { + (App(COMPARE, [], _), [.., u, t]) => { ensure_ty_eq(ctx, t, u).map_err(|e| match e { TcError::TypesNotEqual(e) => TcError::NoMatchingOverload { instr: Prim::COMPARE, stack: stack.clone(), - reason: Some(e.into()), + reason: Option::Some(e.into()), }, e => e, })?; @@ -594,38 +797,43 @@ fn typecheck_instruction( stack[0] = T::Int; I::Compare } - (I::Compare, [] | [_]) => no_overload!(COMPARE, len 2), + (App(COMPARE, [], _), [] | [_]) => no_overload!(COMPARE, len 2), + (App(COMPARE, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Amount, ..) => { + (App(AMOUNT, [], _), ..) => { stack.push(T::Mutez); I::Amount } + (App(AMOUNT, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Nil(ty), ..) => { - verify_ty(ctx, &ty)?; + (App(NIL, [ty], _), ..) => { + let ty = parse_ty(ctx, ty)?; stack.push(T::new_list(ty)); I::Nil(()) } + (App(NIL, ..), _) => unexpected_micheline!(), - (I::Cons, [.., T::List(ty1), ty2]) => { + (App(CONS, [], _), [.., T::List(ty1), ty2]) => { ensure_ty_eq(ctx, ty1, ty2)?; pop!(); I::Cons } - (I::Cons, [.., ty, _]) => no_overload!(CONS, NMOR::ExpectedList(ty.clone())), - (I::Cons, [] | [_]) => no_overload!(CONS, len 2), + (App(CONS, [], _), [.., ty, _]) => no_overload!(CONS, NMOR::ExpectedList(ty.clone())), + (App(CONS, [], _), [] | [_]) => no_overload!(CONS, len 2), + (App(CONS, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Get(..), [.., T::Map(..), _]) => { + (App(GET, [], _), [.., T::Map(..), _]) => { let kty_ = pop!(); let (kty, vty) = *pop!(T::Map); ensure_ty_eq(ctx, &kty, &kty_)?; stack.push(T::new_option(vty)); I::Get(overloads::Get::Map) } - (I::Get(..), [.., _, _]) => no_overload!(GET), - (I::Get(..), [] | [_]) => no_overload!(GET, len 2), + (App(GET, [], _), [.., _, _]) => no_overload!(GET), + (App(GET, [], _), [] | [_]) => no_overload!(GET, len 2), + (App(GET, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Update(..), [.., T::Map(m), T::Option(vty_new), kty_]) => { + (App(UPDATE, [], _), [.., T::Map(m), T::Option(vty_new), kty_]) => { let (kty, vty) = m.as_ref(); ensure_ty_eq(ctx, kty, kty_)?; ensure_ty_eq(ctx, vty, vty_new)?; @@ -633,81 +841,89 @@ fn typecheck_instruction( pop!(); I::Update(overloads::Update::Map) } - (I::Update(..), [.., _, _, _]) => no_overload!(UPDATE), - (I::Update(..), [] | [_] | [_, _]) => no_overload!(UPDATE, len 3), + (App(UPDATE, [], _), [.., _, _, _]) => no_overload!(UPDATE), + (App(UPDATE, [], _), [] | [_] | [_, _]) => no_overload!(UPDATE, len 3), + (App(UPDATE, expect_args!(0), _), _) => unexpected_micheline!(), - (I::ChainId, ..) => { + (App(CHAIN_ID, [], _), ..) => { stack.push(T::ChainId); I::ChainId } + (App(CHAIN_ID, expect_args!(0), _), _) => unexpected_micheline!(), - (I::ISelf, ..) => { + (App(SELF, [], _), ..) => { stack.push(T::new_contract( self_type.ok_or(TcError::SelfForbidden)?.clone(), )); I::ISelf } + (App(SELF, expect_args!(0), _), _) => unexpected_micheline!(), - (I::Seq(nested), ..) => I::Seq(typecheck(nested, ctx, self_type, opt_stack)?), + (Seq(nested), _) => I::Seq(typecheck(nested, ctx, self_type, opt_stack)?), }) } /// Typecheck a value. Assumes passed the type is valid, i.e. doesn't contain /// illegal types like `set operation` or `contract operation`. -fn typecheck_value(ctx: &mut Ctx, t: &Type, v: Value) -> Result { +pub(crate) fn typecheck_value( + ctx: &mut Ctx, + t: &Type, + v: &Micheline, +) -> Result { + use Micheline as V; use Type as T; use TypedValue as TV; - use Value as V; ctx.gas.consume(gas::tc_cost::VALUE_STEP)?; Ok(match (t, v) { - (T::Nat, V::Number(n)) => TV::Nat(n.try_into()?), - (T::Int, V::Number(n)) => TV::Int(n), - (T::Bool, V::Boolean(b)) => TV::Bool(b), - (T::Mutez, V::Number(n)) if n >= 0 => TV::Mutez(n.try_into()?), - (T::String, V::String(s)) => TV::String(s), - (T::Unit, V::Unit) => TV::Unit, - (T::Pair(pt), V::Pair(pv)) => { + (T::Nat, V::Int(n)) => TV::Nat((*n).try_into()?), + (T::Int, V::Int(n)) => TV::Int(*n), + (T::Bool, V::App(Prim::True, [], _)) => TV::Bool(true), + (T::Bool, V::App(Prim::False, [], _)) => TV::Bool(false), + (T::Mutez, V::Int(n)) if *n >= 0 => TV::Mutez((*n).try_into()?), + (T::String, V::String(s)) => TV::String(s.clone()), + (T::Unit, V::App(Prim::Unit, [], _)) => TV::Unit, + (T::Pair(pt), V::App(Prim::Pair, [vl, rest @ ..], _)) if !rest.is_empty() => { let (tl, tr) = pt.as_ref(); - let (vl, vr) = *pv; let l = typecheck_value(ctx, tl, vl)?; - let r = typecheck_value(ctx, tr, vr)?; + let r = match rest { + [vr] => typecheck_value(ctx, tr, vr)?, + vrs => typecheck_value(ctx, tr, &V::App(Prim::Pair, vrs, vec![]))?, + }; TV::new_pair(l, r) } - (T::Or(ot), V::Or(val)) => { + (T::Or(ot), V::App(prim @ (Prim::Left | Prim::Right), [val], _)) => { let (tl, tr) = ot.as_ref(); - let typed_val = match *val { - crate::ast::Or::Left(lv) => crate::ast::Or::Left(typecheck_value(ctx, tl, lv)?), - crate::ast::Or::Right(rv) => crate::ast::Or::Right(typecheck_value(ctx, tr, rv)?), + let typed_val = match prim { + Prim::Left => crate::ast::Or::Left(typecheck_value(ctx, tl, val)?), + Prim::Right => crate::ast::Or::Right(typecheck_value(ctx, tr, val)?), + _ => unreachable!(), }; TV::new_or(typed_val) } - (T::Option(ty), V::Option(v)) => match v { - Some(v) => { - let v = typecheck_value(ctx, ty, *v)?; - TV::new_option(Some(v)) - } - None => TV::new_option(None), - }, + (T::Option(ty), V::App(Prim::Some, [v], _)) => { + let v = typecheck_value(ctx, ty, v)?; + TV::new_option(Some(v)) + } + (T::Option(_), V::App(Prim::None, [], _)) => TV::new_option(None), (T::List(ty), V::Seq(vs)) => TV::List( - vs.into_iter() + vs.iter() .map(|v| typecheck_value(ctx, ty, v)) .collect::>()?, ), (T::Map(m), V::Seq(vs)) => { let (tk, tv) = m.as_ref(); - let tc_elt = |v: Value| -> Result<(TypedValue, TypedValue), TcError> { + let tc_elt = |v: &Micheline| -> Result<(TypedValue, TypedValue), TcError> { match v { - Value::Elt(e) => { - let (k, v) = *e; + Micheline::App(Prim::Elt, [k, v], _) => { let k = typecheck_value(ctx, tk, k)?; let v = typecheck_value(ctx, tv, v)?; Ok((k, v)) } - _ => Err(TcError::InvalidEltForMap(v, t.clone())), + _ => Err(TcError::InvalidEltForMap(format!("{v:?}"), t.clone())), } }; let elts: Vec<(TypedValue, TypedValue)> = - vs.into_iter().map(tc_elt).collect::>()?; + vs.iter().map(tc_elt).collect::>()?; if elts.len() > 1 { let mut prev = &elts[0].0; for i in &elts[1..] { @@ -735,11 +951,11 @@ fn typecheck_value(ctx: &mut Ctx, t: &Type, v: Value) -> Result { ctx.gas.consume(gas::tc_cost::KEY_HASH_READABLE)?; - TV::Address(Address::from_base58_check(&str)?) + TV::Address(Address::from_base58_check(str)?) } (T::Address, V::Bytes(bs)) => { ctx.gas.consume(gas::tc_cost::KEY_HASH_OPTIMIZED)?; - TV::Address(Address::from_bytes(&bs)?) + TV::Address(Address::from_bytes(bs)?) } (T::Contract(ty), addr) => { let t_addr = irrefutable_match!(typecheck_value(ctx, &T::Address, addr)?; TV::Address); @@ -769,18 +985,26 @@ fn typecheck_value(ctx: &mut Ctx, t: &Type, v: Value) -> Result { ctx.gas.consume(gas::tc_cost::CHAIN_ID_READABLE)?; TV::ChainId( - ChainId::from_base58_check(&str).map_err(|x| TcError::ChainIdError(x.into()))?, + ChainId::from_base58_check(str).map_err(|x| TcError::ChainIdError(x.into()))?, ) } (T::ChainId, V::Bytes(bs)) => { use tezos_crypto_rs::hash::HashTrait; ctx.gas.consume(gas::tc_cost::CHAIN_ID_OPTIMIZED)?; - TV::ChainId(ChainId::try_from_bytes(&bs).map_err(|x| TcError::ChainIdError(x.into()))?) + TV::ChainId(ChainId::try_from_bytes(bs).map_err(|x| TcError::ChainIdError(x.into()))?) } - (t, v) => return Err(TcError::InvalidValueForType(v, t.clone())), + (t, v) => return Err(TcError::InvalidValueForType(format!("{v:?}"), t.clone())), }) } +fn validate_u10(n: i128) -> Result { + let res = u16::try_from(n).map_err(|_| TcError::ExpectedU10(n))?; + if res >= 1024 { + return Err(TcError::ExpectedU10(n)); + } + Ok(res) +} + /// Ensures type stack is at least of the required length, otherwise returns /// `Err(StackTooShort)`. fn ensure_stack_len(instr: Prim, stack: &TypeStack, l: usize) -> Result<(), TcError> { @@ -846,17 +1070,15 @@ fn ensure_ty_eq(ctx: &mut Ctx, ty1: &Type, ty2: &Type) -> Result<(), TcError> { #[cfg(test)] mod typecheck_tests { + use crate::ast::micheline::test_helpers::*; use crate::gas::Gas; + use crate::parser::test_helpers::*; use crate::typechecker::*; use Instruction::*; - fn parse(s: &str) -> Result { - todo!() - } - /// hack to simplify syntax in tests fn typecheck_instruction( - i: ParsedInstruction, + i: &Micheline, ctx: &mut Ctx, opt_stack: &mut FailingTypeStack, ) -> Result { @@ -869,7 +1091,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Nat, Type::Nat]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Dup(Some(1)), &mut ctx, &mut stack), + typecheck_instruction(&app!(DUP[1]), &mut ctx, &mut stack), Ok(Dup(Some(1))) ); assert_eq!(stack, expected_stack); @@ -882,7 +1104,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Int, Type::Nat, Type::Int]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Dup(Some(2)), &mut ctx, &mut stack), + typecheck_instruction(&app!(DUP[2]), &mut ctx, &mut stack), Ok(Dup(Some(2))) ); assert_eq!(stack, expected_stack); @@ -894,7 +1116,10 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Nat, Type::Int]; let expected_stack = tc_stk![Type::Int, Type::Nat]; let mut ctx = Ctx::default(); - assert_eq!(typecheck_instruction(Swap, &mut ctx, &mut stack), Ok(Swap)); + assert_eq!( + typecheck_instruction(&app!(SWAP), &mut ctx, &mut stack), + Ok(Swap) + ); assert_eq!(stack, expected_stack); assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - 440); } @@ -904,7 +1129,10 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Nat]; let expected_stack = tc_stk![Type::Int]; let mut ctx = Ctx::default(); - assert_eq!(typecheck_instruction(Int, &mut ctx, &mut stack), Ok(Int)); + assert_eq!( + typecheck_instruction(&app!(INT), &mut ctx, &mut stack), + Ok(Int) + ); assert_eq!(stack, expected_stack); assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - 440); } @@ -915,7 +1143,7 @@ mod typecheck_tests { let expected_stack = tc_stk![]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Drop(None), &mut ctx, &mut stack), + typecheck_instruction(&app!(DROP), &mut ctx, &mut stack), Ok(Drop(None)) ); assert_eq!(stack, expected_stack); @@ -928,7 +1156,7 @@ mod typecheck_tests { let expected_stack = tc_stk![]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Drop(Some(2)), &mut ctx, &mut stack), + typecheck_instruction(&app!(DROP[2]), &mut ctx, &mut stack), Ok(Drop(Some(2))) ); assert_eq!(stack, expected_stack); @@ -941,7 +1169,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Nat, Type::Int]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Push((Type::Int, Value::Number(1))), &mut ctx, &mut stack), + typecheck_instruction(&app!(PUSH[app!(int), 1]), &mut ctx, &mut stack), Ok(Push(TypedValue::Int(1))) ); assert_eq!(stack, expected_stack); @@ -953,7 +1181,10 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Int]; let expected_stack = tc_stk![Type::Bool]; let mut ctx = Ctx::default(); - assert_eq!(typecheck_instruction(Gt, &mut ctx, &mut stack), Ok(Gt)); + assert_eq!( + typecheck_instruction(&app!(GT), &mut ctx, &mut stack), + Ok(Gt) + ); assert_eq!(stack, expected_stack); assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - 440); } @@ -964,7 +1195,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Int, Type::Nat, Type::Bool]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("DIP 1 {PUSH nat 6}").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("DIP 1 {PUSH nat 6}").unwrap(), &mut ctx, &mut stack), Ok(Dip(Some(1), vec![Push(TypedValue::Nat(6))])) ); assert_eq!(stack, expected_stack); @@ -977,7 +1208,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Int]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Add(()), &mut ctx, &mut stack), + typecheck_instruction(&app!(ADD), &mut ctx, &mut stack), Ok(Add(overloads::Add::IntInt)) ); assert_eq!(stack, expected_stack); @@ -990,7 +1221,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Nat]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Add(()), &mut ctx, &mut stack), + typecheck_instruction(&app!(ADD), &mut ctx, &mut stack), Ok(Add(overloads::Add::NatNat)) ); assert_eq!(stack, expected_stack); @@ -1003,7 +1234,7 @@ mod typecheck_tests { let expected_stack = tc_stk![Type::Mutez]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Add(()), &mut ctx, &mut stack), + typecheck_instruction(&app!(ADD), &mut ctx, &mut stack), Ok(Add(overloads::Add::MutezMutez)) ); assert_eq!(stack, expected_stack); @@ -1017,7 +1248,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - parse("LOOP {PUSH bool True}").unwrap(), + &parse("LOOP {PUSH bool True}").unwrap(), &mut ctx, &mut stack ), @@ -1033,7 +1264,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - parse("LOOP {PUSH int 1; PUSH bool True}").unwrap(), + &parse("LOOP {PUSH int 1; PUSH bool True}").unwrap(), &mut ctx, &mut stack ) @@ -1052,7 +1283,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - parse("LOOP {DROP; PUSH bool False; PUSH bool True}").unwrap(), + &parse("LOOP {DROP; PUSH bool False; PUSH bool True}").unwrap(), &mut ctx, &mut stack ) @@ -1070,7 +1301,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_list(Type::Int)]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { DROP }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { DROP }").unwrap(), &mut ctx, &mut stack), Ok(Iter(overloads::Iter::List, vec![Drop(None)])) ); assert_eq!(stack, tc_stk![]); @@ -1078,7 +1309,7 @@ mod typecheck_tests { #[test] fn test_iter_too_short() { - too_short_test(Iter((), vec![]), Prim::ITER, 1) + too_short_test(&app!(ITER[Micheline::Seq(&[])]), Prim::ITER, 1) } #[test] @@ -1086,7 +1317,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_list(Type::Int)]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { }").unwrap(), &mut ctx, &mut stack), Err(TcError::StacksNotEqual( stk![], stk![Type::Int], @@ -1100,7 +1331,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_map(Type::Int, Type::Nat)]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { CAR; DROP }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { CAR; DROP }").unwrap(), &mut ctx, &mut stack), Ok(Iter(overloads::Iter::Map, vec![Car, Drop(None)])) ); assert_eq!(stack, tc_stk![]); @@ -1111,7 +1342,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_map(Type::Int, Type::Nat)]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { }").unwrap(), &mut ctx, &mut stack), Err(TcError::StacksNotEqual( stk![], stk![Type::new_pair(Type::Int, Type::Nat)], @@ -1125,7 +1356,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { DROP }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { DROP }").unwrap(), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::ITER, stack: stk![Type::String], @@ -1139,7 +1370,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_list(Type::Int)]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(parse("ITER { FAILWITH }").unwrap(), &mut ctx, &mut stack), + typecheck_instruction(&parse("ITER { FAILWITH }").unwrap(), &mut ctx, &mut stack), Ok(Iter(overloads::Iter::List, vec![Failwith(Type::Int)])) ); assert_eq!(stack, tc_stk![]) @@ -1148,7 +1379,11 @@ mod typecheck_tests { #[test] fn test_failwith() { assert_eq!( - typecheck_instruction(Failwith(()), &mut Ctx::default(), &mut tc_stk![Type::Int]), + typecheck_instruction( + &app!(FAILWITH), + &mut Ctx::default(), + &mut tc_stk![Type::Int] + ), Ok(Failwith(Type::Int)) ); } @@ -1159,7 +1394,7 @@ mod typecheck_tests { ($code:expr) => { assert_eq!( typecheck_instruction( - parse($code).unwrap(), + &parse($code).unwrap(), &mut Ctx::default(), &mut tc_stk![] ), @@ -1173,7 +1408,7 @@ mod typecheck_tests { macro_rules! test_ok { ($code:expr) => { assert!(typecheck_instruction( - parse($code).unwrap(), + &parse($code).unwrap(), &mut Ctx::default(), &mut tc_stk![] ) @@ -1189,11 +1424,7 @@ mod typecheck_tests { #[test] fn string_values() { assert_eq!( - typecheck_value( - &mut Ctx::default(), - &Type::String, - Value::String("foo".to_owned()) - ), + typecheck_value(&mut Ctx::default(), &Type::String, &"foo".into()), Ok(TypedValue::String("foo".to_owned())) ) } @@ -1203,7 +1434,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH string "foo""#).unwrap(), + &parse(r#"PUSH string "foo""#).unwrap(), &mut Ctx::default(), &mut stack ), @@ -1217,7 +1448,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH unit Unit").unwrap(), + &parse("PUSH unit Unit").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1230,7 +1461,7 @@ mod typecheck_tests { fn unit_instruction() { let mut stack = tc_stk![]; assert_eq!( - typecheck_instruction(parse("UNIT").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("UNIT").unwrap(), &mut Ctx::default(), &mut stack), Ok(Unit) ); assert_eq!(stack, tc_stk![Type::Unit]); @@ -1241,7 +1472,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (pair int nat bool) (Pair -5 3 False)").unwrap(), + &parse("PUSH (pair int nat bool) (Pair -5 3 False)").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1264,7 +1495,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (or int bool) (Left 1)").unwrap(), + &parse("PUSH (or int bool) (Left 1)").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1278,7 +1509,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (or int bool) (Right False)").unwrap(), + &parse("PUSH (or int bool) (Right False)").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1292,7 +1523,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (option nat) (Some 3)").unwrap(), + &parse("PUSH (option nat) (Some 3)").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1306,7 +1537,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (option nat) None").unwrap(), + &parse("PUSH (option nat) None").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1320,7 +1551,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("{ PUSH (pair int nat bool) (Pair -5 3 False); CAR }").unwrap(), + &parse("{ PUSH (pair int nat bool) (Pair -5 3 False); CAR }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1340,7 +1571,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("{ PUSH (pair int nat bool) (Pair -5 3 False); CDR }").unwrap(), + &parse("{ PUSH (pair int nat bool) (Pair -5 3 False); CDR }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1359,7 +1590,7 @@ mod typecheck_tests { fn car_fail() { let mut stack = tc_stk![Type::Unit]; assert_eq!( - typecheck_instruction(parse("CAR").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("CAR").unwrap(), &mut Ctx::default(), &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::CAR, stack: stk![Type::Unit], @@ -1372,7 +1603,7 @@ mod typecheck_tests { fn cdr_fail() { let mut stack = tc_stk![Type::Unit]; assert_eq!( - typecheck_instruction(parse("CDR").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("CDR").unwrap(), &mut Ctx::default(), &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::CDR, stack: stk![Type::Unit], @@ -1385,7 +1616,7 @@ mod typecheck_tests { fn pair() { let mut stack = tc_stk![Type::Int, Type::Nat]; // NB: nat is top assert_eq!( - typecheck_instruction(parse("PAIR").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("PAIR").unwrap(), &mut Ctx::default(), &mut stack), Ok(Pair) ); assert_eq!(stack, tc_stk![Type::new_pair(Type::Nat, Type::Int)]); @@ -1395,7 +1626,7 @@ mod typecheck_tests { fn unpair() { let mut stack = tc_stk![Type::new_pair(Type::Nat, Type::Int)]; assert_eq!( - typecheck_instruction(parse("UNPAIR").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("UNPAIR").unwrap(), &mut Ctx::default(), &mut stack), Ok(Unpair) ); assert_eq!(stack, tc_stk![Type::Int, Type::Nat]); @@ -1406,7 +1637,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Int, Type::Nat]; // NB: nat is top assert_eq!( typecheck_instruction( - parse("{ PAIR; CAR }").unwrap(), + &parse("{ PAIR; CAR }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1420,7 +1651,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Int, Type::Nat]; // NB: nat is top assert_eq!( typecheck_instruction( - parse("{ PAIR; CDR }").unwrap(), + &parse("{ PAIR; CDR }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1434,7 +1665,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_option(Type::Int)]; assert_eq!( typecheck_instruction( - parse("IF_NONE { PUSH int 5; } {}").unwrap(), + &parse("IF_NONE { PUSH int 5; } {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1448,14 +1679,14 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_list(Type::Int)]; assert_eq!( typecheck_instruction( - parse("IF_CONS { DROP 2 } {}").unwrap(), + &parse("IF_CONS { DROP 2 } {}").unwrap(), &mut Ctx::default(), &mut stack ), Ok(IfCons(vec![Drop(Some(2))], vec![])) ); assert_eq!(stack, tc_stk![]); - too_short_test(IfCons(vec![], vec![]), Prim::IF_CONS, 1) + too_short_test(&app!(IF_CONS[seq!{}, seq!{}]), Prim::IF_CONS, 1) } #[test] @@ -1463,7 +1694,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; assert_eq!( typecheck_instruction( - parse("IF_CONS { DROP 2 } {}").unwrap(), + &parse("IF_CONS { DROP 2 } {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1480,7 +1711,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_list(Type::Int)]; assert_eq!( typecheck_instruction( - parse("IF_CONS {} {}").unwrap(), + &parse("IF_CONS {} {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1497,7 +1728,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_or(Type::Int, Type::Bool)]; assert_eq!( typecheck_instruction( - parse("IF_LEFT { GT } {}").unwrap(), + &parse("IF_LEFT { GT } {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1508,7 +1739,7 @@ mod typecheck_tests { #[test] fn if_left_too_short() { - too_short_test(IfLeft(vec![], vec![]), Prim::IF_LEFT, 1) + too_short_test(&app!(IF_LEFT[seq!{}, seq!{}]), Prim::IF_LEFT, 1) } #[test] @@ -1516,7 +1747,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_or(Type::Int, Type::Int)]; assert_eq!( typecheck_instruction( - parse("IF_LEFT {} {}").unwrap(), + &parse("IF_LEFT {} {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1530,7 +1761,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; assert_eq!( typecheck_instruction( - parse("IF_LEFT {} {}").unwrap(), + &parse("IF_LEFT {} {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1547,7 +1778,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::new_or(Type::Int, Type::Nat)]; assert_eq!( typecheck_instruction( - parse("IF_LEFT {} {}").unwrap(), + &parse("IF_LEFT {} {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1564,7 +1795,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Int]; assert_eq!( typecheck_instruction( - parse("IF_NONE { PUSH int 5; } {}").unwrap(), + &parse("IF_NONE { PUSH int 5; } {}").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1580,7 +1811,7 @@ mod typecheck_tests { fn some() { let mut stack = tc_stk![Type::Int]; assert_eq!( - typecheck_instruction(parse("SOME").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("SOME").unwrap(), &mut Ctx::default(), &mut stack), Ok(ISome) ); assert_eq!(stack, tc_stk![Type::new_option(Type::Int)]); @@ -1590,7 +1821,7 @@ mod typecheck_tests { fn compare_int() { let mut stack = tc_stk![Type::Int, Type::Int]; assert_eq!( - typecheck_instruction(parse("COMPARE").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("COMPARE").unwrap(), &mut Ctx::default(), &mut stack), Ok(Compare) ); assert_eq!(stack, tc_stk![Type::Int]); @@ -1600,7 +1831,7 @@ mod typecheck_tests { fn compare_int_fail() { let mut stack = tc_stk![Type::Int, Type::Nat]; assert_eq!( - typecheck_instruction(parse("COMPARE").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("COMPARE").unwrap(), &mut Ctx::default(), &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::COMPARE, stack: stk![Type::Int, Type::Nat], @@ -1613,7 +1844,7 @@ mod typecheck_tests { fn amount() { let mut stack = tc_stk![]; assert_eq!( - typecheck_instruction(parse("AMOUNT").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("AMOUNT").unwrap(), &mut Ctx::default(), &mut stack), Ok(Amount) ); assert_eq!(stack, tc_stk![Type::Mutez]); @@ -1624,7 +1855,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (list int) { 1; 2; 3 }").unwrap(), + &parse("PUSH (list int) { 1; 2; 3 }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1640,11 +1871,14 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("PUSH (list int) { 1; Unit; 3 }").unwrap(), + &parse("PUSH (list int) { 1; Unit; 3 }").unwrap(), &mut Ctx::default(), &mut stack ), - Err(TcError::InvalidValueForType(Value::Unit, Type::Int)) + Err(TcError::InvalidValueForType( + "App(Unit, [], [])".into(), + Type::Int + )) ); } @@ -1652,7 +1886,7 @@ mod typecheck_tests { fn nil() { let mut stack = tc_stk![]; assert_eq!( - typecheck_instruction(parse("NIL int").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("NIL int").unwrap(), &mut Ctx::default(), &mut stack), Ok(Nil(())) ); assert_eq!(stack, tc_stk![Type::new_list(Type::Int)]); @@ -1662,7 +1896,7 @@ mod typecheck_tests { fn cons() { let mut stack = tc_stk![Type::new_list(Type::Int), Type::Int]; assert_eq!( - typecheck_instruction(parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), Ok(Cons) ); assert_eq!(stack, tc_stk![Type::new_list(Type::Int)]); @@ -1670,14 +1904,14 @@ mod typecheck_tests { #[test] fn cons_too_short() { - too_short_test(Cons, Prim::CONS, 2); + too_short_test(&app!(CONS), Prim::CONS, 2); } #[test] fn cons_mismatch_elt() { let mut stack = tc_stk![Type::new_list(Type::Int), Type::Nat]; assert_eq!( - typecheck_instruction(parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), Err(TypesNotEqual(Type::Int, Type::Nat).into()) ); } @@ -1686,7 +1920,7 @@ mod typecheck_tests { fn cons_mismatch_list() { let mut stack = tc_stk![Type::String, Type::Nat]; assert_eq!( - typecheck_instruction(parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("CONS").unwrap(), &mut Ctx::default(), &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::CONS, stack: stk![Type::String, Type::Nat], @@ -1700,7 +1934,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse("NIL operation").unwrap(), + &parse("NIL operation").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1713,7 +1947,7 @@ mod typecheck_tests { fn failwith_operation() { let mut stack = tc_stk![Type::new_list(Type::Operation)]; assert_eq!( - typecheck_instruction(parse("FAILWITH").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("FAILWITH").unwrap(), &mut Ctx::default(), &mut stack), Err(TcError::InvalidTypeProperty( TypeProperty::Pushable, Type::Operation @@ -1726,7 +1960,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map int string) { Elt 1 "foo"; Elt 2 "bar" }"#).unwrap(), + &parse(r#"PUSH (map int string) { Elt 1 "foo"; Elt 2 "bar" }"#).unwrap(), &mut Ctx::default(), &mut stack ), @@ -1743,7 +1977,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map int string) { Elt 2 "foo"; Elt 1 "bar" }"#).unwrap(), + &parse(r#"PUSH (map int string) { Elt 2 "foo"; Elt 1 "bar" }"#).unwrap(), &mut Ctx::default(), &mut stack ), @@ -1759,7 +1993,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map (list int) string) { Elt { 2 } "foo"; Elt { 1 } "bar" }"#) + &parse(r#"PUSH (map (list int) string) { Elt { 2 } "foo"; Elt { 1 } "bar" }"#) .unwrap(), &mut Ctx::default(), &mut stack @@ -1776,7 +2010,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map (list int) string) { }"#).unwrap(), + &parse(r#"PUSH (map (list int) string) { }"#).unwrap(), &mut Ctx::default(), &mut stack ), @@ -1792,12 +2026,12 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map int string) { Elt "1" "foo"; Elt 2 "bar" }"#).unwrap(), + &parse(r#"PUSH (map int string) { Elt "1" "foo"; Elt 2 "bar" }"#).unwrap(), &mut Ctx::default(), &mut stack ), Err(TcError::InvalidValueForType( - Value::String("1".to_owned()), + "String(\"1\")".into(), Type::Int )) ); @@ -1808,12 +2042,12 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map int string) { Elt 1 "foo"; "bar" }"#).unwrap(), + &parse(r#"PUSH (map int string) { Elt 1 "foo"; "bar" }"#).unwrap(), &mut Ctx::default(), &mut stack ), Err(TcError::InvalidEltForMap( - Value::String("bar".to_owned()), + "String(\"bar\")".to_owned(), Type::new_map(Type::Int, Type::String) )) ); @@ -1824,7 +2058,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction( - parse(r#"PUSH (map int string) { Elt 1 "foo"; Elt 1 "bar" }"#).unwrap(), + &parse(r#"PUSH (map int string) { Elt 1 "foo"; Elt 1 "bar" }"#).unwrap(), &mut Ctx::default(), &mut stack ), @@ -1839,7 +2073,7 @@ mod typecheck_tests { fn get_map() { let mut stack = tc_stk![Type::new_map(Type::Int, Type::String), Type::Int]; assert_eq!( - typecheck_instruction(parse("GET").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("GET").unwrap(), &mut Ctx::default(), &mut stack), Ok(Get(overloads::Get::Map)) ); assert_eq!(stack, tc_stk![Type::new_option(Type::String)]); @@ -1847,14 +2081,15 @@ mod typecheck_tests { #[test] fn get_map_incomparable() { - let mut stack = tc_stk![ - Type::new_map(Type::new_list(Type::Int), Type::String), - Type::new_list(Type::Int) - ]; assert_eq!( - parse("GET") - .unwrap() - .typecheck(&mut Ctx::default(), None, &mut stack), + parse("GET").unwrap().typecheck( + &mut Ctx::default(), + None, + &[ + app!(map[app!(list[app!(int)]), app!(string)]), + app!(list[app!(int)]), + ] + ), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Int) @@ -1866,7 +2101,7 @@ mod typecheck_tests { fn get_map_wrong_type() { let mut stack = tc_stk![Type::new_map(Type::Int, Type::String), Type::Nat]; assert_eq!( - typecheck_instruction(parse("GET").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("GET").unwrap(), &mut Ctx::default(), &mut stack), Err(TypesNotEqual(Type::Int, Type::Nat).into()), ); } @@ -1879,7 +2114,7 @@ mod typecheck_tests { Type::Int ]; assert_eq!( - typecheck_instruction(parse("UPDATE").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("UPDATE").unwrap(), &mut Ctx::default(), &mut stack), Ok(Update(overloads::Update::Map)) ); assert_eq!(stack, tc_stk![Type::new_map(Type::Int, Type::String)]); @@ -1893,22 +2128,23 @@ mod typecheck_tests { Type::Int ]; assert_eq!( - typecheck_instruction(parse("UPDATE").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("UPDATE").unwrap(), &mut Ctx::default(), &mut stack), Err(TypesNotEqual(Type::String, Type::Nat).into()) ); } #[test] fn update_map_incomparable() { - let mut stack = tc_stk![ - Type::new_map(Type::new_list(Type::Int), Type::String), - Type::new_option(Type::String), - Type::new_list(Type::Int) - ]; assert_eq!( - parse("UPDATE") - .unwrap() - .typecheck(&mut Ctx::default(), None, &mut stack), + parse("UPDATE").unwrap().typecheck( + &mut Ctx::default(), + None, + &[ + app!(map[app!(list[app!(int)]), app!(string)]), + app!(option[app!(string)]), + app!(list[app!(int)]), + ] + ), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Int) @@ -1921,7 +2157,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::Int, Type::Nat]; assert_eq!( typecheck_instruction( - parse("{ { PAIR }; {{ CAR; }}; {}; {{{}}}; {{{{{DROP}}}}} }").unwrap(), + &parse("{ { PAIR }; {{ CAR; }}; {}; {{{}}}; {{{{{DROP}}}}} }").unwrap(), &mut Ctx::default(), &mut stack ), @@ -1940,7 +2176,7 @@ mod typecheck_tests { fn add_int_nat() { let mut stack = tc_stk![Type::Nat, Type::Int]; assert_eq!( - typecheck_instruction(parse("ADD").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("ADD").unwrap(), &mut Ctx::default(), &mut stack), Ok(Add(overloads::Add::IntNat)) ); assert_eq!(stack, tc_stk![Type::Int]); @@ -1950,18 +2186,18 @@ mod typecheck_tests { fn add_nat_int() { let mut stack = tc_stk![Type::Int, Type::Nat]; assert_eq!( - typecheck_instruction(parse("ADD").unwrap(), &mut Ctx::default(), &mut stack), + typecheck_instruction(&parse("ADD").unwrap(), &mut Ctx::default(), &mut stack), Ok(Add(overloads::Add::NatInt)) ); assert_eq!(stack, tc_stk![Type::Int]); } #[track_caller] - fn too_short_test(instr: ParsedInstruction, prim: Prim, len: usize) { + fn too_short_test(instr: &Micheline, prim: Prim, len: usize) { for n in 0..len { let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(instr.clone(), &mut ctx, &mut tc_stk![Type::Unit; n]), + typecheck_instruction(instr, &mut ctx, &mut tc_stk![Type::Unit; n]), Err(TcError::NoMatchingOverload { instr: prim, stack: stk![Type::Unit; n], @@ -1973,7 +2209,7 @@ mod typecheck_tests { #[test] fn test_add_short() { - too_short_test(Add(()), Prim::ADD, 2); + too_short_test(&app!(ADD), Prim::ADD, 2); } #[test] @@ -1981,7 +2217,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String, Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Add(()), &mut ctx, &mut stack), + typecheck_instruction(&app!(ADD), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::ADD, stack: stk![Type::String, Type::String], @@ -1995,7 +2231,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Dup(Some(0)), &mut ctx, &mut stack), + typecheck_instruction(&app!(DUP[0]), &mut ctx, &mut stack), Err(TcError::Dup0) ); } @@ -2005,7 +2241,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Gt, &mut ctx, &mut stack), + typecheck_instruction(&app!(GT), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::GT, stack: stk![Type::String], @@ -2016,7 +2252,7 @@ mod typecheck_tests { #[test] fn test_gt_short() { - too_short_test(Gt, Prim::GT, 1); + too_short_test(&app!(GT), Prim::GT, 1); } #[test] @@ -2024,7 +2260,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(If(vec![], vec![]), &mut ctx, &mut stack), + typecheck_instruction(&app!(IF[seq!{}, seq!{}]), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::IF, stack: stk![Type::String], @@ -2035,17 +2271,17 @@ mod typecheck_tests { #[test] fn test_if_short() { - too_short_test(If(vec![], vec![]), Prim::IF, 1); + too_short_test(&app!(IF[seq![], seq![]]), Prim::IF, 1); } #[test] fn test_if_none_short() { - too_short_test(IfNone(vec![], vec![]), Prim::IF_NONE, 1); + too_short_test(&app!(IF_NONE[seq![], seq![]]), Prim::IF_NONE, 1); } #[test] fn test_int_short() { - too_short_test(Int, Prim::INT, 1); + too_short_test(&app!(INT), Prim::INT, 1); } #[test] @@ -2053,7 +2289,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Int, &mut ctx, &mut stack), + typecheck_instruction(&app!(INT), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::INT, stack: stk![Type::String], @@ -2064,7 +2300,7 @@ mod typecheck_tests { #[test] fn test_loop_short() { - too_short_test(Loop(vec![]), Prim::LOOP, 1); + too_short_test(&app!(LOOP[seq![]]), Prim::LOOP, 1); } #[test] @@ -2072,7 +2308,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Loop(vec![]), &mut ctx, &mut stack), + typecheck_instruction(&app!(LOOP[seq![]]), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::LOOP, stack: stk![Type::String], @@ -2086,7 +2322,7 @@ mod typecheck_tests { let mut stack = tc_stk![Type::String]; let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Unpair, &mut ctx, &mut stack), + typecheck_instruction(&app!(UNPAIR), &mut ctx, &mut stack), Err(TcError::NoMatchingOverload { instr: Prim::UNPAIR, stack: stk![Type::String], @@ -2097,52 +2333,52 @@ mod typecheck_tests { #[test] fn test_swap_short() { - too_short_test(Swap, Prim::SWAP, 2); + too_short_test(&app!(SWAP), Prim::SWAP, 2); } #[test] fn test_pair_short() { - too_short_test(Pair, Prim::PAIR, 2); + too_short_test(&app!(PAIR), Prim::PAIR, 2); } #[test] fn test_get_short() { - too_short_test(Get(()), Prim::GET, 2); + too_short_test(&app!(GET), Prim::GET, 2); } #[test] fn test_update_short() { - too_short_test(Update(()), Prim::UPDATE, 3); + too_short_test(&app!(UPDATE), Prim::UPDATE, 3); } #[test] fn test_failwith_short() { - too_short_test(Failwith(()), Prim::FAILWITH, 1); + too_short_test(&app!(FAILWITH), Prim::FAILWITH, 1); } #[test] fn test_car_short() { - too_short_test(Car, Prim::CAR, 1); + too_short_test(&app!(CAR), Prim::CAR, 1); } #[test] fn test_cdr_short() { - too_short_test(Cdr, Prim::CDR, 1); + too_short_test(&app!(CDR), Prim::CDR, 1); } #[test] fn test_some_short() { - too_short_test(ISome, Prim::SOME, 1); + too_short_test(&app!(SOME), Prim::SOME, 1); } #[test] fn test_compare_short() { - too_short_test(Compare, Prim::COMPARE, 2); + too_short_test(&app!(COMPARE), Prim::COMPARE, 2); } #[test] fn test_unpair_short() { - too_short_test(Unpair, Prim::UNPAIR, 1); + too_short_test(&app!(UNPAIR), Prim::UNPAIR, 1); } #[test] @@ -2152,7 +2388,11 @@ mod typecheck_tests { ..Ctx::default() }; assert_eq!( - typecheck_instruction(Compare, &mut ctx, &mut tc_stk![Type::Unit, Type::Unit]), + typecheck_instruction( + &app!(COMPARE), + &mut ctx, + &mut tc_stk![Type::Unit, Type::Unit] + ), Err(TcError::OutOfGas(OutOfGas)) ); } @@ -2162,7 +2402,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - Compare, + &app!(COMPARE), &mut ctx, &mut tc_stk![Type::Operation, Type::Operation] ), @@ -2177,7 +2417,7 @@ mod typecheck_tests { fn test_get_mismatch() { let mut ctx = Ctx::default(); assert_eq!( - typecheck_instruction(Get(()), &mut ctx, &mut tc_stk![Type::Unit, Type::Unit]), + typecheck_instruction(&app!(GET), &mut ctx, &mut tc_stk![Type::Unit, Type::Unit]), Err(TcError::NoMatchingOverload { instr: Prim::GET, stack: stk![Type::Unit, Type::Unit], @@ -2191,7 +2431,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - Update(()), + &app!(UPDATE), &mut ctx, &mut tc_stk![Type::Unit, Type::Unit, Type::Unit] ), @@ -2208,7 +2448,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - Push((Type::Operation, Value::Unit)), + &app!(PUSH[app!(operation), app!(Unit)]), &mut ctx, &mut tc_stk![] ), @@ -2223,12 +2463,13 @@ mod typecheck_tests { fn test_non_passable_parameter() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::Operation, - storage: Type::Nat, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter operation;", + "storage nat;", + "code FAILWITH" + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Passable, Type::Operation @@ -2240,12 +2481,13 @@ mod typecheck_tests { fn test_non_storable_storage() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::Nat, - storage: Type::Operation, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter nat;", + "storage operation;", + "code FAILWITH" + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Storable, Type::Operation @@ -2257,12 +2499,13 @@ mod typecheck_tests { fn test_invalid_map() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::new_map(Type::new_list(Type::Unit), Type::Unit), - storage: Type::Nat, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter (map (list unit) unit);", + "storage nat;", + "code FAILWITH;", + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Unit) @@ -2277,10 +2520,8 @@ mod typecheck_tests { fn test_invalid_map_value() { let mut ctx = Ctx::default(); assert_eq!( - Value::from(vec![] as Vec<()>).typecheck( - &mut ctx, - &Type::new_map(Type::new_list(Type::Unit), Type::Unit) - ), + Micheline::Seq(&[]) + .typecheck_value(&mut ctx, &app!(map[app!(list[app!(unit)]), app!(unit)])), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Unit) @@ -2294,15 +2535,13 @@ mod typecheck_tests { fn test_nested_invalid_map() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::new_pair( - Type::Unit, - Type::new_option(Type::new_map(Type::new_list(Type::Unit), Type::Unit)) - ), - storage: Type::Nat, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter (pair unit (option (map (list unit) unit)));", + "storage nat;", + "code FAILWITH", + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Unit) @@ -2314,12 +2553,13 @@ mod typecheck_tests { fn test_contract_not_storable() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::Unit, - storage: Type::new_contract(Type::Unit), - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter unit;", + "storage (contract unit);", + "code FAILWITH;", + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Storable, Type::new_contract(Type::Unit) @@ -2332,7 +2572,7 @@ mod typecheck_tests { let mut ctx = Ctx::default(); assert_eq!( typecheck_instruction( - Push((Type::new_contract(Type::Unit), Value::Unit)), + &app!(PUSH[app!(contract[app!(unit)]), app!(Unit)]), &mut ctx, &mut tc_stk![] ), @@ -2347,12 +2587,13 @@ mod typecheck_tests { fn test_contract_with_unpassable_arg() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::new_contract(Type::Operation), - storage: Type::Unit, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter (contract operation);", + "storage unit;", + "code FAILWITH;" + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Passable, Type::Operation @@ -2364,12 +2605,13 @@ mod typecheck_tests { fn test_contract_is_passable() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::new_contract(Type::Unit), - storage: Type::Unit, - code: Seq(vec![Drop(None), Unit, Failwith(())]) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter (contract unit);", + "storage unit;", + "code { DROP; UNIT; FAILWITH };", + )) + .unwrap() + .typecheck_script(&mut ctx), Ok(ContractScript { parameter: Type::new_contract(Type::Unit), storage: Type::Unit, @@ -2382,12 +2624,13 @@ mod typecheck_tests { fn test_fail_with_contract_should_fail() { let mut ctx = Ctx::default(); assert_eq!( - ContractScript { - parameter: Type::new_contract(Type::Unit), - storage: Type::Unit, - code: Failwith(()) - } - .typecheck(&mut ctx), + parse_contract_script(concat!( + "parameter (contract unit);", + "storage unit;", + "code FAILWITH;", + )) + .unwrap() + .typecheck_script(&mut ctx), Err(TcError::InvalidTypeProperty( TypeProperty::Pushable, Type::new_contract(Type::Unit) @@ -2402,7 +2645,7 @@ mod typecheck_tests { let exp = Ok(Push(TypedValue::Address(exp))); assert_eq!( &typecheck_instruction( - parse(&format!("PUSH address {lit}")).unwrap(), + &parse(&format!("PUSH address {lit}")).unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2410,7 +2653,7 @@ mod typecheck_tests { ); assert_eq!( &typecheck_instruction( - parse(&format!("PUSH address {bytes}")).unwrap(), + &parse(&format!("PUSH address {bytes}")).unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2546,7 +2789,7 @@ mod typecheck_tests { assert_matches!( typecheck_instruction( - parse("PUSH address \"tz1foobarfoobarfoobarfoobarfoobarfoo\"").unwrap(), + &parse("PUSH address \"tz1foobarfoobarfoobarfoobarfoobarfoo\"").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2554,7 +2797,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address \"tz9foobarfoobarfoobarfoobarfoobarfoo\"").unwrap(), + &parse("PUSH address \"tz9foobarfoobarfoobarfoobarfoobarfoo\"").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2562,7 +2805,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address \"tz\"").unwrap(), + &parse("PUSH address \"tz\"").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2570,7 +2813,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0x0001fffe").unwrap(), + &parse("PUSH address 0x0001fffe").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2578,7 +2821,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0xff00fe0000000000000000000000000000000000000000").unwrap(), + &parse("PUSH address 0xff00fe0000000000000000000000000000000000000000").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2586,7 +2829,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0x00fffe0000000000000000000000000000000000000000").unwrap(), + &parse("PUSH address 0x00fffe0000000000000000000000000000000000000000").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2594,7 +2837,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0x00").unwrap(), + &parse("PUSH address 0x00").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2602,7 +2845,8 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0x011f2d825fdd9da219235510335e558520235f4f5401666f6f").unwrap(), + &parse("PUSH address 0x011f2d825fdd9da219235510335e558520235f4f5401666f6f") + .unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2610,7 +2854,7 @@ mod typecheck_tests { ); assert_matches!( typecheck_instruction( - parse("PUSH address 0x03d601f22256d2ad1faec0c64374e527c6e62f2e5a666f6f").unwrap(), + &parse("PUSH address 0x03d601f22256d2ad1faec0c64374e527c6e62f2e5a666f6f").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2626,7 +2870,7 @@ mod typecheck_tests { let lit = "NetXynUjJNZm7wi"; assert_eq!( &typecheck_instruction( - parse(&format!("PUSH chain_id \"{}\"", lit)).unwrap(), + &parse(&format!("PUSH chain_id \"{}\"", lit)).unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2634,7 +2878,7 @@ mod typecheck_tests { ); assert_eq!( &typecheck_instruction( - parse(&format!("PUSH chain_id 0x{}", bytes)).unwrap(), + &parse(&format!("PUSH chain_id 0x{}", bytes)).unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2642,7 +2886,7 @@ mod typecheck_tests { ); assert_eq!( typecheck_instruction( - parse("PUSH chain_id \"foobar\"").unwrap(), + &parse("PUSH chain_id \"foobar\"").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2652,7 +2896,7 @@ mod typecheck_tests { ); assert_eq!( typecheck_instruction( - parse("PUSH chain_id 0xbeef").unwrap(), + &parse("PUSH chain_id 0xbeef").unwrap(), &mut Ctx::default(), &mut tc_stk![], ), @@ -2666,7 +2910,7 @@ mod typecheck_tests { fn chain_id_instr() { assert_eq!( typecheck_instruction( - parse("CHAIN_ID").unwrap(), + &parse("CHAIN_ID").unwrap(), &mut Ctx::default(), &mut tc_stk![] ), @@ -2679,7 +2923,7 @@ mod typecheck_tests { let stk = &mut tc_stk![]; assert_eq!( super::typecheck_instruction( - parse("SELF").unwrap(), + &parse("SELF").unwrap(), &mut Ctx::default(), Some(&Type::Nat), stk @@ -2688,4 +2932,65 @@ mod typecheck_tests { ); assert_eq!(stk, &tc_stk![Type::new_contract(Type::Nat)]); } + + #[test] + fn read_top_level() { + use crate::lexer::Prim::{code, parameter, storage}; + use TcError as Err; + + let go = |s| { + parse_contract_script(s) + .unwrap() + .typecheck_script(&mut Ctx::default()) + }; + + // duplicate + assert_eq!( + go("parameter unit; parameter int; storage unit; code FAILWITH"), + Err(Err::DuplicateTopLevelElt(parameter)) + ); + assert_eq!( + go("parameter unit; storage unit; storage int; code FAILWITH"), + Err(Err::DuplicateTopLevelElt(storage)) + ); + assert_eq!( + go("code INT; parameter unit; storage unit; code FAILWITH"), + Err(Err::DuplicateTopLevelElt(code)) + ); + // missing + assert_eq!( + go("storage unit; code FAILWITH"), + Err(Err::MissingTopLevelElt(parameter)) + ); + assert_eq!( + go("parameter unit; code FAILWITH"), + Err(Err::MissingTopLevelElt(storage)) + ); + assert_eq!( + go("parameter unit; storage unit"), + Err(Err::MissingTopLevelElt(code)) + ); + } + + #[test] + fn dip_dup_arg_too_large() { + assert_eq!( + parse("DROP 1025") + .unwrap() + .typecheck(&mut Ctx::default(), None, &[]), + Err(TcError::ExpectedU10(1025)) + ); + assert_eq!( + parse("DIP 1024 {}") + .unwrap() + .typecheck(&mut Ctx::default(), None, &[]), + Err(TcError::ExpectedU10(1024)) + ); + assert_eq!( + parse("DUP 65536") + .unwrap() + .typecheck(&mut Ctx::default(), None, &[]), + Err(TcError::ExpectedU10(65536)) + ); + } } diff --git a/contrib/mir/src/tzt.rs b/contrib/mir/src/tzt.rs index 49f565b3d425..340929f0ddaa 100644 --- a/contrib/mir/src/tzt.rs +++ b/contrib/mir/src/tzt.rs @@ -74,8 +74,8 @@ pub struct TztTest<'a> { fn typecheck_stack(stk: Vec<(Micheline, Micheline)>) -> Result, TcError> { stk.into_iter() .map(|(t, v)| { - let t = t.typecheck_ty(&mut Ctx::default())?; - let tc_val = v.typecheck_value(&mut Default::default(), &t)?; + let t = parse_ty(&mut Ctx::default(), &t)?; + let tc_val = typecheck_value(&mut Default::default(), &t, &v)?; Ok((t, tc_val)) }) .collect() @@ -143,19 +143,19 @@ impl<'a> TryFrom>> for TztTest<'a> { chain_id: m_chain_id .map(|v| { Ok::<_, TcError>(irrefutable_match!( - v.typecheck_value(&mut Ctx::default(), &Type::ChainId)?; + typecheck_value(&mut Ctx::default(), &Type::ChainId, &v)?; TypedValue::ChainId )) }) .transpose()?, parameter: m_parameter - .map(|v| v.typecheck_ty(&mut Ctx::default())) + .map(|v| parse_ty(&mut Ctx::default(), &v)) .transpose()?, self_addr: m_self .map(|v| { Ok::<_, TcError>( irrefutable_match!( - v.typecheck_value(&mut Ctx::default(), &Type::Address)?; + typecheck_value(&mut Ctx::default(), &Type::Address, &v)?; TypedValue::Address ) .hash, @@ -255,7 +255,7 @@ fn execute_tzt_test_code( // This value along with the test expectation // from the test file will be used to decide if // the test was a success or a fail. - let typechecked_code = code.typecheck(ctx, Some(parameter), &mut t_stack)?; + let typechecked_code = typecheck_instruction(&code, ctx, Some(parameter), &mut t_stack)?; let mut i_stack: IStack = TopIsFirst::from(vals).0; typechecked_code.interpret(ctx, &mut i_stack)?; Ok((t_stack, i_stack)) diff --git a/contrib/mir/src/tzt/expectation.rs b/contrib/mir/src/tzt/expectation.rs index d16a3444303b..8f4d20e8485c 100644 --- a/contrib/mir/src/tzt/expectation.rs +++ b/contrib/mir/src/tzt/expectation.rs @@ -43,7 +43,7 @@ fn unify_interpreter_error( (FailedWith(value), InterpretError::FailedWith(typ, failed_typed_value)) => { // Here we typecheck the untyped value from the expectation using the // typed of the failed value we get from the interpreter. - match value.clone().typecheck_value(ctx, typ) { + match typecheck_value(ctx, typ, value) { Ok(exp_typed_val) => { // Then both `Typedvalue`s are untyped and compared to get the result. Here // untyping is done before comparing so that we are not using the Eq trait of -- GitLab From f4c4f5ea361268cc3bfa3d9bd75a9c435bd4c8ef Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Mon, 13 Nov 2023 15:07:42 +0300 Subject: [PATCH 6/9] MIR: remove now-redundant staging from Instruction --- contrib/mir/src/ast.rs | 65 ++++++------------- .../mir/src/ast/{parsed.rs => overloads.rs} | 32 +++++---- contrib/mir/src/ast/typechecked.rs | 50 -------------- contrib/mir/src/interpreter.rs | 16 ++--- contrib/mir/src/typechecker.rs | 19 +++--- 5 files changed, 53 insertions(+), 129 deletions(-) rename contrib/mir/src/ast/{parsed.rs => overloads.rs} (55%) delete mode 100644 contrib/mir/src/ast/typechecked.rs diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index ef0ef83c1ad2..6dfcf3b3ad66 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -10,8 +10,7 @@ pub mod micheline; pub mod michelson_address; pub mod michelson_list; pub mod or; -pub mod parsed; -pub mod typechecked; +pub mod overloads; pub use micheline::Micheline; use std::collections::BTreeMap; @@ -23,8 +22,6 @@ use crate::lexer::Prim; pub use michelson_address::*; pub use michelson_list::MichelsonList; pub use or::Or; -pub use parsed::{ParsedInstruction, ParsedStage}; -pub use typechecked::{overloads, TypecheckedInstruction, TypecheckedStage}; #[derive(Debug, Clone, Eq, PartialEq)] pub enum Type { @@ -222,40 +219,20 @@ impl TypedValue { } } -pub type ParsedInstructionBlock = Vec; - -macro_rules! meta_types { - ($($i:ident),* $(,)*) => { - $(type $i: std::fmt::Debug + PartialEq + Clone;)* - }; -} - -pub trait Stage { - meta_types! { - AddMeta, - PushValue, - NilType, - GetOverload, - UpdateOverload, - FailwithType, - IterOverload, - } -} - #[derive(Debug, Eq, PartialEq, Clone)] -pub enum Instruction { - Add(T::AddMeta), - Dip(Option, Vec>), +pub enum Instruction { + Add(overloads::Add), + Dip(Option, Vec), Drop(Option), Dup(Option), Gt, - If(Vec>, Vec>), - IfNone(Vec>, Vec>), + If(Vec, Vec), + IfNone(Vec, Vec), Int, - Loop(Vec>), - Push(T::PushValue), + Loop(Vec), + Push(TypedValue), Swap, - Failwith(T::FailwithType), + Failwith(Type), Unit, Car, Cdr, @@ -264,27 +241,23 @@ pub enum Instruction { ISome, Compare, Amount, - Nil(T::NilType), - Get(T::GetOverload), - Update(T::UpdateOverload), - Seq(Vec>), + Nil, + Get(overloads::Get), + Update(overloads::Update), + Seq(Vec), Unpair, Cons, - IfCons(Vec>, Vec>), - Iter(T::IterOverload, Vec>), - IfLeft(Vec>, Vec>), + IfCons(Vec, Vec), + Iter(overloads::Iter, Vec), + IfLeft(Vec, Vec), ChainId, /// `ISelf` because `Self` is a reserved keyword ISelf, } -pub type ParsedAST = Vec; - -pub type TypecheckedAST = Vec; - -#[derive(Debug, Clone, PartialEq)] -pub struct ContractScript { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContractScript { pub parameter: Type, pub storage: Type, - pub code: Instruction, + pub code: Instruction, } diff --git a/contrib/mir/src/ast/parsed.rs b/contrib/mir/src/ast/overloads.rs similarity index 55% rename from contrib/mir/src/ast/parsed.rs rename to contrib/mir/src/ast/overloads.rs index 690ce47bf308..c16091bdbbe2 100644 --- a/contrib/mir/src/ast/parsed.rs +++ b/contrib/mir/src/ast/overloads.rs @@ -5,19 +5,27 @@ /* */ /******************************************************************************/ -use super::{Instruction, Stage, Type, Value}; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Add { + IntInt, + NatNat, + IntNat, + NatInt, + MutezMutez, +} -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ParsedStage {} +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Get { + Map, +} -impl Stage for ParsedStage { - type AddMeta = (); - type PushValue = (Type, Value); - type NilType = Type; - type GetOverload = (); - type UpdateOverload = (); - type FailwithType = (); - type IterOverload = (); +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Update { + Map, } -pub type ParsedInstruction = Instruction; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Iter { + List, + Map, +} diff --git a/contrib/mir/src/ast/typechecked.rs b/contrib/mir/src/ast/typechecked.rs deleted file mode 100644 index cdfb8002f788..000000000000 --- a/contrib/mir/src/ast/typechecked.rs +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************/ -/* */ -/* SPDX-License-Identifier: MIT */ -/* Copyright (c) [2023] Serokell */ -/* */ -/******************************************************************************/ - -use super::{Instruction, Stage, Type, TypedValue}; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TypecheckedStage {} - -impl Stage for TypecheckedStage { - type AddMeta = overloads::Add; - type PushValue = TypedValue; - type NilType = (); - type GetOverload = overloads::Get; - type UpdateOverload = overloads::Update; - type FailwithType = Type; - type IterOverload = overloads::Iter; -} - -pub type TypecheckedInstruction = Instruction; - -pub mod overloads { - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - pub enum Add { - IntInt, - NatNat, - IntNat, - NatInt, - MutezMutez, - } - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - pub enum Get { - Map, - } - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - pub enum Update { - Map, - } - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - pub enum Iter { - List, - Map, - } -} diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 534913a7a604..bb4f759b8aa2 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -31,7 +31,7 @@ pub enum ContractInterpretError { InterpretError(#[from] crate::interpreter::InterpretError), } -impl ContractScript { +impl ContractScript { /// Interpret a typechecked contract script using the provided parameter and /// storage. Parameter and storage are given as `Micheline`, as this /// allows ensuring they satisfy the types expected by the script. @@ -58,7 +58,7 @@ impl ContractScript { } } -impl TypecheckedInstruction { +impl Instruction { /// Interpret the instruction with the given `Ctx` and input stack. Note the /// interpreter assumes the instruction can execute on the provided stack, /// otherwise this function will panic. @@ -72,7 +72,7 @@ impl TypecheckedInstruction { } fn interpret( - ast: &TypecheckedAST, + ast: &Vec, ctx: &mut Ctx, stack: &mut IStack, ) -> Result<(), InterpretError> { @@ -90,11 +90,7 @@ fn unreachable_state() -> ! { panic!("Unreachable state reached during interpreting, possibly broken typechecking!") } -fn interpret_one( - i: &TypecheckedInstruction, - ctx: &mut Ctx, - stack: &mut IStack, -) -> Result<(), InterpretError> { +fn interpret_one(i: &Instruction, ctx: &mut Ctx, stack: &mut IStack) -> Result<(), InterpretError> { use Instruction as I; use TypedValue as V; @@ -323,7 +319,7 @@ fn interpret_one( ctx.gas.consume(interpret_cost::AMOUNT)?; stack.push(V::Mutez(ctx.amount)); } - I::Nil(..) => { + I::Nil => { ctx.gas.consume(interpret_cost::NIL)?; stack.push(V::List(MichelsonList::new())); } @@ -1040,7 +1036,7 @@ mod interpreter_tests { fn nil() { let mut stack = stk![]; let mut ctx = Ctx::default(); - assert_eq!(interpret(&vec![Nil(())], &mut ctx, &mut stack), Ok(())); + assert_eq!(interpret(&vec![Nil], &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![TypedValue::List(vec![].into())]); assert_eq!( ctx.gas.milligas(), diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index bf04bdabbbed..3313084a2b33 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -150,7 +150,7 @@ impl Micheline<'_> { ctx: &mut Ctx, self_type: Option<&Micheline>, stack: &[Micheline], - ) -> Result { + ) -> Result { let self_type = self_type .map(|ty| { let ty = parse_ty(ctx, ty)?; @@ -174,10 +174,7 @@ impl Micheline<'_> { /// Typecheck the contract script. Validates the script's types, then /// typechecks the code and checks the result stack is as expected. Returns /// typechecked script. - pub fn typecheck_script( - &self, - ctx: &mut Ctx, - ) -> Result, TcError> { + pub fn typecheck_script(&self, ctx: &mut Ctx) -> Result { let seq = match self { // top-level allows one level of nesting Micheline::Seq([Micheline::Seq(seq)]) => seq, @@ -345,7 +342,7 @@ fn typecheck( ctx: &mut Ctx, self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, -) -> Result { +) -> Result, TcError> { ast.iter() .map(|i| typecheck_instruction(i, ctx, self_type, opt_stack)) .collect() @@ -373,7 +370,7 @@ pub(crate) fn typecheck_instruction( ctx: &mut Ctx, self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, -) -> Result { +) -> Result { use Instruction as I; use NoMatchingOverloadReason as NMOR; use Type as T; @@ -809,7 +806,7 @@ pub(crate) fn typecheck_instruction( (App(NIL, [ty], _), ..) => { let ty = parse_ty(ctx, ty)?; stack.push(T::new_list(ty)); - I::Nil(()) + I::Nil } (App(NIL, ..), _) => unexpected_micheline!(), @@ -1081,7 +1078,7 @@ mod typecheck_tests { i: &Micheline, ctx: &mut Ctx, opt_stack: &mut FailingTypeStack, - ) -> Result { + ) -> Result { super::typecheck_instruction(i, ctx, None, opt_stack) } @@ -1887,7 +1884,7 @@ mod typecheck_tests { let mut stack = tc_stk![]; assert_eq!( typecheck_instruction(&parse("NIL int").unwrap(), &mut Ctx::default(), &mut stack), - Ok(Nil(())) + Ok(Nil) ); assert_eq!(stack, tc_stk![Type::new_list(Type::Int)]); } @@ -1938,7 +1935,7 @@ mod typecheck_tests { &mut Ctx::default(), &mut stack ), - Ok(Nil(())) + Ok(Nil) ); assert_eq!(stack, tc_stk![Type::new_list(Type::Operation)]); } -- GitLab From 21e6363a9a3fc778a676250200fbcfeaad4e03a7 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Mon, 13 Nov 2023 15:08:34 +0300 Subject: [PATCH 7/9] MIR: remove now-redundant Value type --- contrib/mir/src/ast.rs | 70 ------------------------------------------ 1 file changed, 70 deletions(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 6dfcf3b3ad66..c5b9cd1a70f1 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -79,76 +79,6 @@ impl Type { } } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Value { - Number(i128), - Boolean(bool), - String(String), - Unit, - Pair(Box<(Value, Value)>), - Option(Option>), - Seq(Vec), - Elt(Box<(Value, Value)>), - Or(Box>), - Bytes(Vec), -} - -impl Value { - pub fn new_pair(l: Self, r: Self) -> Self { - Self::Pair(Box::new((l, r))) - } - - pub fn new_option(x: Option) -> Self { - Self::Option(x.map(Box::new)) - } - - pub fn new_elt(k: Self, v: Self) -> Self { - Self::Elt(Box::new((k, v))) - } - - pub fn new_or(v: Or) -> Self { - Self::Or(Box::new(v)) - } -} - -macro_rules! valuefrom { - ($( <$($gs:ident),*> $ty:ty, $cons:expr );* $(;)*) => { - $( - impl<$($gs),*> From<$ty> for Value where $($gs: Into),* { - fn from(x: $ty) -> Self { - $cons(x) - } - } - )* - }; -} - -/// Simple helper for constructing Elt values: -/// -/// ```text -/// let val: Value = Elt("foo", 3).into() -/// ``` -pub struct Elt(pub K, pub V); - -valuefrom! { - <> i128, Value::Number; - <> bool, Value::Boolean; - <> String, Value::String; - <> (), |_| Value::Unit; - <> Vec, Value::Bytes; - (L, R), |(l, r): (L, R)| Value::new_pair(l.into(), r.into()); - Elt, |Elt(l, r): Elt| Value::new_elt(l.into(), r.into()); - Option, |x: Option| Value::new_option(x.map(Into::into)); - Vec, |x: Vec| Value::Seq(x.into_iter().map(Into::into).collect()); - Or, |x: Or| Value::new_or(x.bimap(Into::into, Into::into)); -} - -impl From<&str> for Value { - fn from(s: &str) -> Self { - Value::String(s.to_owned()) - } -} - #[derive(Debug, Clone, Eq, PartialEq)] pub enum TypedValue { Int(i128), -- GitLab From aefb84342196d28164e6dfc34012a025c23b3ce7 Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Wed, 15 Nov 2023 21:09:28 +0300 Subject: [PATCH 8/9] MIR: rename Micheline::typecheck to typecheck_instruciton --- contrib/mir/src/lib.rs | 22 ++++++++++------------ contrib/mir/src/typechecker.rs | 12 ++++++------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index edea29d58152..92f11aecdba2 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -40,7 +40,7 @@ mod tests { fn interpret_test_expect_success() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &[app!(nat)]) + .typecheck_instruction(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(10)]; assert!(ast.interpret(&mut Ctx::default(), &mut istack).is_ok()); @@ -51,7 +51,7 @@ mod tests { fn interpret_mutez_push_add() { let ast = parse("{ PUSH mutez 100; PUSH mutez 500; ADD }").unwrap(); let mut ctx = Ctx::default(); - let ast = ast.typecheck(&mut ctx, None, &[]).unwrap(); + let ast = ast.typecheck_instruction(&mut ctx, None, &[]).unwrap(); let mut istack = stk![]; assert!(ast.interpret(&mut ctx, &mut istack).is_ok()); assert_eq!(istack, stk![TypedValue::Mutez(600)]); @@ -61,7 +61,7 @@ mod tests { fn interpret_test_gas_consumption() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &[app!(nat)]) + .typecheck_instruction(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx::default(); @@ -75,7 +75,7 @@ mod tests { fn interpret_test_gas_out_of_gas() { let ast = parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), None, &[app!(nat)]) + .typecheck_instruction(&mut Ctx::default(), None, &[app!(nat)]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx { @@ -104,7 +104,7 @@ mod tests { let mut ctx = Ctx::default(); let start_milligas = ctx.gas.milligas(); report_gas(&mut ctx, |ctx| { - assert!(ast.typecheck(ctx, None, &[app!(nat)]).is_ok()); + assert!(ast.typecheck_instruction(ctx, None, &[app!(nat)]).is_ok()); }); assert_eq!(start_milligas - ctx.gas.milligas(), 12680); } @@ -117,7 +117,7 @@ mod tests { ..Ctx::default() }; assert_eq!( - ast.typecheck(&mut ctx, None, &[app!(nat)]), + ast.typecheck_instruction(&mut ctx, None, &[app!(nat)]), Err(typechecker::TcError::OutOfGas(crate::gas::OutOfGas)) ); } @@ -127,7 +127,7 @@ mod tests { use typechecker::{NoMatchingOverloadReason, TcError}; let ast = parse(FIBONACCI_ILLTYPED_SRC).unwrap(); assert_eq!( - ast.typecheck(&mut Ctx::default(), None, &[app!(nat)]), + ast.typecheck_instruction(&mut Ctx::default(), None, &[app!(nat)]), Err(TcError::NoMatchingOverload { instr: crate::lexer::Prim::DUP, stack: stk![Type::Int, Type::Int, Type::Int], @@ -180,11 +180,9 @@ mod tests { fn parser_test_expect_fail() { use crate::ast::micheline::test_helpers::app; assert_eq!( - parse(FIBONACCI_MALFORMED_SRC).unwrap().typecheck( - &mut Ctx::default(), - None, - &[app!(nat)] - ), + parse(FIBONACCI_MALFORMED_SRC) + .unwrap() + .typecheck_instruction(&mut Ctx::default(), None, &[app!(nat)]), Err(typechecker::TcError::UnexpectedMicheline(format!( "{:?}", app!(DUP[4, app!(GT)]) diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 3313084a2b33..79f8417b69c5 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -145,7 +145,7 @@ impl Micheline<'_> { /// /// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like /// in lambdas). - pub fn typecheck( + pub fn typecheck_instruction( &self, ctx: &mut Ctx, self_type: Option<&Micheline>, @@ -2079,7 +2079,7 @@ mod typecheck_tests { #[test] fn get_map_incomparable() { assert_eq!( - parse("GET").unwrap().typecheck( + parse("GET").unwrap().typecheck_instruction( &mut Ctx::default(), None, &[ @@ -2133,7 +2133,7 @@ mod typecheck_tests { #[test] fn update_map_incomparable() { assert_eq!( - parse("UPDATE").unwrap().typecheck( + parse("UPDATE").unwrap().typecheck_instruction( &mut Ctx::default(), None, &[ @@ -2974,19 +2974,19 @@ mod typecheck_tests { assert_eq!( parse("DROP 1025") .unwrap() - .typecheck(&mut Ctx::default(), None, &[]), + .typecheck_instruction(&mut Ctx::default(), None, &[]), Err(TcError::ExpectedU10(1025)) ); assert_eq!( parse("DIP 1024 {}") .unwrap() - .typecheck(&mut Ctx::default(), None, &[]), + .typecheck_instruction(&mut Ctx::default(), None, &[]), Err(TcError::ExpectedU10(1024)) ); assert_eq!( parse("DUP 65536") .unwrap() - .typecheck(&mut Ctx::default(), None, &[]), + .typecheck_instruction(&mut Ctx::default(), None, &[]), Err(TcError::ExpectedU10(65536)) ); } -- GitLab From f533eac1f6b312744e65ee0f581ff5c57bdc062b Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Tue, 21 Nov 2023 13:13:24 +0300 Subject: [PATCH 9/9] MIR: reorder typecheck_value arguments for consistency --- contrib/mir/src/interpreter.rs | 2 +- contrib/mir/src/typechecker.rs | 28 ++++++++++++++-------------- contrib/mir/src/tzt.rs | 6 +++--- contrib/mir/src/tzt/expectation.rs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index bb4f759b8aa2..c474a1bc500e 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -44,7 +44,7 @@ impl ContractScript { let in_ty = Type::new_pair(self.parameter.clone(), self.storage.clone()); let in_val = &[parameter, storage]; let in_val = Micheline::App(Prim::Pair, in_val, vec![]); - let tc_val = typecheck_value(ctx, &in_ty, &in_val)?; + let tc_val = typecheck_value(&in_val, ctx, &in_ty)?; let mut stack = stk![tc_val]; self.code.interpret(ctx, &mut stack)?; use TypedValue as V; diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 79f8417b69c5..dd3216388080 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -135,7 +135,7 @@ impl Micheline<'_> { value_type: &Micheline, ) -> Result { let ty = parse_ty(ctx, value_type)?; - typecheck_value(ctx, &ty, self) + typecheck_value(self, ctx, &ty) } /// Typechecks `Micheline` as an instruction (or a sequence of instruction), @@ -702,7 +702,7 @@ pub(crate) fn typecheck_instruction( (App(PUSH, [t, v], _), ..) => { let t = parse_ty(ctx, t)?; t.ensure_prop(&mut ctx.gas, TypeProperty::Pushable)?; - let v = typecheck_value(ctx, &t, v)?; + let v = typecheck_value(v, ctx, &t)?; stack.push(t); I::Push(v) } @@ -863,9 +863,9 @@ pub(crate) fn typecheck_instruction( /// Typecheck a value. Assumes passed the type is valid, i.e. doesn't contain /// illegal types like `set operation` or `contract operation`. pub(crate) fn typecheck_value( + v: &Micheline, ctx: &mut Ctx, t: &Type, - v: &Micheline, ) -> Result { use Micheline as V; use Type as T; @@ -881,30 +881,30 @@ pub(crate) fn typecheck_value( (T::Unit, V::App(Prim::Unit, [], _)) => TV::Unit, (T::Pair(pt), V::App(Prim::Pair, [vl, rest @ ..], _)) if !rest.is_empty() => { let (tl, tr) = pt.as_ref(); - let l = typecheck_value(ctx, tl, vl)?; + let l = typecheck_value(vl, ctx, tl)?; let r = match rest { - [vr] => typecheck_value(ctx, tr, vr)?, - vrs => typecheck_value(ctx, tr, &V::App(Prim::Pair, vrs, vec![]))?, + [vr] => typecheck_value(vr, ctx, tr)?, + vrs => typecheck_value(&V::App(Prim::Pair, vrs, vec![]), ctx, tr)?, }; TV::new_pair(l, r) } (T::Or(ot), V::App(prim @ (Prim::Left | Prim::Right), [val], _)) => { let (tl, tr) = ot.as_ref(); let typed_val = match prim { - Prim::Left => crate::ast::Or::Left(typecheck_value(ctx, tl, val)?), - Prim::Right => crate::ast::Or::Right(typecheck_value(ctx, tr, val)?), + Prim::Left => crate::ast::Or::Left(typecheck_value(val, ctx, tl)?), + Prim::Right => crate::ast::Or::Right(typecheck_value(val, ctx, tr)?), _ => unreachable!(), }; TV::new_or(typed_val) } (T::Option(ty), V::App(Prim::Some, [v], _)) => { - let v = typecheck_value(ctx, ty, v)?; + let v = typecheck_value(v, ctx, ty)?; TV::new_option(Some(v)) } (T::Option(_), V::App(Prim::None, [], _)) => TV::new_option(None), (T::List(ty), V::Seq(vs)) => TV::List( vs.iter() - .map(|v| typecheck_value(ctx, ty, v)) + .map(|v| typecheck_value(v, ctx, ty)) .collect::>()?, ), (T::Map(m), V::Seq(vs)) => { @@ -912,8 +912,8 @@ pub(crate) fn typecheck_value( let tc_elt = |v: &Micheline| -> Result<(TypedValue, TypedValue), TcError> { match v { Micheline::App(Prim::Elt, [k, v], _) => { - let k = typecheck_value(ctx, tk, k)?; - let v = typecheck_value(ctx, tv, v)?; + let k = typecheck_value(k, ctx, tk)?; + let v = typecheck_value(v, ctx, tv)?; Ok((k, v)) } _ => Err(TcError::InvalidEltForMap(format!("{v:?}"), t.clone())), @@ -955,7 +955,7 @@ pub(crate) fn typecheck_value( TV::Address(Address::from_bytes(bs)?) } (T::Contract(ty), addr) => { - let t_addr = irrefutable_match!(typecheck_value(ctx, &T::Address, addr)?; TV::Address); + let t_addr = irrefutable_match!(typecheck_value(addr, ctx, &T::Address)?; TV::Address); match t_addr.hash { AddressHash::Tz1(_) | AddressHash::Tz2(_) @@ -1421,7 +1421,7 @@ mod typecheck_tests { #[test] fn string_values() { assert_eq!( - typecheck_value(&mut Ctx::default(), &Type::String, &"foo".into()), + typecheck_value(&"foo".into(), &mut Ctx::default(), &Type::String), Ok(TypedValue::String("foo".to_owned())) ) } diff --git a/contrib/mir/src/tzt.rs b/contrib/mir/src/tzt.rs index 340929f0ddaa..1846e4144614 100644 --- a/contrib/mir/src/tzt.rs +++ b/contrib/mir/src/tzt.rs @@ -75,7 +75,7 @@ fn typecheck_stack(stk: Vec<(Micheline, Micheline)>) -> Result TryFrom>> for TztTest<'a> { chain_id: m_chain_id .map(|v| { Ok::<_, TcError>(irrefutable_match!( - typecheck_value(&mut Ctx::default(), &Type::ChainId, &v)?; + typecheck_value(&v, &mut Ctx::default(), &Type::ChainId)?; TypedValue::ChainId )) }) @@ -155,7 +155,7 @@ impl<'a> TryFrom>> for TztTest<'a> { .map(|v| { Ok::<_, TcError>( irrefutable_match!( - typecheck_value(&mut Ctx::default(), &Type::Address, &v)?; + typecheck_value(&v, &mut Ctx::default(), &Type::Address)?; TypedValue::Address ) .hash, diff --git a/contrib/mir/src/tzt/expectation.rs b/contrib/mir/src/tzt/expectation.rs index 8f4d20e8485c..d62cee4f3108 100644 --- a/contrib/mir/src/tzt/expectation.rs +++ b/contrib/mir/src/tzt/expectation.rs @@ -43,7 +43,7 @@ fn unify_interpreter_error( (FailedWith(value), InterpretError::FailedWith(typ, failed_typed_value)) => { // Here we typecheck the untyped value from the expectation using the // typed of the failed value we get from the interpreter. - match typecheck_value(ctx, typ, value) { + match typecheck_value(value, ctx, typ) { Ok(exp_typed_val) => { // Then both `Typedvalue`s are untyped and compared to get the result. Here // untyping is done before comparing so that we are not using the Eq trait of -- GitLab