diff --git a/Cargo.lock b/Cargo.lock index a0a70338..822179e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,15 +29,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "ahash" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" -dependencies = [ - "const-random", -] - [[package]] name = "ahash" version = "0.7.6" @@ -390,11 +381,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chumsky" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" +checksum = "c4d619fba796986dd538d82660b76e0b9756c6e19b2e4d4559ba5a57f9f00810" dependencies = [ - "ahash 0.3.8", + "hashbrown", + "stacker", ] [[package]] @@ -451,28 +443,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" -[[package]] -name = "const-random" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" -dependencies = [ - "getrandom", - "once_cell", - "proc-macro-hack", - "tiny-keccak", -] - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -556,12 +526,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -955,7 +919,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -1800,12 +1764,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.49" @@ -1835,6 +1793,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "pulldown-cmark" version = "0.8.0" @@ -2275,6 +2242,19 @@ dependencies = [ "der", ] +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -2461,15 +2441,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinyvec" version = "1.6.0" diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index b68289ad..8147ebdd 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -11,7 +11,7 @@ authors = ["Lucas Rosa ", "Kasey White "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chumsky = "0.8.0" +chumsky = "0.9.0" hex = "0.4.3" indexmap = "1.9.1" indoc = "1.0.7" diff --git a/crates/aiken-lang/src/air.rs b/crates/aiken-lang/src/air.rs index 7460abff..1bd2add4 100644 --- a/crates/aiken-lang/src/air.rs +++ b/crates/aiken-lang/src/air.rs @@ -235,21 +235,13 @@ pub enum Air { }, // Misc. - Todo { - scope: Vec, - label: Option, - tipo: Arc, - }, - ErrorTerm { scope: Vec, tipo: Arc, - label: Option, }, Trace { scope: Vec, - text: Option, tipo: Arc, }, } @@ -292,7 +284,6 @@ impl Air { | Air::ListExpose { scope, .. } | Air::TupleAccessor { scope, .. } | Air::TupleIndex { scope, .. } - | Air::Todo { scope, .. } | Air::ErrorTerm { scope, .. } | Air::Trace { scope, .. } => scope.clone(), } @@ -375,7 +366,6 @@ impl Air { | Air::ListExpose { tipo, .. } | Air::TupleAccessor { tipo, .. } | Air::TupleIndex { tipo, .. } - | Air::Todo { tipo, .. } | Air::ErrorTerm { tipo, .. } | Air::Trace { tipo, .. } => Some(tipo.clone()), diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index b137c7f2..2f28b034 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -995,9 +995,10 @@ pub struct RecordUpdateSpread { } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum TodoKind { - Keyword, - EmptyFunction, +pub enum TraceKind { + Trace, + Todo, + Error, } #[derive(Copy, Clone, PartialEq, Eq)] diff --git a/crates/aiken-lang/src/builder.rs b/crates/aiken-lang/src/builder.rs index cf9ec97a..063c623f 100644 --- a/crates/aiken-lang/src/builder.rs +++ b/crates/aiken-lang/src/builder.rs @@ -433,11 +433,22 @@ pub fn rearrange_clauses( sorted_clauses[sorted_clauses.len() - 1].clone().then } Pattern::Discard { .. } => sorted_clauses[sorted_clauses.len() - 1].clone().then, - _ => TypedExpr::ErrorTerm { - location: Span::empty(), - tipo: sorted_clauses[sorted_clauses.len() - 1].then.tipo(), - label: Some("Clause not filled".to_string()), - }, + _ => { + let tipo = sorted_clauses[sorted_clauses.len() - 1].then.tipo(); + TypedExpr::Trace { + location: Span::empty(), + tipo: tipo.clone(), + text: Box::new(TypedExpr::String { + location: Span::empty(), + tipo: crate::builtins::string(), + value: "Clause not filled".to_string(), + }), + then: Box::new(TypedExpr::ErrorTerm { + location: Span::empty(), + tipo, + }), + } + } }; for (index, clause) in sorted_clauses.iter().enumerate() { @@ -1667,30 +1678,21 @@ pub fn monomorphize( needs_variant = true; } } - Air::Todo { scope, label, tipo } => { + Air::ErrorTerm { scope, tipo } => { if tipo.is_generic() { let mut tipo = tipo.clone(); find_generics_to_replace(&mut tipo, &generic_types); - new_air[index] = Air::Todo { scope, tipo, label }; + new_air[index] = Air::ErrorTerm { scope, tipo }; needs_variant = true; } } - Air::ErrorTerm { scope, label, tipo } => { + Air::Trace { scope, tipo } => { if tipo.is_generic() { let mut tipo = tipo.clone(); find_generics_to_replace(&mut tipo, &generic_types); - new_air[index] = Air::ErrorTerm { scope, tipo, label }; - needs_variant = true; - } - } - Air::Trace { scope, text, tipo } => { - if tipo.is_generic() { - let mut tipo = tipo.clone(); - find_generics_to_replace(&mut tipo, &generic_types); - - new_air[index] = Air::Trace { scope, tipo, text }; + new_air[index] = Air::Trace { scope, tipo }; needs_variant = true; } } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 3feab45c..b5dab82f 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -5,7 +5,7 @@ use vec1::Vec1; use crate::{ ast::{ Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, DefinitionLocation, IfBranch, - Pattern, RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UnOp, + Pattern, RecordUpdateSpread, Span, TraceKind, TypedRecordUpdateArg, UnOp, UntypedRecordUpdateArg, }, builtins::void, @@ -96,7 +96,7 @@ pub enum TypedExpr { location: Span, tipo: Arc, then: Box, - text: Option, + text: Box, }, When { @@ -143,16 +143,9 @@ pub enum TypedExpr { tuple: Box, }, - Todo { - location: Span, - label: Option, - tipo: Arc, - }, - ErrorTerm { location: Span, tipo: Arc, - label: Option, }, RecordUpdate { @@ -177,7 +170,6 @@ impl TypedExpr { Self::Trace { then, .. } => then.tipo(), Self::Fn { tipo, .. } | Self::Int { tipo, .. } - | Self::Todo { tipo, .. } | Self::ErrorTerm { tipo, .. } | Self::When { tipo, .. } | Self::List { tipo, .. } @@ -223,7 +215,6 @@ impl TypedExpr { | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::When { .. } - | TypedExpr::Todo { .. } | TypedExpr::ErrorTerm { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } @@ -262,7 +253,6 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Var { location, .. } | Self::Trace { location, .. } - | Self::Todo { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } @@ -298,7 +288,6 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Trace { location, .. } | Self::Var { location, .. } - | Self::Todo { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } @@ -387,9 +376,10 @@ pub enum UntypedExpr { }, Trace { + kind: TraceKind, location: Span, then: Box, - text: Option, + text: Box, }, When { @@ -421,15 +411,8 @@ pub enum UntypedExpr { tuple: Box, }, - Todo { - kind: TodoKind, - location: Span, - label: Option, - }, - ErrorTerm { location: Span, - label: Option, }, RecordUpdate { @@ -446,7 +429,35 @@ pub enum UntypedExpr { }, } +pub const DEFAULT_TODO_STR: &str = "aiken::todo"; + +pub const DEFAULT_ERROR_STR: &str = "aiken::error"; + impl UntypedExpr { + pub fn todo(location: Span, reason: Option) -> Self { + UntypedExpr::Trace { + location, + kind: TraceKind::Todo, + then: Box::new(UntypedExpr::ErrorTerm { location }), + text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { + location, + value: DEFAULT_TODO_STR.to_string(), + })), + } + } + + pub fn error(location: Span, reason: Option) -> Self { + UntypedExpr::Trace { + location, + kind: TraceKind::Error, + then: Box::new(UntypedExpr::ErrorTerm { location }), + text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { + location, + value: DEFAULT_ERROR_STR.to_string(), + })), + } + } + pub fn append_in_sequence(self, next: Self) -> Self { let location = Span { start: self.location().start, @@ -502,7 +513,6 @@ impl UntypedExpr { Self::Fn { location, .. } | Self::Var { location, .. } | Self::Int { location, .. } - | Self::Todo { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 6f4850e3..8c27c166 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -7,12 +7,13 @@ use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, ClauseGuard, Constant, DataType, Definition, Function, IfBranch, ModuleConstant, Pattern, RecordConstructor, - RecordConstructorArg, RecordUpdateSpread, Span, TypeAlias, TypedArg, TypedConstant, UnOp, - UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, - UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, CAPTURE_VARIABLE, + RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, + TypedConstant, UnOp, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, + UntypedDefinition, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, + CAPTURE_VARIABLE, }, docvec, - expr::UntypedExpr, + expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, parser::extra::{Comment, ModuleExtra}, pretty::{break_, concat, flex_break, join, line, lines, nil, Document, Documentable}, tipo::{self, Type}, @@ -654,9 +655,6 @@ impl<'comments> Formatter<'comments> { final_else, .. } => self.if_expr(branches, final_else), - UntypedExpr::Todo { label: None, .. } => "todo".to_doc(), - - UntypedExpr::Todo { label: Some(l), .. } => docvec!["todo(\"", l, "\")"], UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions), @@ -706,27 +704,8 @@ impl<'comments> Formatter<'comments> { } => self.assignment(pattern, value, None, Some(*kind), annotation), UntypedExpr::Trace { - text: None, then, .. - } => "trace" - .to_doc() - .append(if self.pop_empty_lines(then.start_byte_index()) { - lines(2) - } else { - line() - }) - .append(self.expr(then)), - - UntypedExpr::Trace { - text: Some(l), - then, - .. - } => docvec!["trace(\"", l, "\")"] - .append(if self.pop_empty_lines(then.start_byte_index()) { - lines(2) - } else { - line() - }) - .append(self.expr(then)), + kind, text, then, .. + } => self.trace(kind, text, then), UntypedExpr::When { subjects, clauses, .. @@ -755,9 +734,7 @@ impl<'comments> Formatter<'comments> { .append(suffix) } - UntypedExpr::ErrorTerm { label: None, .. } => "error".to_doc(), - - UntypedExpr::ErrorTerm { label: Some(l), .. } => docvec!["error(\"", l, "\")"], + UntypedExpr::ErrorTerm { .. } => "error".to_doc(), }; commented(document, comments) @@ -772,6 +749,41 @@ impl<'comments> Formatter<'comments> { } } + pub fn trace<'a>( + &mut self, + kind: &'a TraceKind, + text: &'a UntypedExpr, + then: &'a UntypedExpr, + ) -> Document<'a> { + let (keyword, default_text) = match kind { + TraceKind::Trace => ("trace", None), + TraceKind::Error => ("error", Some(DEFAULT_ERROR_STR.to_string())), + TraceKind::Todo => ("todo", Some(DEFAULT_TODO_STR.to_string())), + }; + + let body = match text { + UntypedExpr::String { value, .. } if Some(value) == default_text.as_ref() => { + keyword.to_doc() + } + _ => keyword + .to_doc() + .append(" ") + .append(self.wrap_expr(text)) + .group(), + }; + + match kind { + TraceKind::Error | TraceKind::Todo => body, + TraceKind::Trace => body + .append(if self.pop_empty_lines(then.start_byte_index()) { + lines(2) + } else { + line() + }) + .append(self.expr(then)), + } + } + pub fn pattern_constructor<'a>( &mut self, name: &'a str, @@ -1322,7 +1334,10 @@ impl<'comments> Formatter<'comments> { fn wrap_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { - UntypedExpr::Trace { .. } + UntypedExpr::Trace { + kind: TraceKind::Trace, + .. + } | UntypedExpr::Sequence { .. } | UntypedExpr::Assignment { .. } => "{" .to_doc() @@ -1361,7 +1376,10 @@ impl<'comments> Formatter<'comments> { fn case_clause_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { - UntypedExpr::Trace { .. } + UntypedExpr::Trace { + kind: TraceKind::Trace, + .. + } | UntypedExpr::Sequence { .. } | UntypedExpr::Assignment { .. } => " {" .to_doc() diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index d3df67c3..26c29f5d 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -7,7 +7,7 @@ pub mod lexer; pub mod token; use crate::{ - ast::{self, BinOp, Span, TodoKind, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, + ast::{self, BinOp, Span, TraceKind, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, expr, }; @@ -254,11 +254,7 @@ pub fn fn_parser() -> impl Parser impl Parser impl Parser value} - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .or_not(), - ) + .ignore_then(expr_parser(r.clone())) .then(r.clone()) .map_with_span(|(text, then_), span| expr::UntypedExpr::Trace { + kind: TraceKind::Trace, location: span, then: Box::new(then_), - text, + text: Box::new(text), }), + just(Token::ErrorTerm) + .ignore_then(expr_parser(r.clone()).or_not()) + .map_with_span(|reason, span| expr::UntypedExpr::error(span, reason)), + just(Token::Todo) + .ignore_then(expr_parser(r.clone()).or_not()) + .map_with_span(|reason, span| expr::UntypedExpr::todo(span, reason)), expr_parser(r.clone()) .then(r.repeated()) .foldl(|current, next| current.append_in_sequence(next)), @@ -871,29 +866,6 @@ pub fn expr_parser( name, }); - let todo_parser = just(Token::Todo) - .ignore_then( - select! {Token::String {value} => value} - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .or_not(), - ) - .map_with_span(|label, span| expr::UntypedExpr::Todo { - kind: TodoKind::Keyword, - location: span, - label, - }); - - let error_parser = just(Token::ErrorTerm) - .ignore_then( - select! {Token::String {value} => value} - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .or_not(), - ) - .map_with_span(|label, span| expr::UntypedExpr::ErrorTerm { - location: span, - label, - }); - let tuple = r .clone() .separated_by(just(Token::Comma)) @@ -988,7 +960,23 @@ pub fn expr_parser( }), ))) // TODO: add hint "Did you mean to wrap a multi line clause in curly braces?" - .then(r.clone()) + .then(choice(( + r.clone(), + just(Token::Todo) + .ignore_then( + r.clone() + .then_ignore(one_of(Token::RArrow).not().rewind()) + .or_not(), + ) + .map_with_span(|reason, span| expr::UntypedExpr::todo(span, reason)), + just(Token::ErrorTerm) + .ignore_then( + r.clone() + .then_ignore(just(Token::RArrow).not().rewind()) + .or_not(), + ) + .map_with_span(|reason, span| expr::UntypedExpr::error(span, reason)), + ))) .map_with_span( |(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause { location: span, @@ -1084,8 +1072,6 @@ pub fn expr_parser( record_parser, field_access_constructor, var_parser, - todo_parser, - error_parser, tuple, bytearray, list_parser, diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index f547261b..e6b46c2c 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -126,3 +126,35 @@ fn list_pattern_6() { "#; assert!(check(parse(source_code)).is_ok()) } + +#[test] +fn trace_strings() { + let source_code = r#" + fn bar() { + "BAR" + } + + test foo() { + let msg1 = "FOO" + trace("INLINE") + trace(msg1) + trace(bar()) + True + } + "#; + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn trace_non_strings() { + let source_code = r#" + test foo() { + trace(14 + 42) + True + } + "#; + assert!(matches!( + check(parse(source_code)), + Err((_, Error::CouldNotUnify { .. })) + )) +} diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 90257acd..76b09a0b 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -320,7 +320,7 @@ fn test_nested_function_calls() { , when output.datum is { InlineDatum(_) -> True - _ -> error("expected inline datum") + _ -> error "expected inline datum" }, ] |> list.and @@ -339,7 +339,7 @@ fn test_nested_function_calls() { ), when output.datum is { InlineDatum(_) -> True - _ -> error("expected inline datum") + _ -> error "expected inline datum" }, ] |> list.and @@ -348,3 +348,34 @@ fn test_nested_function_calls() { assert_fmt(src, expected); } + +#[test] +fn format_trace_todo_error() { + let src = indoc! {r#" + fn foo_1() { + todo + } + + fn foo_2() { + todo "my custom message" + } + + fn foo_3() { + when x is { + Foo -> True + _ -> error + } + } + + fn foo_4() { + if 14 == 42 { + error "I don't think so" + } else { + trace "been there" + True + } + } + "#}; + + assert_fmt(src, src); +} diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 129af56a..45dfe250 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -308,10 +308,16 @@ fn empty_function() { code, vec![ast::UntypedDefinition::Fn(Function { arguments: vec![], - body: expr::UntypedExpr::Todo { - kind: ast::TodoKind::EmptyFunction, + body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, location: Span::new((), 0..15), - label: None, + text: Box::new(expr::UntypedExpr::String { + value: "aiken::todo".to_string(), + location: Span::new((), 0..15), + }), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 0..15), + }), }, doc: None, location: Span::new((), 0..12), @@ -1781,10 +1787,16 @@ fn function_def() { vec![ast::UntypedDefinition::Fn(Function { doc: None, arguments: vec![], - body: expr::UntypedExpr::Todo { - kind: ast::TodoKind::EmptyFunction, + body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, location: Span::new((), 0..11), - label: None, + text: Box::new(expr::UntypedExpr::String { + value: "aiken::todo".to_string(), + location: Span::new((), 0..11), + }), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 0..11), + }), }, location: Span::new((), 0..8), name: "foo".to_string(), @@ -2586,3 +2598,345 @@ fn scope_logical_expression() { })], ) } + +#[test] +fn trace_expressions() { + let code = indoc! {r#" + fn foo() { + let msg1 = "FOO" + trace "INLINE" + trace msg1 + trace string.concat(msg1, "BAR") + trace ( 14 + 42 * 1337 ) + Void + } + "#}; + assert_definitions( + code, + vec![ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 13..128), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 13..29), + value: Box::new(expr::UntypedExpr::String { + location: Span::new((), 24..29), + value: "FOO".to_string(), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 17..21), + name: "msg1".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Trace { + kind: ast::TraceKind::Trace, + location: Span::new((), 32..128), + then: Box::new(expr::UntypedExpr::Trace { + kind: ast::TraceKind::Trace, + location: Span::new((), 49..128), + then: Box::new(expr::UntypedExpr::Trace { + kind: ast::TraceKind::Trace, + location: Span::new((), 62..128), + then: Box::new(expr::UntypedExpr::Trace { + kind: ast::TraceKind::Trace, + location: Span::new((), 97..128), + then: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 124..128), + name: "Void".to_string(), + }), + text: Box::new(expr::UntypedExpr::BinOp { + location: Span::new((), 105..119), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 105..107), + value: "14".to_string(), + }), + right: Box::new(expr::UntypedExpr::BinOp { + location: Span::new((), 110..119), + name: ast::BinOp::MultInt, + left: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 110..112), + value: "42".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 115..119), + value: "1337".to_string(), + }), + }), + }), + }), + text: Box::new(expr::UntypedExpr::Call { + arguments: vec![ + ast::CallArg { + label: None, + location: Span::new((), 82..86), + value: expr::UntypedExpr::Var { + location: Span::new((), 82..86), + name: "msg1".to_string(), + }, + }, + ast::CallArg { + label: None, + location: Span::new((), 88..93), + value: expr::UntypedExpr::String { + location: Span::new((), 88..93), + value: "BAR".to_string(), + }, + }, + ], + fun: Box::new(expr::UntypedExpr::FieldAccess { + location: Span::new((), 68..81), + label: "concat".to_string(), + container: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 68..74), + name: "string".to_string(), + }), + }), + location: Span::new((), 68..94), + }), + }), + text: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 55..59), + name: "msg1".to_string(), + }), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 38..46), + value: "INLINE".to_string(), + }), + }, + ], + }, + doc: None, + location: Span::new((), 0..8), + name: "foo".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 129, + })], + ) +} + +#[test] +fn parse_keyword_error() { + let code = indoc! {r#" + fn foo() { + error "not implemented" + } + + fn bar() { + when x is { + Something -> Void + _ -> error + } + } + "#}; + assert_definitions( + code, + vec![ + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Error, + location: Span::new((), 13..36), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 13..36), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 19..36), + value: "not implemented".to_string(), + }), + }, + doc: None, + location: Span::new((), 0..8), + name: "foo".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 37, + }), + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::When { + location: Span::new((), 53..109), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 58..59), + name: "x".to_string(), + }], + clauses: vec![ + ast::Clause { + location: Span::new((), 71..88), + pattern: vec![ast::Pattern::Constructor { + is_record: false, + location: Span::new((), 71..80), + name: "Something".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Var { + location: Span::new((), 84..88), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 95..105), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 95..96), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Error, + location: Span::new((), 100..105), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 100..105), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 100..105), + value: "aiken::error".to_string(), + }), + }, + }, + ], + }, + doc: None, + location: Span::new((), 40..48), + name: "bar".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 110, + }), + ], + ) +} + +#[test] +fn parse_keyword_todo() { + let code = indoc! {r#" + fn foo() { + todo "not implemented" + } + + fn bar() { + when x is { + Foo -> todo + Bar -> True + _ -> False + } + } + "#}; + assert_definitions( + code, + vec![ + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, + location: Span::new((), 13..35), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 13..35), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 18..35), + value: "not implemented".to_string(), + }), + }, + doc: None, + location: Span::new((), 0..8), + name: "foo".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 36, + }), + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::When { + location: Span::new((), 52..120), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 57..58), + name: "x".to_string(), + }], + clauses: vec![ + ast::Clause { + location: Span::new((), 70..81), + pattern: vec![ast::Pattern::Constructor { + is_record: false, + location: Span::new((), 70..73), + name: "Foo".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, + location: Span::new((), 77..81), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 77..81), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 77..81), + value: "aiken::todo".to_string(), + }), + }, + }, + ast::Clause { + location: Span::new((), 88..99), + pattern: vec![ast::Pattern::Constructor { + is_record: false, + location: Span::new((), 88..91), + name: "Bar".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Var { + location: Span::new((), 95..99), + name: "True".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 106..116), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 106..107), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Var { + location: Span::new((), 111..116), + name: "False".to_string(), + }, + }, + ], + }, + doc: None, + location: Span::new((), 39..47), + name: "bar".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 121, + }), + ], + ) +} diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 5fa694a7..fdae1c3e 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -1,6 +1,6 @@ use super::Type; use crate::{ - ast::{Annotation, BinOp, CallArg, Span, TodoKind, UntypedPattern}, + ast::{Annotation, BinOp, CallArg, Span, UntypedPattern}, expr::{self, UntypedExpr}, format::Formatter, levenshtein, @@ -1118,7 +1118,6 @@ pub enum Warning { #[diagnostic(help("You probably want to replace that one with real code... eventually."))] #[diagnostic(code("todo"))] Todo { - kind: TodoKind, #[label] location: Span, tipo: Arc, diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index a6080842..637aaac5 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -5,7 +5,7 @@ use vec1::Vec1; use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, - RecordUpdateSpread, Span, TodoKind, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, + RecordUpdateSpread, Span, TraceKind, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch, UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg, @@ -221,7 +221,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | UntypedExpr::RecordUpdate { .. } | UntypedExpr::Sequence { .. } | UntypedExpr::String { .. } - | UntypedExpr::Todo { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::UnOp { .. } @@ -249,16 +248,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { /// returning an error. pub fn infer(&mut self, expr: UntypedExpr) -> Result { match expr { - UntypedExpr::Todo { - location, - label, - kind, - .. - } => Ok(self.infer_todo(location, kind, label)), - - UntypedExpr::ErrorTerm { location, label } => { - Ok(self.infer_error_term(location, label)) - } + UntypedExpr::ErrorTerm { location } => Ok(self.infer_error_term(location)), UntypedExpr::Var { location, name, .. } => self.infer_var(name, location), @@ -309,7 +299,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, then, text, - } => self.infer_trace(*then, location, text), + kind, + } => self.infer_trace(kind, *then, location, *text), UntypedExpr::When { location, @@ -1857,47 +1848,37 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } - fn infer_todo(&mut self, location: Span, kind: TodoKind, label: Option) -> TypedExpr { + fn infer_error_term(&mut self, location: Span) -> TypedExpr { let tipo = self.new_unbound_var(); - self.environment.warnings.push(Warning::Todo { - kind, - location, - tipo: tipo.clone(), - }); - - TypedExpr::Todo { - location, - label, - tipo, - } - } - - fn infer_error_term(&mut self, location: Span, label: Option) -> TypedExpr { - let tipo = self.new_unbound_var(); - - TypedExpr::ErrorTerm { - location, - tipo, - label, - } + TypedExpr::ErrorTerm { location, tipo } } fn infer_trace( &mut self, + kind: TraceKind, then: UntypedExpr, location: Span, - text: Option, + text: UntypedExpr, ) -> Result { - let then = self.infer(then)?; + let text = self.infer(text)?; + self.unify(text.tipo(), string(), text.location(), false)?; + let then = self.infer(then)?; let tipo = then.tipo(); + if let TraceKind::Todo = kind { + self.environment.warnings.push(Warning::Todo { + location, + tipo: tipo.clone(), + }) + } + Ok(TypedExpr::Trace { location, tipo, then: Box::new(then), - text, + text: Box::new(text), }) } diff --git a/crates/aiken-lang/src/uplc.rs b/crates/aiken-lang/src/uplc.rs index 5578c610..1d8fd79c 100644 --- a/crates/aiken-lang/src/uplc.rs +++ b/crates/aiken-lang/src/uplc.rs @@ -587,13 +587,6 @@ impl<'a> CodeGenerator<'a> { constants_ir(literal, ir_stack, scope); } }, - TypedExpr::Todo { label, tipo, .. } => { - ir_stack.push(Air::Todo { - scope, - label: label.clone(), - tipo: tipo.clone(), - }); - } TypedExpr::RecordUpdate { spread, args, tipo, .. } => { @@ -651,19 +644,21 @@ impl<'a> CodeGenerator<'a> { ir_stack.append(&mut elems_air); } + TypedExpr::Trace { tipo, then, text, .. } => { let mut scope = scope; ir_stack.push(Air::Trace { - text: text.clone(), tipo: tipo.clone(), scope: scope.clone(), }); scope.push(self.id_gen.next()); + self.build_ir(text, ir_stack, scope.clone()); + scope.push(self.id_gen.next()); self.build_ir(then, ir_stack, scope); } @@ -677,11 +672,10 @@ impl<'a> CodeGenerator<'a> { self.build_ir(tuple, ir_stack, scope); } - TypedExpr::ErrorTerm { tipo, label, .. } => { + TypedExpr::ErrorTerm { tipo, .. } => { ir_stack.push(Air::ErrorTerm { scope, tipo: tipo.clone(), - label: label.clone(), }); } } @@ -2566,10 +2560,19 @@ impl<'a> CodeGenerator<'a> { }); } + assert_vec.push(Air::Trace { + scope: scope.clone(), + tipo: tipo.clone(), + }); + + assert_vec.push(Air::String { + scope: scope.clone(), + value: "Constr index did not match any type variant".to_string(), + }); + assert_vec.push(Air::ErrorTerm { scope, tipo: tipo.clone(), - label: Some("Constr index did not match any type variant".to_string()), }); } } @@ -3484,33 +3487,21 @@ impl<'a> CodeGenerator<'a> { tuple_index, }; } - Air::Todo { tipo, scope, label } => { - let mut replaced_type = tipo.clone(); - replace_opaque_type(&mut replaced_type, self.data_types.clone()); - - ir_stack[index] = Air::Todo { - scope, - label, - tipo: replaced_type, - }; - } - Air::ErrorTerm { tipo, scope, label } => { + Air::ErrorTerm { tipo, scope } => { let mut replaced_type = tipo.clone(); replace_opaque_type(&mut replaced_type, self.data_types.clone()); ir_stack[index] = Air::ErrorTerm { scope, tipo: replaced_type, - label, }; } - Air::Trace { tipo, scope, text } => { + Air::Trace { tipo, scope } => { let mut replaced_type = tipo.clone(); replace_opaque_type(&mut replaced_type, self.data_types.clone()); ir_stack[index] = Air::Trace { scope, - text, tipo: replaced_type, }; } @@ -5278,23 +5269,6 @@ impl<'a> CodeGenerator<'a> { arg_stack.push(term); } } - Air::Todo { label, .. } => { - let term = apply_wrap( - apply_wrap( - Term::Builtin(DefaultFunction::Trace).force_wrap(), - Term::Constant( - UplcConstant::String( - label.unwrap_or_else(|| "aiken::todo".to_string()), - ) - .into(), - ), - ), - Term::Delay(Term::Error.into()), - ) - .force_wrap(); - - arg_stack.push(term); - } Air::RecordUpdate { highest_index, indices, @@ -5631,41 +5605,20 @@ impl<'a> CodeGenerator<'a> { arg_stack.push(term); } - Air::Trace { text, .. } => { + Air::Trace { .. } => { + let text = arg_stack.pop().unwrap(); + let term = arg_stack.pop().unwrap(); let term = apply_wrap( - apply_wrap( - Term::Builtin(DefaultFunction::Trace).force_wrap(), - Term::Constant( - UplcConstant::String( - text.unwrap_or_else(|| "aiken::trace".to_string()), - ) - .into(), - ), - ), + apply_wrap(Term::Builtin(DefaultFunction::Trace).force_wrap(), text), Term::Delay(term.into()), ) .force_wrap(); arg_stack.push(term); } - Air::ErrorTerm { label, .. } => { - if let Some(label) = label { - let term = apply_wrap( - apply_wrap( - Term::Builtin(DefaultFunction::Trace).force_wrap(), - Term::Constant(UplcConstant::String(label).into()), - ), - Term::Delay(Term::Error.into()), - ) - .force_wrap(); - - arg_stack.push(term); - } else { - arg_stack.push(Term::Error) - } - } + Air::ErrorTerm { .. } => arg_stack.push(Term::Error), Air::TupleClause { tipo, indices, diff --git a/examples/acceptance_tests/032/lib/tests.ak b/examples/acceptance_tests/032/lib/tests.ak index 035686da..2c2326b4 100644 --- a/examples/acceptance_tests/032/lib/tests.ak +++ b/examples/acceptance_tests/032/lib/tests.ak @@ -1,9 +1,19 @@ +use aiken/builtin + +fn concat(left: String, right: String) -> String { + builtin.append_bytearray( + builtin.encode_utf8(left), + builtin.encode_utf8(right), + ) + |> builtin.decode_utf8 +} + fn is_negative(i: Int) -> Bool { if i < 0 { - trace("is negative") + trace "is negative" True } else { - trace("is non-negative") + trace concat("is", concat(" ", "non-negative")) False } }