From 7abd76b6ad53699cd63ff4b21ce5cd273f42f976 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Feb 2023 18:46:07 +0100 Subject: [PATCH 1/5] Allow to trace expressions (and not only string literals) This however enforces that the argument unifies to a `String`. So this is more flexible than the previous form, but does fundamentally the same thing. Fixes #378. --- crates/aiken-lang/src/air.rs | 1 - crates/aiken-lang/src/builder.rs | 4 +- crates/aiken-lang/src/expr.rs | 4 +- crates/aiken-lang/src/format.rs | 18 +--- crates/aiken-lang/src/parser.rs | 7 +- crates/aiken-lang/src/tests/check.rs | 32 ++++++ crates/aiken-lang/src/tests/parser.rs | 118 +++++++++++++++++++++ crates/aiken-lang/src/tipo/expr.rs | 10 +- crates/aiken-lang/src/uplc.rs | 21 ++-- examples/acceptance_tests/032/lib/tests.ak | 12 ++- 10 files changed, 185 insertions(+), 42 deletions(-) diff --git a/crates/aiken-lang/src/air.rs b/crates/aiken-lang/src/air.rs index 7460abff..ac43c86c 100644 --- a/crates/aiken-lang/src/air.rs +++ b/crates/aiken-lang/src/air.rs @@ -249,7 +249,6 @@ pub enum Air { Trace { scope: Vec, - text: Option, tipo: Arc, }, } diff --git a/crates/aiken-lang/src/builder.rs b/crates/aiken-lang/src/builder.rs index cf9ec97a..94205690 100644 --- a/crates/aiken-lang/src/builder.rs +++ b/crates/aiken-lang/src/builder.rs @@ -1685,12 +1685,12 @@ pub fn monomorphize( needs_variant = true; } } - Air::Trace { scope, text, 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::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..29c17a58 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -96,7 +96,7 @@ pub enum TypedExpr { location: Span, tipo: Arc, then: Box, - text: Option, + text: Box, }, When { @@ -389,7 +389,7 @@ pub enum UntypedExpr { Trace { location: Span, then: Box, - text: Option, + text: Box, }, When { diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 6f4850e3..cade25c2 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -705,22 +705,10 @@ impl<'comments> Formatter<'comments> { .. } => self.assignment(pattern, value, None, Some(*kind), annotation), - UntypedExpr::Trace { - text: None, then, .. - } => "trace" + UntypedExpr::Trace { text, 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(wrap_args([(self.wrap_expr(text), false)])) + .group() .append(if self.pop_empty_lines(then.start_byte_index()) { lines(2) } else { diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index d3df67c3..06bd9896 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -592,15 +592,14 @@ pub fn expr_seq_parser() -> impl Parser value} - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .or_not(), + expr_parser(r.clone()) + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), ) .then(r.clone()) .map_with_span(|(text, then_), span| expr::UntypedExpr::Trace { location: span, then: Box::new(then_), - text, + text: Box::new(text), }), expr_parser(r.clone()) .then(r.repeated()) 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/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 129af56a..f5ddc28b 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -2586,3 +2586,121 @@ 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 { + location: Span::new((), 32..128), + then: Box::new(expr::UntypedExpr::Trace { + location: Span::new((), 49..128), + then: Box::new(expr::UntypedExpr::Trace { + location: Span::new((), 62..128), + then: Box::new(expr::UntypedExpr::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, + })], + ) +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index a6080842..5b04947c 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -309,7 +309,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, then, text, - } => self.infer_trace(*then, location, text), + } => self.infer_trace(*then, location, *text), UntypedExpr::When { location, @@ -1887,17 +1887,19 @@ impl<'a, 'b> ExprTyper<'a, 'b> { &mut self, 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(); 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..c3bbd0af 100644 --- a/crates/aiken-lang/src/uplc.rs +++ b/crates/aiken-lang/src/uplc.rs @@ -651,19 +651,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); } @@ -3504,13 +3506,12 @@ impl<'a> CodeGenerator<'a> { 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, }; } @@ -5631,19 +5632,13 @@ 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(); diff --git a/examples/acceptance_tests/032/lib/tests.ak b/examples/acceptance_tests/032/lib/tests.ak index 035686da..d1905953 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") True } else { - trace("is non-negative") + trace(concat("is", concat(" ", "non-negative"))) False } } From 7b676643bd371ba44f9a4464aed0acaa4309e4ac Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Feb 2023 21:09:03 +0100 Subject: [PATCH 2/5] Lift 'error' up one level in the parser and remove its label. We now parse errors as a combination of a trace plus and error term. This is a baby step in order to simplify the code generation down the line and the internal representation of todo / errors. --- crates/aiken-lang/src/air.rs | 1 - crates/aiken-lang/src/builder.rs | 25 ++++-- crates/aiken-lang/src/expr.rs | 2 - crates/aiken-lang/src/format.rs | 4 +- crates/aiken-lang/src/parser.rs | 62 ++++++++------- crates/aiken-lang/src/tests/parser.rs | 108 ++++++++++++++++++++++++++ crates/aiken-lang/src/tipo/expr.rs | 12 +-- crates/aiken-lang/src/uplc.rs | 34 ++++---- 8 files changed, 176 insertions(+), 72 deletions(-) diff --git a/crates/aiken-lang/src/air.rs b/crates/aiken-lang/src/air.rs index ac43c86c..39909907 100644 --- a/crates/aiken-lang/src/air.rs +++ b/crates/aiken-lang/src/air.rs @@ -244,7 +244,6 @@ pub enum Air { ErrorTerm { scope: Vec, tipo: Arc, - label: Option, }, Trace { diff --git a/crates/aiken-lang/src/builder.rs b/crates/aiken-lang/src/builder.rs index 94205690..f204e91f 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() { @@ -1676,12 +1687,12 @@ pub fn monomorphize( needs_variant = true; } } - Air::ErrorTerm { 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::ErrorTerm { scope, tipo, label }; + new_air[index] = Air::ErrorTerm { scope, tipo }; needs_variant = true; } } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 29c17a58..9afb0803 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -152,7 +152,6 @@ pub enum TypedExpr { ErrorTerm { location: Span, tipo: Arc, - label: Option, }, RecordUpdate { @@ -429,7 +428,6 @@ pub enum UntypedExpr { ErrorTerm { location: Span, - label: Option, }, RecordUpdate { diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index cade25c2..12e42e40 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -743,9 +743,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) diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 06bd9896..84816880 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -591,16 +591,14 @@ pub fn expr_seq_parser() -> impl Parser impl Parser( + r: Recursive<'a, Token, expr::UntypedExpr, ParseError>, + keyword: Token, + default_value: &'a str, +) -> impl Parser + 'a { + just(keyword) + .ignore_then(expr_parser(r.clone()).or_not()) + .then(r.clone().or_not()) + .map_with_span(|(text, then_), span| match then_ { + None => expr::UntypedExpr::Trace { + location: span, + then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), + text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { + location: span, + value: default_value.to_string(), + })), + }, + Some(e) => expr::UntypedExpr::Trace { + location: span, + then: Box::new( + expr::UntypedExpr::ErrorTerm { location: span }.append_in_sequence(e), + ), + text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { + location: span, + value: default_value.to_string(), + })), + }, + }) +} + pub fn expr_parser( seq_r: Recursive<'_, Token, expr::UntypedExpr, ParseError>, ) -> impl Parser + '_ { @@ -870,29 +898,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)) @@ -940,6 +945,7 @@ pub fn expr_parser( choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), just(Token::RightParen), ), + just(Token::ErrorTerm).rewind().ignore_then(seq_r.clone()), )); let anon_fn_parser = just(Token::Fn) @@ -1083,8 +1089,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/parser.rs b/crates/aiken-lang/src/tests/parser.rs index f5ddc28b..322ccb73 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -2704,3 +2704,111 @@ fn trace_expressions() { })], ) } + +#[test] +fn parse_keyword_error() { + let code = indoc! {r#" + fn foo() { + error "not implemented" + Void + } + + fn bar() { + when x is { + Something -> Void + _ -> error + } + } + "#}; + assert_definitions( + code, + vec![ + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Trace { + location: Span::new((), 13..43), + then: Box::new(expr::UntypedExpr::Sequence { + location: Span::new((), 13..43), + expressions: vec![ + expr::UntypedExpr::ErrorTerm { + location: Span::new((), 13..43), + }, + expr::UntypedExpr::Var { + location: Span::new((), 39..43), + name: "Void".to_string(), + }, + ], + }), + 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: 44, + }), + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::When { + location: Span::new((), 60..116), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 65..66), + name: "x".to_string(), + }], + clauses: vec![ + ast::Clause { + location: Span::new((), 78..95), + pattern: vec![ast::Pattern::Constructor { + is_record: false, + location: Span::new((), 78..87), + 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((), 91..95), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 102..112), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 102..103), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Trace { + location: Span::new((), 107..112), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 107..112), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 107..112), + value: "aiken::error".to_string(), + }), + }, + }, + ], + }, + doc: None, + location: Span::new((), 47..55), + name: "bar".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 117, + }), + ], + ) +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 5b04947c..70b2e739 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -256,9 +256,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => 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), @@ -1873,14 +1871,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } } - fn infer_error_term(&mut self, location: Span, label: Option) -> TypedExpr { + fn infer_error_term(&mut self, location: Span) -> TypedExpr { let tipo = self.new_unbound_var(); - TypedExpr::ErrorTerm { - location, - tipo, - label, - } + TypedExpr::ErrorTerm { location, tipo } } fn infer_trace( diff --git a/crates/aiken-lang/src/uplc.rs b/crates/aiken-lang/src/uplc.rs index c3bbd0af..ab2dfc00 100644 --- a/crates/aiken-lang/src/uplc.rs +++ b/crates/aiken-lang/src/uplc.rs @@ -679,11 +679,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(), }); } } @@ -2568,10 +2567,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()), }); } } @@ -3496,14 +3504,13 @@ impl<'a> CodeGenerator<'a> { 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 } => { @@ -5645,22 +5652,7 @@ impl<'a> CodeGenerator<'a> { 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, From 6525f21712ab1ed5f97fa62a029aa102fc022f60 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Feb 2023 21:53:11 +0100 Subject: [PATCH 3/5] Remove 'Todo' from the AST & AIR Todo is fundamentally just a trace and an error. The only reason we kept it as a separate element in the AST is for the formatter to work out whether it should format something back to a todo or something else. However, this introduces redundancy in the code internally and makes the AIR more complicated than it needs to be. Both todo and errors can actually be represented as trace + errors, and we only need to record their preferred shape when parsing so that we can format them back to what's expected. --- crates/aiken-lang/src/air.rs | 8 -- crates/aiken-lang/src/ast.rs | 7 +- crates/aiken-lang/src/builder.rs | 9 -- crates/aiken-lang/src/expr.rs | 35 +++--- crates/aiken-lang/src/format.rs | 3 - crates/aiken-lang/src/parser.rs | 56 ++++----- crates/aiken-lang/src/tests/parser.rs | 162 +++++++++++++++++++++----- crates/aiken-lang/src/tipo/error.rs | 3 +- crates/aiken-lang/src/tipo/expr.rs | 33 ++---- crates/aiken-lang/src/uplc.rs | 34 ------ 10 files changed, 179 insertions(+), 171 deletions(-) diff --git a/crates/aiken-lang/src/air.rs b/crates/aiken-lang/src/air.rs index 39909907..1bd2add4 100644 --- a/crates/aiken-lang/src/air.rs +++ b/crates/aiken-lang/src/air.rs @@ -235,12 +235,6 @@ pub enum Air { }, // Misc. - Todo { - scope: Vec, - label: Option, - tipo: Arc, - }, - ErrorTerm { scope: Vec, tipo: Arc, @@ -290,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(), } @@ -373,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 f204e91f..063c623f 100644 --- a/crates/aiken-lang/src/builder.rs +++ b/crates/aiken-lang/src/builder.rs @@ -1678,15 +1678,6 @@ pub fn monomorphize( needs_variant = true; } } - Air::Todo { scope, label, 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 }; - needs_variant = true; - } - } Air::ErrorTerm { scope, tipo } => { if tipo.is_generic() { let mut tipo = tipo.clone(); diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 9afb0803..9bf00b3c 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -5,8 +5,7 @@ use vec1::Vec1; use crate::{ ast::{ Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, DefinitionLocation, IfBranch, - Pattern, RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UnOp, - UntypedRecordUpdateArg, + Pattern, RecordUpdateSpread, Span, TypedRecordUpdateArg, UnOp, UntypedRecordUpdateArg, }, builtins::void, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, @@ -143,12 +142,6 @@ pub enum TypedExpr { tuple: Box, }, - Todo { - location: Span, - label: Option, - tipo: Arc, - }, - ErrorTerm { location: Span, tipo: Arc, @@ -176,7 +169,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, .. } @@ -222,7 +214,6 @@ impl TypedExpr { | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::When { .. } - | TypedExpr::Todo { .. } | TypedExpr::ErrorTerm { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } @@ -261,7 +252,6 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Var { location, .. } | Self::Trace { location, .. } - | Self::Todo { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } @@ -297,7 +287,6 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Trace { location, .. } | Self::Var { location, .. } - | Self::Todo { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } @@ -420,12 +409,6 @@ pub enum UntypedExpr { tuple: Box, }, - Todo { - kind: TodoKind, - location: Span, - label: Option, - }, - ErrorTerm { location: Span, }, @@ -444,7 +427,22 @@ 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, + then: Box::new(UntypedExpr::ErrorTerm { location }), + text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { + location, + value: DEFAULT_TODO_STR.to_string(), + })), + } + } + pub fn append_in_sequence(self, next: Self) -> Self { let location = Span { start: self.location().start, @@ -500,7 +498,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 12e42e40..edc131d4 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -654,9 +654,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), diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 84816880..46dcc6c9 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, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, expr, }; @@ -254,11 +254,7 @@ pub fn fn_parser() -> impl Parser impl Parser impl Parser impl Parser( - r: Recursive<'a, Token, expr::UntypedExpr, ParseError>, +pub fn todo_parser( + r: Recursive<'_, Token, expr::UntypedExpr, ParseError>, keyword: Token, - default_value: &'a str, -) -> impl Parser + 'a { - just(keyword) +) -> impl Parser + '_ { + just(keyword.clone()) .ignore_then(expr_parser(r.clone()).or_not()) - .then(r.clone().or_not()) - .map_with_span(|(text, then_), span| match then_ { - None => expr::UntypedExpr::Trace { + .map_with_span(move |text, span| expr::UntypedExpr::Trace { + location: span, + then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), + text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { location: span, - then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), - text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { - location: span, - value: default_value.to_string(), - })), - }, - Some(e) => expr::UntypedExpr::Trace { - location: span, - then: Box::new( - expr::UntypedExpr::ErrorTerm { location: span }.append_in_sequence(e), - ), - text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { - location: span, - value: default_value.to_string(), - })), - }, + value: match keyword { + Token::ErrorTerm => expr::DEFAULT_ERROR_STR.to_string(), + Token::Todo => expr::DEFAULT_TODO_STR.to_string(), + _ => unreachable!(), + }, + })), }) } @@ -946,6 +929,7 @@ pub fn expr_parser( just(Token::RightParen), ), just(Token::ErrorTerm).rewind().ignore_then(seq_r.clone()), + just(Token::Todo).rewind().ignore_then(seq_r.clone()), )); let anon_fn_parser = just(Token::Fn) diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 322ccb73..15712962 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -308,10 +308,15 @@ fn empty_function() { code, vec![ast::UntypedDefinition::Fn(Function { arguments: vec![], - body: expr::UntypedExpr::Todo { - kind: ast::TodoKind::EmptyFunction, + body: expr::UntypedExpr::Trace { 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 +1786,15 @@ fn function_def() { vec![ast::UntypedDefinition::Fn(Function { doc: None, arguments: vec![], - body: expr::UntypedExpr::Todo { - kind: ast::TodoKind::EmptyFunction, + body: expr::UntypedExpr::Trace { 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(), @@ -2710,7 +2720,6 @@ fn parse_keyword_error() { let code = indoc! {r#" fn foo() { error "not implemented" - Void } fn bar() { @@ -2726,18 +2735,9 @@ fn parse_keyword_error() { ast::Definition::Fn(Function { arguments: vec![], body: expr::UntypedExpr::Trace { - location: Span::new((), 13..43), - then: Box::new(expr::UntypedExpr::Sequence { - location: Span::new((), 13..43), - expressions: vec![ - expr::UntypedExpr::ErrorTerm { - location: Span::new((), 13..43), - }, - expr::UntypedExpr::Var { - location: Span::new((), 39..43), - name: "Void".to_string(), - }, - ], + 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), @@ -2750,22 +2750,22 @@ fn parse_keyword_error() { public: false, return_annotation: None, return_type: (), - end_position: 44, + end_position: 37, }), ast::Definition::Fn(Function { arguments: vec![], body: expr::UntypedExpr::When { - location: Span::new((), 60..116), + location: Span::new((), 53..109), subjects: vec![expr::UntypedExpr::Var { - location: Span::new((), 65..66), + location: Span::new((), 58..59), name: "x".to_string(), }], clauses: vec![ ast::Clause { - location: Span::new((), 78..95), + location: Span::new((), 71..88), pattern: vec![ast::Pattern::Constructor { is_record: false, - location: Span::new((), 78..87), + location: Span::new((), 71..80), name: "Something".to_string(), arguments: vec![], module: None, @@ -2776,25 +2776,25 @@ fn parse_keyword_error() { alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Var { - location: Span::new((), 91..95), + location: Span::new((), 84..88), name: "Void".to_string(), }, }, ast::Clause { - location: Span::new((), 102..112), + location: Span::new((), 95..105), pattern: vec![ast::Pattern::Discard { name: "_".to_string(), - location: Span::new((), 102..103), + location: Span::new((), 95..96), }], alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Trace { - location: Span::new((), 107..112), + location: Span::new((), 100..105), then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 107..112), + location: Span::new((), 100..105), }), text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 107..112), + location: Span::new((), 100..105), value: "aiken::error".to_string(), }), }, @@ -2802,12 +2802,110 @@ fn parse_keyword_error() { ], }, doc: None, - location: Span::new((), 47..55), + location: Span::new((), 40..48), name: "bar".to_string(), public: false, return_annotation: None, return_type: (), - end_position: 117, + end_position: 110, + }), + ], + ) +} + +#[test] +fn parse_keyword_todo() { + let code = indoc! {r#" + fn foo() { + todo "not implemented" + } + + fn bar() { + when x is { + Something -> Void + _ -> todo + } + } + "#}; + assert_definitions( + code, + vec![ + ast::Definition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Trace { + 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..107), + subjects: vec![expr::UntypedExpr::Var { + location: Span::new((), 57..58), + name: "x".to_string(), + }], + clauses: vec![ + ast::Clause { + location: Span::new((), 70..87), + pattern: vec![ast::Pattern::Constructor { + is_record: false, + location: Span::new((), 70..79), + 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((), 83..87), + name: "Void".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 94..103), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 94..95), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Trace { + location: Span::new((), 99..103), + then: Box::new(expr::UntypedExpr::ErrorTerm { + location: Span::new((), 99..103), + }), + text: Box::new(expr::UntypedExpr::String { + location: Span::new((), 99..103), + value: "aiken::todo".to_string(), + }), + }, + }, + ], + }, + doc: None, + location: Span::new((), 39..47), + name: "bar".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 108, }), ], ) 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 70b2e739..6a906579 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, 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,13 +248,6 @@ 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 } => Ok(self.infer_error_term(location)), UntypedExpr::Var { location, name, .. } => self.infer_var(name, location), @@ -1855,22 +1847,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } - fn infer_todo(&mut self, location: Span, kind: TodoKind, label: Option) -> 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) -> TypedExpr { let tipo = self.new_unbound_var(); @@ -1889,6 +1865,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let then = self.infer(then)?; let tipo = then.tipo(); + // TODO: reinstate once we can distinguish traces + // + // self.environment.warnings.push(Warning::Todo { + // location, + // tipo: tipo.clone(), + // }) + Ok(TypedExpr::Trace { location, tipo, diff --git a/crates/aiken-lang/src/uplc.rs b/crates/aiken-lang/src/uplc.rs index ab2dfc00..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, .. } => { @@ -3494,16 +3487,6 @@ 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 } => { let mut replaced_type = tipo.clone(); replace_opaque_type(&mut replaced_type, self.data_types.clone()); @@ -5286,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, From 808ff97c68b904de0e56ebc792853120d65932df Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 15 Feb 2023 23:02:49 +0100 Subject: [PATCH 4/5] Preserve trace, error & todo formatting. --- crates/aiken-lang/src/expr.rs | 5 +- crates/aiken-lang/src/format.rs | 67 ++++++++++++++++------ crates/aiken-lang/src/parser.rs | 28 +++++---- crates/aiken-lang/src/tests/format.rs | 35 ++++++++++- crates/aiken-lang/src/tests/parser.rs | 10 ++++ crates/aiken-lang/src/tipo/expr.rs | 18 +++--- examples/acceptance_tests/032/lib/tests.ak | 4 +- 7 files changed, 127 insertions(+), 40 deletions(-) diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 9bf00b3c..70da624e 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -5,7 +5,8 @@ use vec1::Vec1; use crate::{ ast::{ Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, DefinitionLocation, IfBranch, - Pattern, RecordUpdateSpread, Span, TypedRecordUpdateArg, UnOp, UntypedRecordUpdateArg, + Pattern, RecordUpdateSpread, Span, TraceKind, TypedRecordUpdateArg, UnOp, + UntypedRecordUpdateArg, }, builtins::void, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, @@ -375,6 +376,7 @@ pub enum UntypedExpr { }, Trace { + kind: TraceKind, location: Span, then: Box, text: Box, @@ -435,6 +437,7 @@ 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, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index edc131d4..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}, @@ -702,16 +703,9 @@ impl<'comments> Formatter<'comments> { .. } => self.assignment(pattern, value, None, Some(*kind), annotation), - UntypedExpr::Trace { text, then, .. } => "trace" - .to_doc() - .append(wrap_args([(self.wrap_expr(text), false)])) - .group() - .append(if self.pop_empty_lines(then.start_byte_index()) { - lines(2) - } else { - line() - }) - .append(self.expr(then)), + UntypedExpr::Trace { + kind, text, then, .. + } => self.trace(kind, text, then), UntypedExpr::When { subjects, clauses, .. @@ -755,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, @@ -1305,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() @@ -1344,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 46dcc6c9..e0457e60 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, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, + ast::{self, BinOp, Span, TraceKind, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, expr, }; @@ -586,6 +586,7 @@ pub fn expr_seq_parser() -> impl Parser impl Parser + '_ { just(keyword.clone()) .ignore_then(expr_parser(r.clone()).or_not()) - .map_with_span(move |text, span| expr::UntypedExpr::Trace { - location: span, - then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), - text: Box::new(text.unwrap_or_else(|| expr::UntypedExpr::String { + .map_with_span(move |text, span| { + let (kind, value) = match keyword { + Token::ErrorTerm => (TraceKind::Error, expr::DEFAULT_ERROR_STR.to_string()), + Token::Todo => (TraceKind::Todo, expr::DEFAULT_TODO_STR.to_string()), + _ => unreachable!(), + }; + + expr::UntypedExpr::Trace { + kind, location: span, - value: match keyword { - Token::ErrorTerm => expr::DEFAULT_ERROR_STR.to_string(), - Token::Todo => expr::DEFAULT_TODO_STR.to_string(), - _ => unreachable!(), - }, - })), + then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), + text: Box::new(text.unwrap_or(expr::UntypedExpr::String { + location: span, + value, + })), + } }) } 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 15712962..46710255 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -309,6 +309,7 @@ fn empty_function() { vec![ast::UntypedDefinition::Fn(Function { arguments: vec![], body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, location: Span::new((), 0..15), text: Box::new(expr::UntypedExpr::String { value: "aiken::todo".to_string(), @@ -1787,6 +1788,7 @@ fn function_def() { doc: None, arguments: vec![], body: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, location: Span::new((), 0..11), text: Box::new(expr::UntypedExpr::String { value: "aiken::todo".to_string(), @@ -2630,12 +2632,16 @@ fn trace_expressions() { 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), @@ -2735,6 +2741,7 @@ fn parse_keyword_error() { 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), @@ -2789,6 +2796,7 @@ fn parse_keyword_error() { 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), @@ -2833,6 +2841,7 @@ fn parse_keyword_todo() { 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), @@ -2887,6 +2896,7 @@ fn parse_keyword_todo() { alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Trace { + kind: ast::TraceKind::Todo, location: Span::new((), 99..103), then: Box::new(expr::UntypedExpr::ErrorTerm { location: Span::new((), 99..103), diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 6a906579..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, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, + RecordUpdateSpread, Span, TraceKind, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedConstant, TypedIfBranch, TypedMultiPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedIfBranch, UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg, @@ -299,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, @@ -1855,6 +1856,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { fn infer_trace( &mut self, + kind: TraceKind, then: UntypedExpr, location: Span, text: UntypedExpr, @@ -1865,12 +1867,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let then = self.infer(then)?; let tipo = then.tipo(); - // TODO: reinstate once we can distinguish traces - // - // self.environment.warnings.push(Warning::Todo { - // location, - // tipo: tipo.clone(), - // }) + if let TraceKind::Todo = kind { + self.environment.warnings.push(Warning::Todo { + location, + tipo: tipo.clone(), + }) + } Ok(TypedExpr::Trace { location, diff --git a/examples/acceptance_tests/032/lib/tests.ak b/examples/acceptance_tests/032/lib/tests.ak index d1905953..2c2326b4 100644 --- a/examples/acceptance_tests/032/lib/tests.ak +++ b/examples/acceptance_tests/032/lib/tests.ak @@ -10,10 +10,10 @@ fn concat(left: String, right: String) -> String { fn is_negative(i: Int) -> Bool { if i < 0 { - trace("is negative") + trace "is negative" True } else { - trace(concat("is", concat(" ", "non-negative"))) + trace concat("is", concat(" ", "non-negative")) False } } From 56258dc815363498a86e393c1a3ecd2bf1ffa0b9 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 16 Feb 2023 00:33:13 +0100 Subject: [PATCH 5/5] Fix todo/error parser on when clauses. --- Cargo.lock | 83 +++++++++------------------ crates/aiken-lang/Cargo.toml | 2 +- crates/aiken-lang/src/expr.rs | 12 ++++ crates/aiken-lang/src/parser.rs | 53 ++++++++--------- crates/aiken-lang/src/tests/parser.rs | 62 +++++++++++++------- 5 files changed, 104 insertions(+), 108 deletions(-) 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/expr.rs b/crates/aiken-lang/src/expr.rs index 70da624e..b5dab82f 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -446,6 +446,18 @@ impl UntypedExpr { } } + 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, diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index e0457e60..26c29f5d 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -591,8 +591,12 @@ pub fn expr_seq_parser() -> impl Parser impl Parser, - keyword: Token, -) -> impl Parser + '_ { - just(keyword.clone()) - .ignore_then(expr_parser(r.clone()).or_not()) - .map_with_span(move |text, span| { - let (kind, value) = match keyword { - Token::ErrorTerm => (TraceKind::Error, expr::DEFAULT_ERROR_STR.to_string()), - Token::Todo => (TraceKind::Todo, expr::DEFAULT_TODO_STR.to_string()), - _ => unreachable!(), - }; - - expr::UntypedExpr::Trace { - kind, - location: span, - then: Box::new(expr::UntypedExpr::ErrorTerm { location: span }), - text: Box::new(text.unwrap_or(expr::UntypedExpr::String { - location: span, - value, - })), - } - }) -} - pub fn expr_parser( seq_r: Recursive<'_, Token, expr::UntypedExpr, ParseError>, ) -> impl Parser + '_ { @@ -934,8 +913,6 @@ pub fn expr_parser( choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), just(Token::RightParen), ), - just(Token::ErrorTerm).rewind().ignore_then(seq_r.clone()), - just(Token::Todo).rewind().ignore_then(seq_r.clone()), )); let anon_fn_parser = just(Token::Fn) @@ -983,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, diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 46710255..45dfe250 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -2830,8 +2830,9 @@ fn parse_keyword_todo() { fn bar() { when x is { - Something -> Void - _ -> todo + Foo -> todo + Bar -> True + _ -> False } } "#}; @@ -2862,18 +2863,44 @@ fn parse_keyword_todo() { ast::Definition::Fn(Function { arguments: vec![], body: expr::UntypedExpr::When { - location: Span::new((), 52..107), + 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..87), + location: Span::new((), 70..81), pattern: vec![ast::Pattern::Constructor { is_record: false, - location: Span::new((), 70..79), - name: "Something".to_string(), + 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: (), @@ -2883,28 +2910,21 @@ fn parse_keyword_todo() { alternative_patterns: vec![], guard: None, then: expr::UntypedExpr::Var { - location: Span::new((), 83..87), - name: "Void".to_string(), + location: Span::new((), 95..99), + name: "True".to_string(), }, }, ast::Clause { - location: Span::new((), 94..103), + location: Span::new((), 106..116), pattern: vec![ast::Pattern::Discard { name: "_".to_string(), - location: Span::new((), 94..95), + location: Span::new((), 106..107), }], alternative_patterns: vec![], guard: None, - then: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Todo, - location: Span::new((), 99..103), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 99..103), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 99..103), - value: "aiken::todo".to_string(), - }), + then: expr::UntypedExpr::Var { + location: Span::new((), 111..116), + name: "False".to_string(), }, }, ], @@ -2915,7 +2935,7 @@ fn parse_keyword_todo() { public: false, return_annotation: None, return_type: (), - end_position: 108, + end_position: 121, }), ], )