diff --git a/compiler/ast/src/functions/mod.rs b/compiler/ast/src/functions/mod.rs index f0b9f9622d..75583561bb 100644 --- a/compiler/ast/src/functions/mod.rs +++ b/compiler/ast/src/functions/mod.rs @@ -104,6 +104,7 @@ impl Function { Variant::Inline => write!(f, "inline ")?, Variant::Function | Variant::AsyncFunction => write!(f, "function ")?, Variant::Transition | Variant::AsyncTransition => write!(f, "transition ")?, + Variant::Interpret => write!(f, "interpret")?, } write!(f, "{}", self.identifier)?; diff --git a/compiler/ast/src/functions/variant.rs b/compiler/ast/src/functions/variant.rs index eb2851456f..7cc5876a18 100644 --- a/compiler/ast/src/functions/variant.rs +++ b/compiler/ast/src/functions/variant.rs @@ -29,6 +29,7 @@ pub enum Variant { Transition, AsyncTransition, AsyncFunction, + Interpret, } impl Variant { diff --git a/compiler/ast/src/stub/function_stub.rs b/compiler/ast/src/stub/function_stub.rs index c62524f179..0d1b58bd2c 100644 --- a/compiler/ast/src/stub/function_stub.rs +++ b/compiler/ast/src/stub/function_stub.rs @@ -114,6 +114,7 @@ impl FunctionStub { Variant::Inline => write!(f, "inline ")?, Variant::Function | Variant::AsyncFunction => write!(f, "function ")?, Variant::Transition | Variant::AsyncTransition => write!(f, "transition ")?, + Variant::Interpret => write!(f, "interpret")?, } write!(f, "{}", self.identifier)?; diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 006762dec8..1cbe9d8b41 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -162,6 +162,7 @@ impl<'a, N: Network> Compiler<'a, N> { &self.type_table, self.compiler_options.build.conditional_block_max_depth, self.compiler_options.build.disable_conditional_branch_type_checking, + false, // is_test ))?; if self.compiler_options.output.type_checked_symbol_table { self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index 1390c114d1..2c2ff395b0 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -134,7 +134,7 @@ impl ParserContext<'_, N> { let (id, mapping) = self.parse_mapping()?; mappings.push((id, mapping)); } - Token::At | Token::Async | Token::Function | Token::Transition | Token::Inline => { + Token::At | Token::Async | Token::Function | Token::Transition | Token::Inline | Token::Interpret => { let (id, function) = self.parse_function()?; // Partition into transitions and functions so that we don't have to sort later. @@ -334,8 +334,9 @@ impl ParserContext<'_, N> { // Parse a potential async signifier. let (is_async, start_async) = if self.token.token == Token::Async { (true, self.expect(&Token::Async)?) } else { (false, Span::dummy()) }; - // Parse ` IDENT`, where `` is `function`, `transition`, or `inline`. + // Parse ` IDENT`, where `` is `function`, `transition`, `inline`, or `interpret`. let (variant, start) = match self.token.token.clone() { + Token::Interpret => (Variant::Interpret, self.expect(&Token::Interpret)?), Token::Inline => (Variant::Inline, self.expect(&Token::Inline)?), Token::Function => { (if is_async { Variant::AsyncFunction } else { Variant::Function }, self.expect(&Token::Function)?) @@ -344,7 +345,7 @@ impl ParserContext<'_, N> { if is_async { Variant::AsyncTransition } else { Variant::Transition }, self.expect(&Token::Transition)?, ), - _ => self.unexpected("'function', 'transition', or 'inline'")?, + _ => self.unexpected("'function', 'transition', 'inline', or 'interpret'")?, }; let name = self.expect_identifier()?; diff --git a/compiler/parser/src/tokenizer/lexer.rs b/compiler/parser/src/tokenizer/lexer.rs index 51e31412be..44dd8f5f0a 100644 --- a/compiler/parser/src/tokenizer/lexer.rs +++ b/compiler/parser/src/tokenizer/lexer.rs @@ -431,6 +431,7 @@ impl Token { "import" => Token::Import, "in" => Token::In, "inline" => Token::Inline, + "interpret" => Token::Interpret, "let" => Token::Let, "leo" => Token::Leo, "mapping" => Token::Mapping, diff --git a/compiler/parser/src/tokenizer/token.rs b/compiler/parser/src/tokenizer/token.rs index d6499e8962..aca7c187de 100644 --- a/compiler/parser/src/tokenizer/token.rs +++ b/compiler/parser/src/tokenizer/token.rs @@ -156,6 +156,7 @@ pub enum Token { Import, In, Inline, + Interpret, Let, Mapping, Network, @@ -205,6 +206,7 @@ pub const KEYWORD_TOKENS: &[Token] = &[ Token::Import, Token::In, Token::Inline, + Token::Interpret, Token::Let, Token::Mapping, Token::Network, @@ -262,6 +264,7 @@ impl Token { Token::Import => sym::import, Token::In => sym::In, Token::Inline => sym::inline, + Token::Interpret => sym::interpret, Token::Let => sym::Let, Token::Leo => sym::leo, Token::Mapping => sym::mapping, @@ -396,6 +399,7 @@ impl fmt::Display for Token { Import => write!(f, "import"), In => write!(f, "in"), Inline => write!(f, "inline"), + Interpret => write!(f, "interpret"), Let => write!(f, "let"), Mapping => write!(f, "mapping"), Network => write!(f, "network"), diff --git a/compiler/passes/src/code_generation/visit_program.rs b/compiler/passes/src/code_generation/visit_program.rs index 6d08b1e270..0fd39357a0 100644 --- a/compiler/passes/src/code_generation/visit_program.rs +++ b/compiler/passes/src/code_generation/visit_program.rs @@ -186,7 +186,7 @@ impl<'a> CodeGenerator<'a> { Variant::Transition | Variant::AsyncTransition => format!("\nfunction {}:\n", function.identifier), Variant::Function => format!("\nclosure {}:\n", function.identifier), Variant::AsyncFunction => format!("\nfinalize {}:\n", self.finalize_caller.unwrap()), - Variant::Inline => return String::new(), + Variant::Inline | Variant::Interpret => return String::new(), }; // Construct and append the input declarations of the function. diff --git a/compiler/passes/src/function_inlining/inline_expression.rs b/compiler/passes/src/function_inlining/inline_expression.rs index 4d627af9f0..89357caa2d 100644 --- a/compiler/passes/src/function_inlining/inline_expression.rs +++ b/compiler/passes/src/function_inlining/inline_expression.rs @@ -102,9 +102,11 @@ impl ExpressionReconstructor for FunctionInliner<'_> { (result, inlined_statements) } - Variant::Function | Variant::AsyncFunction | Variant::Transition | Variant::AsyncTransition => { - (Expression::Call(input), Default::default()) - } + Variant::Function + | Variant::AsyncFunction + | Variant::Transition + | Variant::AsyncTransition + | Variant::Interpret => (Expression::Call(input), Default::default()), } } } diff --git a/compiler/passes/src/type_checking/check_expressions.rs b/compiler/passes/src/type_checking/check_expressions.rs index 60bb0511f3..097f7130ce 100644 --- a/compiler/passes/src/type_checking/check_expressions.rs +++ b/compiler/passes/src/type_checking/check_expressions.rs @@ -96,7 +96,7 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { // Check core struct name and function. if let Some(core_instruction) = self.get_core_function_call(&access.variant, &access.name) { // Check that operation is not restricted to finalize blocks. - if self.scope_state.variant != Some(Variant::AsyncFunction) + if !matches!(self.scope_state.variant, Some(Variant::AsyncFunction) | Some(Variant::Interpret)) && core_instruction.is_finalize_command() { self.emit_err(TypeCheckerError::operation_must_be_in_finalize_block(input.span())); diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 342c4a91e4..32cbac3674 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -229,10 +229,18 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { fn visit_function(&mut self, function: &'a Function) { // Check that the function's annotations are valid. - // Note that Leo does not natively support any specific annotations. + let valid_annotations = [sym::should_fail, sym::native_test, sym::interpreted_test]; for annotation in function.annotations.iter() { - // TODO: Change to compiler warning. - self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span)) + // All Leo annotations currently apply only to test code. + if !self.is_test || !valid_annotations.contains(&annotation.identifier.name) { + // TODO: Change to compiler warning. + self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span)); + } + } + + // `interpret` can only be used for tests. + if !self.is_test && function.variant == Variant::Interpret { + self.emit_err(TypeCheckerError::interpret_outside_test(function.span)); } // Set type checker variables for function variant details. diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index dfcd0afc6d..21b6f7ac79 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -53,6 +53,8 @@ pub struct TypeChecker<'a, N: Network> { pub(crate) async_function_input_types: IndexMap>, /// The set of used composites. pub(crate) used_structs: IndexSet, + /// Are we compiling tests? + pub(crate) is_test: bool, // Allows the type checker to be generic over the network. phantom: PhantomData, } @@ -109,6 +111,7 @@ impl<'a, N: Network> TypeChecker<'a, N> { handler: &'a Handler, max_depth: usize, disabled: bool, + is_test: bool, ) -> Self { let struct_names = symbol_table.structs.keys().map(|loc| loc.name).collect(); let function_names = symbol_table.functions.keys().map(|loc| loc.name).collect(); @@ -124,6 +127,7 @@ impl<'a, N: Network> TypeChecker<'a, N> { await_checker: AwaitChecker::new(max_depth, !disabled), async_function_input_types: IndexMap::new(), used_structs: IndexSet::new(), + is_test, phantom: Default::default(), } } @@ -1375,7 +1379,9 @@ impl<'a, N: Network> TypeChecker<'a, N> { // Check that the function context matches. if self.scope_state.variant == Some(Variant::AsyncFunction) && !finalize_op { self.handler.emit_err(TypeCheckerError::invalid_operation_inside_finalize(name, span)) - } else if self.scope_state.variant != Some(Variant::AsyncFunction) && finalize_op { + } else if finalize_op + && !matches!(self.scope_state.variant, Some(Variant::AsyncFunction) | Some(Variant::Interpret)) + { self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(name, span)) } } diff --git a/compiler/passes/src/type_checking/mod.rs b/compiler/passes/src/type_checking/mod.rs index d06179100d..6fde23185b 100644 --- a/compiler/passes/src/type_checking/mod.rs +++ b/compiler/passes/src/type_checking/mod.rs @@ -36,11 +36,11 @@ use leo_errors::{Result, emitter::Handler}; use snarkvm::prelude::Network; impl<'a, N: Network> Pass for TypeChecker<'a, N> { - type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, usize, bool); + type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, usize, bool, bool); type Output = Result<(SymbolTable, StructGraph, CallGraph)>; - fn do_pass((ast, handler, st, tt, max_depth, await_checking): Self::Input) -> Self::Output { - let mut visitor = TypeChecker::::new(st, tt, handler, max_depth, await_checking); + fn do_pass((ast, handler, st, tt, max_depth, await_checking, is_test): Self::Input) -> Self::Output { + let mut visitor = TypeChecker::::new(st, tt, handler, max_depth, await_checking, is_test); visitor.visit_program(ast.as_repr()); handler.last_err().map_err(|e| *e)?; diff --git a/compiler/span/src/symbol.rs b/compiler/span/src/symbol.rs index 48ae35d370..b1c845aa34 100644 --- a/compiler/span/src/symbol.rs +++ b/compiler/span/src/symbol.rs @@ -231,6 +231,11 @@ symbols! { False: "false", True: "true", + // annotations + should_fail, + native_test, + interpreted_test, + // general keywords As: "as", assert, @@ -250,6 +255,7 @@ symbols! { increment, inline, input, + interpret, Let: "let", leo, main, diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index f7248b7399..14989fe86e 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -894,4 +894,11 @@ create_messages!( msg: format!("Cannot define a function with no parameters."), help: None, } + + @formatted + interpret_outside_test { + args: (), + msg: "Cannot define an `interpret` function outside of tests.".to_string(), + help: None, + } );