From f9719af23e697abca7129c09e886079368917b0a Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 18 Jul 2024 08:32:26 +0200 Subject: [PATCH 01/10] Allow variadic arguments in trace Although, doesn't do anything with them yet. The idea is to simplify the use of trace to make it a lot more useful than it currently is. --- crates/aiken-lang/src/expr.rs | 9 ++- crates/aiken-lang/src/format.rs | 27 +++++--- .../definition/snapshots/function_empty.snap | 3 +- .../snapshots/function_non_public.snap | 3 +- .../src/parser/expr/fail_todo_trace.rs | 62 +++++++++++++++++-- .../parser/expr/snapshots/error_basic.snap | 3 +- .../parser/expr/snapshots/error_sugar.snap | 3 +- .../src/parser/expr/snapshots/fail_expr.snap | 3 +- .../if_soft_cast_discard_assign.snap | 6 +- .../if_soft_cast_not_var_condition.snap | 6 +- .../src/parser/expr/snapshots/todo_basic.snap | 3 +- .../src/parser/expr/snapshots/todo_empty.snap | 3 +- .../src/parser/expr/snapshots/todo_expr.snap | 3 +- .../src/parser/expr/snapshots/todo_sugar.snap | 3 +- .../expr/snapshots/trace_bytearray.snap | 17 +++++ .../src/parser/expr/snapshots/trace_expr.snap | 3 +- .../expr/snapshots/trace_expr_todo.snap | 8 ++- .../parser/expr/snapshots/trace_labelled.snap | 30 +++++++++ .../parser/expr/snapshots/trace_string.snap | 17 +++++ .../parser/expr/snapshots/trace_variadic.snap | 34 ++++++++++ .../snapshots/when_clause_double_todo.snap | 6 +- .../expr/when/snapshots/when_clause_todo.snap | 3 +- crates/aiken-lang/src/tests/format.rs | 15 +++++ .../snapshots/format_variadic_trace.snap | 11 ++++ crates/aiken-lang/src/tipo/expr.rs | 6 +- 25 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/trace_bytearray.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/trace_labelled.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/trace_string.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/trace_variadic.snap create mode 100644 crates/aiken-lang/src/tests/snapshots/format_variadic_trace.snap diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 3571a412..bc1505c4 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -548,7 +548,8 @@ pub enum UntypedExpr { kind: TraceKind, location: Span, then: Box, - text: Box, + label: Box, + arguments: Vec, }, TraceIfFalse { @@ -1134,10 +1135,11 @@ impl UntypedExpr { location, kind: TraceKind::Todo, then: Box::new(UntypedExpr::ErrorTerm { location }), - text: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { + label: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { location, value: DEFAULT_TODO_STR.to_string(), })), + arguments: Vec::new(), } } @@ -1147,7 +1149,8 @@ impl UntypedExpr { location, kind: TraceKind::Error, then: Box::new(UntypedExpr::ErrorTerm { location }), - text: Box::new(reason), + label: Box::new(reason), + arguments: Vec::new(), } } else { UntypedExpr::ErrorTerm { location } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 4bcded89..debb1d5c 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -969,8 +969,12 @@ impl<'comments> Formatter<'comments> { } => self.assignment(patterns, value, *kind), UntypedExpr::Trace { - kind, text, then, .. - } => self.trace(kind, text, then), + kind, + label, + then, + arguments, + .. + } => self.trace(kind, label, arguments, then), UntypedExpr::When { subject, clauses, .. @@ -1037,26 +1041,34 @@ impl<'comments> Formatter<'comments> { pub fn trace<'a>( &mut self, kind: &'a TraceKind, - text: &'a UntypedExpr, + label: &'a UntypedExpr, + arguments: &'a [UntypedExpr], then: &'a UntypedExpr, ) -> Document<'a> { - let (keyword, default_text) = match kind { + let (keyword, default_label) = match kind { TraceKind::Trace => ("trace", None), TraceKind::Error => ("fail", 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() => { + let mut body = match label { + UntypedExpr::String { value, .. } if Some(value) == default_label.as_ref() => { keyword.to_doc() } _ => keyword .to_doc() .append(" ") - .append(self.wrap_expr(text)) + .append(self.wrap_expr(label)) .group(), }; + for (ix, arg) in arguments.iter().enumerate() { + body = body + .append(if ix == 0 { ": " } else { ", " }) + .append(self.wrap_expr(arg)) + .group(); + } + match kind { TraceKind::Error | TraceKind::Todo => body, TraceKind::Trace => body @@ -1095,7 +1107,6 @@ impl<'comments> Formatter<'comments> { if args.is_empty() && spread_location.is_some() { if is_record { name.append(" { .. }") - // TODO: not possible } else { name.append("(..)") } diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap index 036ca2be..4f772cf6 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap @@ -11,10 +11,11 @@ Fn( then: ErrorTerm { location: 0..15, }, - text: String { + label: String { location: 0..15, value: "aiken::todo", }, + arguments: [], }, doc: None, location: 0..12, diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap index 2e90a1ed..c46c2045 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap @@ -11,10 +11,11 @@ Fn( then: ErrorTerm { location: 0..11, }, - text: String { + label: String { location: 0..11, value: "aiken::todo", }, + arguments: [], }, doc: None, location: 0..8, diff --git a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs index 618daaae..be7c7d34 100644 --- a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +++ b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs @@ -28,13 +28,25 @@ pub fn parser<'a>( .map_with_span(UntypedExpr::fail), just(Token::Trace) .ignore_then(choice((string::hybrid(), expression.clone()))) + .then( + just(Token::Colon) + .ignore_then( + choice((string::hybrid(), expression.clone())) + .separated_by(just(Token::Comma)), + ) + .or_not() + .map(|opt| opt.unwrap_or_default()), + ) .then(sequence.clone().or_not()) - .map_with_span(|(text, then_), span| UntypedExpr::Trace { - kind: TraceKind::Trace, - location: span, - then: Box::new(then_.unwrap_or_else(|| UntypedExpr::todo(None, span))), - text: Box::new(text), - }), + .map_with_span( + |((label, arguments), continuation), span| UntypedExpr::Trace { + kind: TraceKind::Trace, + location: span, + then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::todo(None, span))), + label: Box::new(label), + arguments, + }, + ), )) } @@ -114,6 +126,26 @@ mod tests { ); } + #[test] + fn trace_string() { + assert_expr!( + r#" + trace @"foo" + a + "# + ); + } + + #[test] + fn trace_bytearray() { + assert_expr!( + r#" + trace "foo" + a + "# + ); + } + #[test] fn trace_expr() { assert_expr!( @@ -132,4 +164,22 @@ mod tests { "# ); } + + #[test] + fn trace_labelled() { + assert_expr!( + r#" + trace foo: "bar" + "# + ); + } + + #[test] + fn trace_variadic() { + assert_expr!( + r#" + trace "foo": @"bar", baz + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/error_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/error_basic.snap index 2a1d8c0d..75fca4bd 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/error_basic.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/error_basic.snap @@ -8,8 +8,9 @@ Trace { then: ErrorTerm { location: 0..11, }, - text: String { + label: String { location: 5..11, value: "foo", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/error_sugar.snap b/crates/aiken-lang/src/parser/expr/snapshots/error_sugar.snap index 1cb7d47e..c106702a 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/error_sugar.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/error_sugar.snap @@ -8,8 +8,9 @@ Trace { then: ErrorTerm { location: 0..10, }, - text: String { + label: String { location: 5..10, value: "foo", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/fail_expr.snap b/crates/aiken-lang/src/parser/expr/snapshots/fail_expr.snap index 2e7d8190..64277982 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/fail_expr.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/fail_expr.snap @@ -8,7 +8,7 @@ Trace { then: ErrorTerm { location: 0..67, }, - text: Call { + label: Call { arguments: [ CallArg { label: None, @@ -51,4 +51,5 @@ Trace { }, location: 5..67, }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_discard_assign.snap b/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_discard_assign.snap index aab73ee8..a4f0bdad 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_discard_assign.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_discard_assign.snap @@ -20,10 +20,11 @@ If { then: ErrorTerm { location: 20..24, }, - text: String { + label: String { location: 20..24, value: "aiken::todo", }, + arguments: [], }, is: Some( AssignmentPattern { @@ -51,9 +52,10 @@ If { then: ErrorTerm { location: 36..40, }, - text: String { + label: String { location: 36..40, value: "aiken::todo", }, + arguments: [], }, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_not_var_condition.snap b/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_not_var_condition.snap index e8fd4f35..0bd080ce 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_not_var_condition.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/if_soft_cast_not_var_condition.snap @@ -20,10 +20,11 @@ If { then: ErrorTerm { location: 31..35, }, - text: String { + label: String { location: 31..35, value: "aiken::todo", }, + arguments: [], }, is: Some( AssignmentPattern { @@ -68,9 +69,10 @@ If { then: ErrorTerm { location: 47..51, }, - text: String { + label: String { location: 47..51, value: "aiken::todo", }, + arguments: [], }, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/todo_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/todo_basic.snap index 9470a1a8..4b5a651e 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/todo_basic.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/todo_basic.snap @@ -8,8 +8,9 @@ Trace { then: ErrorTerm { location: 0..11, }, - text: String { + label: String { location: 5..11, value: "foo", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/todo_empty.snap b/crates/aiken-lang/src/parser/expr/snapshots/todo_empty.snap index bc1fc0d1..16d45e79 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/todo_empty.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/todo_empty.snap @@ -8,8 +8,9 @@ Trace { then: ErrorTerm { location: 0..4, }, - text: String { + label: String { location: 0..4, value: "aiken::todo", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/todo_expr.snap b/crates/aiken-lang/src/parser/expr/snapshots/todo_expr.snap index 9767c66a..3dd7a245 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/todo_expr.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/todo_expr.snap @@ -8,7 +8,7 @@ Trace { then: ErrorTerm { location: 0..32, }, - text: Call { + label: Call { arguments: [ CallArg { label: None, @@ -49,4 +49,5 @@ Trace { }, location: 5..32, }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/todo_sugar.snap b/crates/aiken-lang/src/parser/expr/snapshots/todo_sugar.snap index 6e9bf4c0..ff88606c 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/todo_sugar.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/todo_sugar.snap @@ -8,8 +8,9 @@ Trace { then: ErrorTerm { location: 0..10, }, - text: String { + label: String { location: 5..10, value: "foo", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_bytearray.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_bytearray.snap new file mode 100644 index 00000000..63c4af22 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_bytearray.snap @@ -0,0 +1,17 @@ +--- +source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +description: "Code:\n\ntrace \"foo\"\na\n" +--- +Trace { + kind: Trace, + location: 0..13, + then: Var { + location: 12..13, + name: "a", + }, + label: String { + location: 6..11, + value: "foo", + }, + arguments: [], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_expr.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_expr.snap index 783fa6e1..b64f649e 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/trace_expr.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_expr.snap @@ -9,7 +9,7 @@ Trace { location: 34..35, name: "a", }, - text: Call { + label: Call { arguments: [ CallArg { label: None, @@ -50,4 +50,5 @@ Trace { }, location: 6..33, }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_expr_todo.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_expr_todo.snap index c0ecd7ac..ae65d4df 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/trace_expr_todo.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_expr_todo.snap @@ -1,6 +1,6 @@ --- source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs -description: "Code:\n\ntrace some_var \n" +description: "Code:\n\ntrace some_var\n" --- Trace { kind: Trace, @@ -11,13 +11,15 @@ Trace { then: ErrorTerm { location: 0..14, }, - text: String { + label: String { location: 0..14, value: "aiken::todo", }, + arguments: [], }, - text: Var { + label: Var { location: 6..14, name: "some_var", }, + arguments: [], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_labelled.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_labelled.snap new file mode 100644 index 00000000..3af8fa07 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_labelled.snap @@ -0,0 +1,30 @@ +--- +source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +description: "Code:\n\ntrace foo: \"bar\"\n" +--- +Trace { + kind: Trace, + location: 0..16, + then: Trace { + kind: Todo, + location: 0..16, + then: ErrorTerm { + location: 0..16, + }, + label: String { + location: 0..16, + value: "aiken::todo", + }, + arguments: [], + }, + label: Var { + location: 6..9, + name: "foo", + }, + arguments: [ + String { + location: 11..16, + value: "bar", + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_string.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_string.snap new file mode 100644 index 00000000..44459623 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_string.snap @@ -0,0 +1,17 @@ +--- +source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +description: "Code:\n\ntrace @\"foo\"\na\n" +--- +Trace { + kind: Trace, + location: 0..14, + then: Var { + location: 13..14, + name: "a", + }, + label: String { + location: 6..12, + value: "foo", + }, + arguments: [], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/trace_variadic.snap b/crates/aiken-lang/src/parser/expr/snapshots/trace_variadic.snap new file mode 100644 index 00000000..71ee7150 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/trace_variadic.snap @@ -0,0 +1,34 @@ +--- +source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +description: "Code:\n\ntrace \"foo\": @\"bar\", baz\n" +--- +Trace { + kind: Trace, + location: 0..24, + then: Trace { + kind: Todo, + location: 0..24, + then: ErrorTerm { + location: 0..24, + }, + label: String { + location: 0..24, + value: "aiken::todo", + }, + arguments: [], + }, + label: String { + location: 6..11, + value: "foo", + }, + arguments: [ + String { + location: 13..19, + value: "bar", + }, + Var { + location: 21..24, + name: "baz", + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap index 18642888..7fe8c880 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap @@ -32,10 +32,11 @@ When { then: ErrorTerm { location: 28..32, }, - text: String { + label: String { location: 28..32, value: "aiken::todo", }, + arguments: [], }, }, UntypedClause { @@ -61,10 +62,11 @@ When { then: ErrorTerm { location: 47..51, }, - text: String { + label: String { location: 47..51, value: "aiken::todo", }, + arguments: [], }, }, ], diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap index bd0197bc..a437e880 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap @@ -54,10 +54,11 @@ When { then: ErrorTerm { location: 47..68, }, - text: String { + label: String { location: 52..68, value: "unimplemented", }, + arguments: [], }, }, ], diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 5fed0458..ae567e67 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -985,3 +985,18 @@ fn format_validator_pattern() { "# ); } + +#[test] +fn format_variadic_trace() { + assert_format!( + r#" + fn foo() { + trace @"foo": @"bar" + trace "foo": "bar" + trace @"foo": "bar", @"baz" + trace bar: @"baz" + Void + } + "# + ); +} diff --git a/crates/aiken-lang/src/tests/snapshots/format_variadic_trace.snap b/crates/aiken-lang/src/tests/snapshots/format_variadic_trace.snap new file mode 100644 index 00000000..6d902e24 --- /dev/null +++ b/crates/aiken-lang/src/tests/snapshots/format_variadic_trace.snap @@ -0,0 +1,11 @@ +--- +source: crates/aiken-lang/src/tests/format.rs +description: "Code:\n\nfn foo() {\n trace @\"foo\": @\"bar\"\n trace \"foo\": \"bar\"\n trace @\"foo\": \"bar\", @\"baz\"\n trace bar: @\"baz\"\n Void\n}\n" +--- +fn foo() { + trace @"foo": @"bar" + trace @"foo": @"bar" + trace @"foo": @"bar", @"baz" + trace bar: @"baz" + Void +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 102afa14..1480974d 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -513,12 +513,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.infer_assignment(pattern, *value, kind, &annotation, location) } + // TODO: Trace.arguments UntypedExpr::Trace { location, then, - text, + label, kind, - } => self.infer_trace(kind, *then, location, *text), + .. + } => self.infer_trace(kind, *then, location, *label), UntypedExpr::When { location, From 754ed0740859228b425b8e322ac499d417eb9e03 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 18 Jul 2024 10:02:23 +0200 Subject: [PATCH 02/10] Type-check variadic traces & desugarize them. --- crates/aiken-lang/src/tipo/expr.rs | 82 +++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 1480974d..cd6cc184 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -19,12 +19,14 @@ use crate::{ UntypedRecordUpdateArg, }, builtins::{ - bool, byte_array, function, g1_element, g2_element, int, list, pair, string, tuple, void, + bool, byte_array, from_default_function, function, g1_element, g2_element, int, list, pair, + string, tuple, void, BUILTIN, }, expr::{FnStyle, TypedExpr, UntypedExpr}, format, line_numbers::LineNumbers, - tipo::{fields::FieldMap, PatternConstructor, TypeVar}, + tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar}, + IdGenerator, }; use std::{ cmp::Ordering, @@ -513,14 +515,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.infer_assignment(pattern, *value, kind, &annotation, location) } - // TODO: Trace.arguments UntypedExpr::Trace { location, then, label, + arguments, kind, .. - } => self.infer_trace(kind, *then, location, *label), + } => self.infer_trace(kind, *then, location, *label, arguments), UntypedExpr::When { location, @@ -2410,10 +2412,33 @@ impl<'a, 'b> ExprTyper<'a, 'b> { kind: TraceKind, then: UntypedExpr, location: Span, - text: UntypedExpr, + label: UntypedExpr, + arguments: Vec, ) -> Result { - let text = self.infer(text)?; - self.unify(string(), text.tipo(), text.location(), false)?; + let label = self.infer(label)?; + self.unify(string(), label.tipo(), label.location(), false)?; + + let typed_arguments = arguments + .into_iter() + .map(|arg| { + let arg = self.infer(arg)?; + self.unify(string(), arg.tipo(), arg.location(), false)?; + Ok(arg) + }) + .collect::, Error>>()?; + + let text = if typed_arguments.is_empty() { + label + } else { + let delimiter = |ix| TypedExpr::String { + location: Span::empty(), + tipo: string(), + value: if ix == 0 { ": " } else { ", " }.to_string(), + }; + typed_arguments.into_iter().enumerate().fold(label, |text, (ix, arg)| { + append_string_expr(append_string_expr(text, delimiter(ix)), arg) + }) + }; let then = self.infer(then)?; let tipo = then.tipo(); @@ -2786,3 +2811,46 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc, location: Span) -> R } } } + +pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { + let value_constructor = from_default_function(DefaultFunction::AppendString, &IdGenerator::new()); + let append_string = TypedExpr::ModuleSelect { + location: Span::empty(), + tipo: value_constructor.tipo, + label: DefaultFunction::AppendString.aiken_name(), + module_name: BUILTIN.to_string(), + module_alias: BUILTIN.to_string(), + // NOTE: The IdGenerator is unused here, as it's only necessary for generic builtin + // functions such as if_then_else or head_list. However, if such functions were needed, + // passing a brand new IdGenerator here would be WRONG and cause issues down the line. + // + // So this is merely a small work-around for convenience. The proper way here would be to + // pull the function definition for append_string from the pre-registered builtins + // functions somewhere in the environment. + constructor: value_constructor + .variant + .to_module_value_constructor( + string(), + BUILTIN, + &DefaultFunction::AppendString.aiken_name(), + ), + }; + + TypedExpr::Call { + location: Span::empty(), + tipo: string(), + fun: Box::new(append_string.clone()), + args: vec![ + CallArg { + label: None, + location: left.location(), + value: left, + }, + CallArg { + label: None, + location: right.location(), + value: right, + }, + ], + } +} From beb5ac46436a4046e291c8fc98adf4b6f6b132d7 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 18 Jul 2024 16:07:31 +0200 Subject: [PATCH 03/10] Add 'diagnostic' to the prelude, as well as companion functions. This is not fully satisfactory as it pollutes a bit the prelude. Ideally, those functions should only be visible and usable by the underlying trace code. But for now, we'll just go with it. --- crates/aiken-lang/src/builtins.rs | 326 +++++++++++++++++- crates/aiken-lang/src/lib.rs | 31 ++ crates/aiken-lang/src/tipo.rs | 4 +- crates/aiken-lang/src/tipo/expr.rs | 28 +- ...lueprint__validator__tests__free_vars.snap | 2 +- crates/aiken-project/src/lib.rs | 2 +- ...export__tests__cannot_export_generics.snap | 2 +- crates/aiken-project/src/test_framework.rs | 2 +- crates/aiken-project/src/tests/mod.rs | 2 +- 9 files changed, 378 insertions(+), 21 deletions(-) diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index 0975965c..3d9858cf 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1,4 +1,5 @@ use crate::{ + aiken_fn, ast::{ Annotation, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, OnTestFailure, Span, TypedArg, TypedDataType, TypedFunction, UnOp, @@ -268,6 +269,102 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { ), ); + // enumerate + let enumerate_a = generic_var(id_gen.next()); + let enumerate_b = generic_var(id_gen.next()); + prelude.values.insert( + "enumerate".to_string(), + ValueConstructor::public( + function( + vec![ + list(enumerate_a.clone()), + enumerate_b.clone(), + function( + vec![enumerate_a.clone(), enumerate_b.clone()], + enumerate_b.clone(), + ), + function( + vec![enumerate_a.clone(), enumerate_b.clone()], + enumerate_b.clone(), + ), + ], + enumerate_b, + ), + ValueConstructorVariant::ModuleFn { + name: "enumerate".to_string(), + field_map: None, + module: "".to_string(), + arity: 4, + location: Span::empty(), + builtin: None, + }, + ), + ); + + // encode_base16 + prelude.values.insert( + "encode_base16".to_string(), + ValueConstructor::public( + function(vec![byte_array(), int(), byte_array()], byte_array()), + ValueConstructorVariant::ModuleFn { + name: "encode_base16".to_string(), + field_map: None, + module: "".to_string(), + arity: 3, + location: Span::empty(), + builtin: None, + }, + ), + ); + + // from_int + prelude.values.insert( + "from_int".to_string(), + ValueConstructor::public( + function(vec![int(), byte_array()], byte_array()), + ValueConstructorVariant::ModuleFn { + name: "from_int".to_string(), + field_map: None, + module: "".to_string(), + arity: 2, + location: Span::empty(), + builtin: None, + }, + ), + ); + + // do_from_int + prelude.values.insert( + "do_from_int".to_string(), + ValueConstructor::public( + function(vec![int(), byte_array()], byte_array()), + ValueConstructorVariant::ModuleFn { + name: "do_from_int".to_string(), + field_map: None, + module: "".to_string(), + arity: 2, + location: Span::empty(), + builtin: None, + }, + ), + ); + + // diagnostic + prelude.values.insert( + "diagnostic".to_string(), + ValueConstructor::public( + function(vec![data(), byte_array()], byte_array()), + ValueConstructorVariant::ModuleFn { + name: "diagnostic".to_string(), + field_map: None, + module: "".to_string(), + arity: 2, + location: Span::empty(), + builtin: None, + }, + ), + ); + // always let always_a_var = generic_var(id_gen.next()); let always_b_var = generic_var(id_gen.next()); @@ -919,7 +1016,10 @@ pub fn from_default_function(builtin: DefaultFunction, id_gen: &IdGenerator) -> ) } -pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap { +pub fn prelude_functions( + id_gen: &IdGenerator, + module_types: &HashMap, +) -> IndexMap { let mut functions = IndexMap::new(); // /// Negate the argument. Useful for map/fold and pipelines. @@ -1239,6 +1339,230 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap, + zero: b, + with: fn(a, b) -> b, + last: fn(a, b) -> b, + ) -> b { + when self is { + [] -> zero + [x] -> last(x, zero) + [x, ..xs] -> with(x, enumerate(xs, zero, with, last)) + } + } + "# + ), + ); + + functions.insert( + FunctionAccessKey { + module_name: "".to_string(), + function_name: "encode_base16".to_string(), + }, + aiken_fn!( + &module_types, + &id_gen, + r#" + use aiken/builtin + + fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray { + if ix < 0 { + builder + } else { + let byte = builtin.index_bytearray(bytes, ix) + let msb = byte / 16 + let lsb = byte % 16 + let builder = + builtin.cons_bytearray( + msb + if msb < 10 { + 48 + } else { + 55 + }, + builtin.cons_bytearray( + lsb + if lsb < 10 { + 48 + } else { + 55 + }, + builder, + ), + ) + encode_base16(bytes, ix - 1, builder) + } + } + "# + ), + ); + + functions.insert( + FunctionAccessKey { + module_name: "".to_string(), + function_name: "do_from_int".to_string(), + }, + aiken_fn!( + &module_types, + &id_gen, + r#" + use aiken/builtin + + fn do_from_int(i: Int, digits: ByteArray) -> ByteArray { + if i <= 0 { + digits + } else { + do_from_int( + builtin.quotient_integer(i, 10), + builtin.cons_bytearray(builtin.remainder_integer(i, 10) + 48, digits), + ) + } + } + "# + ), + ); + + functions.insert( + FunctionAccessKey { + module_name: "".to_string(), + function_name: "from_int".to_string(), + }, + aiken_fn!( + &module_types, + &id_gen, + r#" + use aiken/builtin + + /// Encode an integer into UTF-8. + fn from_int(i: Int, digits: ByteArray) -> ByteArray { + if i == 0 { + builtin.append_bytearray(#"30", digits) + } else if i < 0 { + builtin.append_bytearray(#"2d", from_int(-i, digits)) + } else { + do_from_int( + builtin.quotient_integer(i, 10), + builtin.cons_bytearray(builtin.remainder_integer(i, 10) + 48, digits), + ) + } + } + "# + ), + ); + + functions.insert( + FunctionAccessKey { + module_name: "".to_string(), + function_name: "diagnostic".to_string(), + }, + aiken_fn!( + &module_types, + &id_gen, + r#" + use aiken/builtin + + fn diagnostic(self: Data, builder: ByteArray) -> ByteArray { + builtin.choose_data( + self, + { + let Pair(constr, fields) = builtin.un_constr_data(self) + + let builder = + when fields is { + [] -> builtin.append_bytearray(#"5b5d29", builder) + _ -> { + let bytes = + enumerate( + fields, + builtin.append_bytearray(#"5d29", builder), + fn(e: Data, st: ByteArray) { + diagnostic(e, builtin.append_bytearray(#"2c20", st)) + }, + fn(e: Data, st: ByteArray) { diagnostic(e, st) }, + ) + builtin.append_bytearray(#"5b5f20", bytes) + } + } + + let constr_tag = + if constr < 7 { + 121 + constr + } else if constr < 128 { + 1280 + constr - 7 + } else { + fail @"What are you doing? No I mean, seriously." + } + + builder + |> builtin.append_bytearray(#"28", _) + |> from_int(constr_tag, _) + }, + { + let elems = builtin.un_map_data(self) + when elems is { + [] -> builtin.append_bytearray(#"7b7d", builder) + _ -> { + let bytes = + enumerate( + elems, + builtin.append_bytearray(#"207d", builder), + fn(e: Pair, st: ByteArray) { + let value = diagnostic(e.2nd, builtin.append_bytearray(#"2c20", st)) + diagnostic(e.1st, builtin.append_bytearray(#"3a20", value)) + }, + fn(e: Pair, st: ByteArray) { + let value = diagnostic(e.2nd, st) + diagnostic(e.1st, builtin.append_bytearray(#"3a20", value)) + }, + ) + builtin.append_bytearray(#"7b5f20", bytes) + } + } + }, + { + let elems = builtin.un_list_data(self) + when elems is { + [] -> builtin.append_bytearray(#"5b5d", builder) + _ -> { + let bytes = + enumerate( + elems, + builtin.append_bytearray(#"5d", builder), + fn(e: Data, st: ByteArray) { + diagnostic(e, builtin.append_bytearray(#"2c20", st)) + }, + fn(e: Data, st: ByteArray) { diagnostic(e, st) }, + ) + builtin.append_bytearray(#"5b5f20", bytes) + } + } + }, + self + |> builtin.un_i_data + |> from_int(builder), + { + let bytes = builtin.un_b_data(self) + bytes + |> encode_base16( + builtin.length_of_bytearray(bytes) - 1, + builtin.append_bytearray(#"27", builder), + ) + |> builtin.append_bytearray(#"6827", _) + }, + ) + } + "# + ), + ); + functions } diff --git a/crates/aiken-lang/src/lib.rs b/crates/aiken-lang/src/lib.rs index 5f52ceb7..2910c3f3 100644 --- a/crates/aiken-lang/src/lib.rs +++ b/crates/aiken-lang/src/lib.rs @@ -31,5 +31,36 @@ impl IdGenerator { } } +#[macro_export] +macro_rules! aiken_fn { + ($module_types:expr, $id_gen:expr, $src:expr) => {{ + let (untyped_module, _) = $crate::parser::module($src, $crate::ast::ModuleKind::Lib) + .expect("failed to parse module."); + + let module_name = ""; + + let mut warnings = vec![]; + + let typed_module = untyped_module + .infer( + $id_gen, + $crate::ast::ModuleKind::Lib, + module_name, + $module_types, + $crate::ast::Tracing::silent(), + &mut warnings, + ) + .unwrap(); + + if let Some($crate::ast::Definition::Fn(typed_fn)) = + typed_module.definitions.into_iter().last() + { + typed_fn + } else { + unreachable!() + } + }}; +} + #[cfg(test)] mod tests; diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index f29257c3..7b8274cf 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -12,10 +12,10 @@ use itertools::Itertools; use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc}; use uplc::{ast::Type as UplcType, builtins::DefaultFunction}; -mod environment; +pub(crate) mod environment; pub mod error; mod exhaustive; -mod expr; +pub(crate) mod expr; pub mod fields; mod hydrator; mod infer; diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index cd6cc184..697d676b 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -96,7 +96,7 @@ pub(crate) fn infer_function( let preregistered_fn = environment .get_variable(name) - .expect("Could not find preregistered type for function"); + .unwrap_or_else(|| panic!("Could not find preregistered type for function: {name}")); let field_map = preregistered_fn.field_map().cloned(); @@ -1797,7 +1797,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } - fn infer_fn( + pub fn infer_fn( &mut self, args: Vec, expected_args: &[Rc], @@ -2435,9 +2435,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { tipo: string(), value: if ix == 0 { ": " } else { ", " }.to_string(), }; - typed_arguments.into_iter().enumerate().fold(label, |text, (ix, arg)| { - append_string_expr(append_string_expr(text, delimiter(ix)), arg) - }) + typed_arguments + .into_iter() + .enumerate() + .fold(label, |text, (ix, arg)| { + append_string_expr(append_string_expr(text, delimiter(ix)), arg) + }) }; let then = self.infer(then)?; @@ -2813,7 +2816,8 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc, location: Span) -> R } pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { - let value_constructor = from_default_function(DefaultFunction::AppendString, &IdGenerator::new()); + let value_constructor = + from_default_function(DefaultFunction::AppendString, &IdGenerator::new()); let append_string = TypedExpr::ModuleSelect { location: Span::empty(), tipo: value_constructor.tipo, @@ -2827,13 +2831,11 @@ pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { // So this is merely a small work-around for convenience. The proper way here would be to // pull the function definition for append_string from the pre-registered builtins // functions somewhere in the environment. - constructor: value_constructor - .variant - .to_module_value_constructor( - string(), - BUILTIN, - &DefaultFunction::AppendString.aiken_name(), - ), + constructor: value_constructor.variant.to_module_value_constructor( + string(), + BUILTIN, + &DefaultFunction::AppendString.aiken_name(), + ), }; TypedExpr::Call { diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap index af354a21..8f64d017 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap @@ -9,7 +9,7 @@ Schema { Var { tipo: RefCell { value: Generic { - id: 35, + id: 64, }, }, alias: None, diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 93196f5f..6efab615 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -130,7 +130,7 @@ where module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); - let functions = builtins::prelude_functions(&id_gen); + let functions = builtins::prelude_functions(&id_gen, &module_types); let data_types = builtins::prelude_data_types(&id_gen); diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap index 69be98f0..32ab8881 100644 --- a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap @@ -9,7 +9,7 @@ Schema { Var { tipo: RefCell { value: Generic { - id: 35, + id: 64, }, }, alias: None, diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index a9ccebb9..15d1f57d 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -1328,7 +1328,7 @@ mod test { .last() .expect("No test found in declared src?"); - let mut functions = builtins::prelude_functions(&id_gen); + let mut functions = builtins::prelude_functions(&id_gen, &module_types); let mut data_types = builtins::prelude_data_types(&id_gen); ast.register_definitions(&mut functions, &mut data_types); diff --git a/crates/aiken-project/src/tests/mod.rs b/crates/aiken-project/src/tests/mod.rs index 94ddf664..d0967542 100644 --- a/crates/aiken-project/src/tests/mod.rs +++ b/crates/aiken-project/src/tests/mod.rs @@ -46,7 +46,7 @@ impl TestProject { module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); - let functions = builtins::prelude_functions(&id_gen); + let functions = builtins::prelude_functions(&id_gen, &module_types); let data_types = builtins::prelude_data_types(&id_gen); TestProject { From f8236817fe250e95b191839b84513dc3757beded Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 18 Jul 2024 17:19:12 +0200 Subject: [PATCH 04/10] Allow serialisable (Data-able) arguments to trace Somehow, we have to patch some function in gen_uplc because of the module name. I have to look further into this because it isn't normal. --- crates/aiken-lang/src/gen_uplc.rs | 7 +- crates/aiken-lang/src/tipo/expr.rs | 122 ++++++++++++++++++++++++----- 2 files changed, 108 insertions(+), 21 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index f80b07e6..df2a75f0 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -43,7 +43,6 @@ use itertools::Itertools; use petgraph::{algo, Graph}; use std::{collections::HashMap, rc::Rc}; use tree::Fields; - use uplc::{ ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType}, builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST}, @@ -749,7 +748,11 @@ impl<'a> CodeGenerator<'a> { } ModuleValueConstructor::Fn { name, module, .. } => { let func = self.functions.get(&FunctionAccessKey { - module_name: module_name.clone(), + module_name: if module_name == "aiken" { + "".to_string() + } else { + module_name.clone() + }, function_name: name.clone(), }); diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 697d676b..dcc8b917 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -19,8 +19,8 @@ use crate::{ UntypedRecordUpdateArg, }, builtins::{ - bool, byte_array, from_default_function, function, g1_element, g2_element, int, list, pair, - string, tuple, void, BUILTIN, + bool, byte_array, data, from_default_function, function, g1_element, g2_element, int, list, + pair, string, tuple, void, BUILTIN, PRELUDE, }, expr::{FnStyle, TypedExpr, UntypedExpr}, format, @@ -2407,6 +2407,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> { TypedExpr::ErrorTerm { location, tipo } } + fn infer_trace_arg(&mut self, arg: UntypedExpr) -> Result { + let typed_arg = self.infer(arg)?; + match self.unify(string(), typed_arg.tipo(), typed_arg.location(), false) { + Err(_) => { + self.unify(data(), typed_arg.tipo(), typed_arg.location(), true)?; + Ok(diagnose_expr(typed_arg)) + } + Ok(()) => Ok(typed_arg), + } + } + fn infer_trace( &mut self, kind: TraceKind, @@ -2415,16 +2426,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> { label: UntypedExpr, arguments: Vec, ) -> Result { - let label = self.infer(label)?; - self.unify(string(), label.tipo(), label.location(), false)?; + let label = self.infer_trace_arg(label)?; let typed_arguments = arguments .into_iter() - .map(|arg| { - let arg = self.infer(arg)?; - self.unify(string(), arg.tipo(), arg.location(), false)?; - Ok(arg) - }) + .map(|arg| self.infer_trace_arg(arg)) .collect::, Error>>()?; let text = if typed_arguments.is_empty() { @@ -2815,24 +2821,102 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc, location: Span) -> R } } -pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { +fn diagnose_expr(expr: TypedExpr) -> TypedExpr { + // NOTE: The IdGenerator is unused. See similar note in 'append_string_expr' + let decode_utf8_constructor = + from_default_function(DefaultFunction::DecodeUtf8, &IdGenerator::new()); + + let decode_utf8 = TypedExpr::ModuleSelect { + location: Span::empty(), + tipo: decode_utf8_constructor.tipo.clone(), + label: DefaultFunction::DecodeUtf8.aiken_name(), + module_name: BUILTIN.to_string(), + module_alias: BUILTIN.to_string(), + constructor: decode_utf8_constructor.variant.to_module_value_constructor( + decode_utf8_constructor.tipo, + BUILTIN, + &DefaultFunction::AppendString.aiken_name(), + ), + }; + + let diagnostic_constructor = ValueConstructor::public( + function(vec![data(), byte_array()], byte_array()), + ValueConstructorVariant::ModuleFn { + name: "diagnostic".to_string(), + field_map: None, + module: PRELUDE.to_string(), + arity: 2, + location: Span::empty(), + builtin: None, + }, + ); + + let diagnostic = TypedExpr::ModuleSelect { + location: Span::empty(), + tipo: diagnostic_constructor.tipo.clone(), + label: "diagnostic".to_string(), + module_name: PRELUDE.to_string(), + module_alias: "".to_string(), + constructor: diagnostic_constructor.variant.to_module_value_constructor( + diagnostic_constructor.tipo, + "", + "diagnostic", + ), + }; + + let location = expr.location(); + + TypedExpr::Call { + tipo: string(), + fun: Box::new(decode_utf8.clone()), + args: vec![CallArg { + label: None, + location: expr.location(), + value: TypedExpr::Call { + tipo: string(), + fun: Box::new(diagnostic.clone()), + args: vec![ + CallArg { + label: None, + value: expr, + location, + }, + CallArg { + label: None, + location, + value: TypedExpr::ByteArray { + tipo: byte_array(), + bytes: vec![], + location, + }, + }, + ], + location, + }, + }], + location, + } +} + +fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { + // NOTE: The IdGenerator is unused here, as it's only necessary for generic builtin + // functions such as if_then_else or head_list. However, if such functions were needed, + // passing a brand new IdGenerator here would be WRONG and cause issues down the line. + // + // So this is merely a small work-around for convenience. The proper way here would be to + // pull the function definition for append_string from the pre-registered builtins + // functions somewhere in the environment. let value_constructor = from_default_function(DefaultFunction::AppendString, &IdGenerator::new()); + let append_string = TypedExpr::ModuleSelect { location: Span::empty(), - tipo: value_constructor.tipo, + tipo: value_constructor.tipo.clone(), label: DefaultFunction::AppendString.aiken_name(), module_name: BUILTIN.to_string(), module_alias: BUILTIN.to_string(), - // NOTE: The IdGenerator is unused here, as it's only necessary for generic builtin - // functions such as if_then_else or head_list. However, if such functions were needed, - // passing a brand new IdGenerator here would be WRONG and cause issues down the line. - // - // So this is merely a small work-around for convenience. The proper way here would be to - // pull the function definition for append_string from the pre-registered builtins - // functions somewhere in the environment. constructor: value_constructor.variant.to_module_value_constructor( - string(), + value_constructor.tipo, BUILTIN, &DefaultFunction::AppendString.aiken_name(), ), From 5afcc9b0c1412ac8ac98dbcb9abca4d3136de1cf Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 09:22:19 +0200 Subject: [PATCH 05/10] Remove unnecessary code_gen patch. This is a little weird but, prelude functions are handled slightly differently. --- crates/aiken-lang/src/gen_uplc.rs | 6 +---- crates/aiken-lang/src/tipo/expr.rs | 42 ++++++++++++------------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index df2a75f0..82f1cbae 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -748,11 +748,7 @@ impl<'a> CodeGenerator<'a> { } ModuleValueConstructor::Fn { name, module, .. } => { let func = self.functions.get(&FunctionAccessKey { - module_name: if module_name == "aiken" { - "".to_string() - } else { - module_name.clone() - }, + module_name: module_name.clone(), function_name: name.clone(), }); diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index dcc8b917..e0d84d68 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -20,7 +20,7 @@ use crate::{ }, builtins::{ bool, byte_array, data, from_default_function, function, g1_element, g2_element, int, list, - pair, string, tuple, void, BUILTIN, PRELUDE, + pair, string, tuple, void, BUILTIN, }, expr::{FnStyle, TypedExpr, UntypedExpr}, format, @@ -2827,7 +2827,7 @@ fn diagnose_expr(expr: TypedExpr) -> TypedExpr { from_default_function(DefaultFunction::DecodeUtf8, &IdGenerator::new()); let decode_utf8 = TypedExpr::ModuleSelect { - location: Span::empty(), + location: expr.location(), tipo: decode_utf8_constructor.tipo.clone(), label: DefaultFunction::DecodeUtf8.aiken_name(), module_name: BUILTIN.to_string(), @@ -2839,29 +2839,21 @@ fn diagnose_expr(expr: TypedExpr) -> TypedExpr { ), }; - let diagnostic_constructor = ValueConstructor::public( - function(vec![data(), byte_array()], byte_array()), - ValueConstructorVariant::ModuleFn { - name: "diagnostic".to_string(), - field_map: None, - module: PRELUDE.to_string(), - arity: 2, - location: Span::empty(), - builtin: None, + let diagnostic = TypedExpr::Var { + location: expr.location(), + name: "diagnostic".to_string(), + constructor: ValueConstructor { + public: true, + tipo: function(vec![data(), byte_array()], byte_array()), + variant: ValueConstructorVariant::ModuleFn { + name: "diagnostic".to_string(), + field_map: None, + module: "".to_string(), + arity: 2, + location: Span::empty(), + builtin: None, + }, }, - ); - - let diagnostic = TypedExpr::ModuleSelect { - location: Span::empty(), - tipo: diagnostic_constructor.tipo.clone(), - label: "diagnostic".to_string(), - module_name: PRELUDE.to_string(), - module_alias: "".to_string(), - constructor: diagnostic_constructor.variant.to_module_value_constructor( - diagnostic_constructor.tipo, - "", - "diagnostic", - ), }; let location = expr.location(); @@ -2873,7 +2865,7 @@ fn diagnose_expr(expr: TypedExpr) -> TypedExpr { label: None, location: expr.location(), value: TypedExpr::Call { - tipo: string(), + tipo: byte_array(), fun: Box::new(diagnostic.clone()), args: vec![ CallArg { From a9d782e2063952b38342a1ec46d587430ecd1a45 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 09:40:05 +0200 Subject: [PATCH 06/10] re-introduce code-gen patch, but with a test. Actually, this has been a bug for a long time it seems. Calling any prelude functions using a qualified import would result in a codegen crash. Whoopsie. This is now fixed as shown by the regression test. --- crates/aiken-lang/src/gen_uplc.rs | 16 ++++++++++++++-- crates/aiken-project/src/tests/gen_uplc.rs | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 82f1cbae..707ff3e5 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -17,7 +17,7 @@ use crate::{ Span, TraceLevel, Tracing, TypedArg, TypedClause, TypedDataType, TypedFunction, TypedPattern, TypedValidator, UnOp, }, - builtins::{bool, data, int, list, void}, + builtins::{bool, data, int, list, void, PRELUDE}, expr::TypedExpr, gen_uplc::{ air::ExpectLevel, @@ -748,7 +748,19 @@ impl<'a> CodeGenerator<'a> { } ModuleValueConstructor::Fn { name, module, .. } => { let func = self.functions.get(&FunctionAccessKey { - module_name: module_name.clone(), + // NOTE: This is needed because we register prelude functions under an + // empty module name. This is to facilitate their access when used + // directly. Note that, if we weren't doing this particular + // transformation, we would need to do the other direction anyway: + // + // if module_name.is_empty() { PRELUDE.to_string() } else { module_name.clone() } + // + // So either way, we need to take care of this. + module_name: if module_name == PRELUDE { + String::new() + } else { + module_name.clone() + }, function_name: name.clone(), }); diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index 20c88855..0019836e 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -7003,3 +7003,25 @@ fn bls12_381_elements_from_data_conversion() { false, ) } + +#[test] +fn qualified_prelude_functions() { + let src = r#" + use aiken + + test foo() { + aiken.identity(True) && identity(True) + } + "#; + + let constant_true = Term::Constant(Constant::Bool(true).into()); + let constant_false = Term::Constant(Constant::Bool(false).into()); + + assert_uplc( + src, + constant_true + .clone() + .delayed_if_then_else(constant_true, constant_false), + false, + ) +} From d6fd37c80ecd2e3bfaa4b25a066e36b20c8996f9 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 10:51:47 +0200 Subject: [PATCH 07/10] Rework 'compact' mode for traces - Trace-if-false are now completely discarded in compact mode. - Only the label (i.e. first trace argument) is preserved. - When compiling with tracing _compact_, the first label MUST unify to a string. This shouldn't be an issue generally speaking and would enforce that traces follow the pattern ``` label: arg_0[, arg_1, ..., arg_n] ``` Note that what isn't obvious with these changes is that we now support what the "emit" keyword was trying to achieve; as we compile now with user-defined traces only, and in compact mode to only keep event labels in the final contract; while allowing larger payloads with verbose tracing. --- crates/aiken-lang/src/ast.rs | 4 + crates/aiken-lang/src/tests/check.rs | 42 +++++++++-- crates/aiken-lang/src/tipo/expr.rs | 107 +++++++++++---------------- crates/aiken-lang/src/tipo/infer.rs | 39 ++-------- 4 files changed, 92 insertions(+), 100 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 20e7b373..97c48267 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1957,6 +1957,10 @@ pub enum TraceLevel { } impl Tracing { + pub fn verbose() -> Self { + Tracing::All(TraceLevel::Verbose) + } + pub fn silent() -> Self { Tracing::All(TraceLevel::Silent) } diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index b157d0ce..58d971b4 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -18,6 +18,7 @@ fn check_module( ast: UntypedModule, extra: Vec<(String, UntypedModule)>, kind: ModuleKind, + tracing: Tracing, ) -> Result<(Vec, TypedModule), (Vec, Error)> { let id_gen = IdGenerator::new(); @@ -47,7 +48,7 @@ fn check_module( kind, "test/project", &module_types, - Tracing::All(TraceLevel::Verbose), + tracing, &mut warnings, ); @@ -57,20 +58,27 @@ fn check_module( } fn check(ast: UntypedModule) -> Result<(Vec, TypedModule), (Vec, Error)> { - check_module(ast, Vec::new(), ModuleKind::Lib) + check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::verbose()) +} + +fn check_with_verbosity( + ast: UntypedModule, + level: TraceLevel, +) -> Result<(Vec, TypedModule), (Vec, Error)> { + check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::All(level)) } fn check_with_deps( ast: UntypedModule, extra: Vec<(String, UntypedModule)>, ) -> Result<(Vec, TypedModule), (Vec, Error)> { - check_module(ast, extra, ModuleKind::Lib) + check_module(ast, extra, ModuleKind::Lib, Tracing::verbose()) } fn check_validator( ast: UntypedModule, ) -> Result<(Vec, TypedModule), (Vec, Error)> { - check_module(ast, Vec::new(), ModuleKind::Validator) + check_module(ast, Vec::new(), ModuleKind::Validator, Tracing::verbose()) } #[test] @@ -1288,8 +1296,32 @@ fn trace_non_strings() { True } "#; + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn trace_string_label_compact() { + let source_code = r#" + test foo() { + trace @"foo": [1,2,3] + True + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn trace_non_string_label_compact() { + let source_code = r#" + test foo() { + trace(14 + 42) + True + } + "#; + assert!(matches!( - check(parse(source_code)), + check_with_verbosity(parse(source_code), TraceLevel::Compact), Err((_, Error::CouldNotUnify { .. })) )) } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index e0d84d68..83fc492c 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -24,7 +24,6 @@ use crate::{ }, expr::{FnStyle, TypedExpr, UntypedExpr}, format, - line_numbers::LineNumbers, tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar}, IdGenerator, }; @@ -41,7 +40,6 @@ pub(crate) fn infer_function( module_name: &str, hydrators: &mut HashMap, environment: &mut Environment<'_>, - lines: &LineNumbers, tracing: Tracing, ) -> Result, TypedExpr, TypedArg>, Error> { if let Some(typed_fun) = environment.inferred_functions.get(&fun.name) { @@ -122,7 +120,7 @@ pub(crate) fn infer_function( .remove(name) .unwrap_or_else(|| panic!("Could not find hydrator for fn {name}")); - let mut expr_typer = ExprTyper::new(environment, lines, tracing); + let mut expr_typer = ExprTyper::new(environment, tracing); expr_typer.hydrator = hydrator; expr_typer.not_yet_inferred = BTreeSet::from_iter(hydrators.keys().cloned()); @@ -155,12 +153,11 @@ pub(crate) fn infer_function( environment.current_module, hydrators, environment, - lines, tracing, )?; // Then, try again the entire function definition. - return infer_function(fun, module_name, hydrators, environment, lines, tracing); + return infer_function(fun, module_name, hydrators, environment, tracing); } let (arguments, body, return_type) = inferred?; @@ -223,8 +220,6 @@ pub(crate) fn infer_function( #[derive(Debug)] pub(crate) struct ExprTyper<'a, 'b> { - pub(crate) lines: &'a LineNumbers, - pub(crate) environment: &'a mut Environment<'b>, // We tweak the tracing behavior during type-check. Traces are either kept or left out of the @@ -244,18 +239,13 @@ pub(crate) struct ExprTyper<'a, 'b> { } impl<'a, 'b> ExprTyper<'a, 'b> { - pub fn new( - environment: &'a mut Environment<'b>, - lines: &'a LineNumbers, - tracing: Tracing, - ) -> Self { + pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self { Self { hydrator: Hydrator::new(), not_yet_inferred: BTreeSet::new(), environment, tracing, ungeneralised_function_used: false, - lines, } } @@ -681,25 +671,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .to_pretty_string(999) ), }), - TraceLevel::Compact => Some(TypedExpr::String { - location, - tipo: string(), - value: self - .lines - .line_and_column_number(location.start) - .expect("Spans are within bounds.") - .to_string(), - }), - TraceLevel::Silent => None, + TraceLevel::Compact | TraceLevel::Silent => None, }; let typed_value = self.infer(value)?; self.unify(bool(), typed_value.tipo(), typed_value.location(), false)?; - match self.tracing.trace_level(false) { - TraceLevel::Silent => Ok(typed_value), - TraceLevel::Verbose | TraceLevel::Compact => Ok(TypedExpr::If { + match text { + None => Ok(typed_value), + Some(text) => Ok(TypedExpr::If { location, branches: vec1::vec1![IfBranch { condition: typed_value, @@ -710,7 +691,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { final_else: Box::new(TypedExpr::Trace { location, tipo: bool(), - text: Box::new(text.expect("TraceLevel::Silent excluded from pattern-guard")), + text: Box::new(text), then: Box::new(var_false), }), tipo: bool(), @@ -2426,29 +2407,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> { label: UntypedExpr, arguments: Vec, ) -> Result { - let label = self.infer_trace_arg(label)?; - let typed_arguments = arguments .into_iter() .map(|arg| self.infer_trace_arg(arg)) .collect::, Error>>()?; - let text = if typed_arguments.is_empty() { - label - } else { - let delimiter = |ix| TypedExpr::String { - location: Span::empty(), - tipo: string(), - value: if ix == 0 { ": " } else { ", " }.to_string(), - }; - typed_arguments - .into_iter() - .enumerate() - .fold(label, |text, (ix, arg)| { - append_string_expr(append_string_expr(text, delimiter(ix)), arg) - }) - }; - let then = self.infer(then)?; let tipo = then.tipo(); @@ -2461,26 +2424,42 @@ impl<'a, 'b> ExprTyper<'a, 'b> { match self.tracing.trace_level(false) { TraceLevel::Silent => Ok(then), - TraceLevel::Compact => Ok(TypedExpr::Trace { - location, - tipo, - then: Box::new(then), - text: Box::new(TypedExpr::String { + TraceLevel::Compact => { + let text = self.infer(label)?; + self.unify(string(), text.tipo(), text.location(), false)?; + Ok(TypedExpr::Trace { location, - tipo: string(), - value: self - .lines - .line_and_column_number(location.start) - .expect("Spans are within bounds.") - .to_string(), - }), - }), - TraceLevel::Verbose => Ok(TypedExpr::Trace { - location, - tipo, - then: Box::new(then), - text: Box::new(text), - }), + tipo, + then: Box::new(then), + text: Box::new(text), + }) + } + TraceLevel::Verbose => { + let label = self.infer_trace_arg(label)?; + + let text = if typed_arguments.is_empty() { + label + } else { + let delimiter = |ix| TypedExpr::String { + location: Span::empty(), + tipo: string(), + value: if ix == 0 { ": " } else { ", " }.to_string(), + }; + typed_arguments + .into_iter() + .enumerate() + .fold(label, |text, (ix, arg)| { + append_string_expr(append_string_expr(text, delimiter(ix)), arg) + }) + }; + + Ok(TypedExpr::Trace { + location, + tipo, + then: Box::new(then), + text: Box::new(text), + }) + } } } diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 075aa774..b9a35b0e 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -13,7 +13,6 @@ use crate::{ }, builtins, builtins::{fuzzer, generic_var}, - line_numbers::LineNumbers, tipo::{expr::infer_function, Span, Type, TypeVar}, IdGenerator, }; @@ -86,14 +85,8 @@ impl UntypedModule { } for def in consts.into_iter().chain(not_consts) { - let definition = infer_definition( - def, - &module_name, - &mut hydrators, - &mut environment, - &self.lines, - tracing, - )?; + let definition = + infer_definition(def, &module_name, &mut hydrators, &mut environment, tracing)?; definitions.push(definition); } @@ -162,7 +155,6 @@ fn infer_definition( module_name: &String, hydrators: &mut HashMap, environment: &mut Environment<'_>, - lines: &LineNumbers, tracing: Tracing, ) -> Result { match def { @@ -171,7 +163,6 @@ fn infer_definition( module_name, hydrators, environment, - lines, tracing, )?)), @@ -228,7 +219,7 @@ fn infer_definition( } let mut typed_fun = - infer_function(&fun, module_name, hydrators, environment, lines, tracing)?; + infer_function(&fun, module_name, hydrators, environment, tracing)?; if !typed_fun.return_type.is_bool() { return Err(Error::ValidatorMustReturnBool { @@ -267,14 +258,8 @@ fn infer_definition( let params = params.into_iter().chain(other.arguments); other.arguments = params.collect(); - let mut other_typed_fun = infer_function( - &other, - module_name, - hydrators, - environment, - lines, - tracing, - )?; + let mut other_typed_fun = + infer_function(&other, module_name, hydrators, environment, tracing)?; if !other_typed_fun.return_type.is_bool() { return Err(Error::ValidatorMustReturnBool { @@ -338,8 +323,7 @@ fn infer_definition( }); } - let typed_via = - ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?; + let typed_via = ExprTyper::new(environment, tracing).infer(arg.via.clone())?; let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap(); @@ -404,14 +388,7 @@ fn infer_definition( None => Ok((None, None)), }?; - let typed_f = infer_function( - &f.into(), - module_name, - hydrators, - environment, - lines, - tracing, - )?; + let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?; environment.unify( typed_f.return_type.clone(), @@ -629,7 +606,7 @@ fn infer_definition( tipo: _, }) => { let typed_expr = - ExprTyper::new(environment, lines, tracing).infer_const(&annotation, *value)?; + ExprTyper::new(environment, tracing).infer_const(&annotation, *value)?; let tipo = typed_expr.tipo(); From 2922c0aa6f8c1bd5898f51468eb31f3126dbecc2 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 12:16:13 +0200 Subject: [PATCH 08/10] Display expected patterns/tokens in parse error when applicable. We've never been using those 'expected' tokens captured during parsing, which is lame because they contain useful information! This is much better than merely showing our infamous "Try removing it!" --- crates/aiken-lang/src/parser/error.rs | 70 ++++++++++++++----- .../src/parser/expr/fail_todo_trace.rs | 2 +- crates/aiken-lang/src/parser/token.rs | 2 +- crates/aiken-project/src/error.rs | 2 +- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 0212e042..2877c7ba 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -9,6 +9,30 @@ use std::collections::HashSet; #[derive(Debug, Clone, Diagnostic, thiserror::Error)] #[error("{kind}\n")] +#[diagnostic( + help( + "{}", + match kind { + ErrorKind::Unexpected(..) if !expected.is_empty() => { + format!( + "I am looking for one of the following patterns:\n{}", + expected + .iter() + .map(|x| format!( + "→ {}", + x.to_aiken() + .if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .join("\n") + ) + }, + _ => { + kind.help().map(|x| x.to_string()).unwrap_or_default() + } + } + ) +)] pub struct ParseError { pub kind: ErrorKind, #[label] @@ -28,6 +52,16 @@ impl ParseError { self } + pub fn expected_but_got(expected: Pattern, got: Pattern, span: Span) -> Self { + Self { + kind: ErrorKind::Unexpected(got), + expected: HashSet::from_iter([expected]), + span, + while_parsing: None, + label: None, + } + } + pub fn invalid_assignment_right_hand_side(span: Span) -> Self { Self { kind: ErrorKind::UnfinishedAssignmentRightHandSide, @@ -163,7 +197,7 @@ pub enum ErrorKind { UnexpectedEnd, #[error("{0}")] - #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new(""))))] + #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new("")))) ] Unexpected(Pattern), #[error("I discovered an invalid tuple index.")] @@ -221,7 +255,9 @@ pub enum ErrorKind { HybridNotationInByteArray, #[error("I failed to understand a when clause guard.")] - #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] + #[diagnostic(url( + "https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns" + ))] #[diagnostic(help("{}", formatdoc! { r#"Clause guards are not as capable as standard expressions. While you can combine multiple clauses using '{operator_or}' and '{operator_and}', you can't do any arithmetic in there. They are mainly meant to compare pattern variables to some known constants using simple binary operators. @@ -278,15 +314,6 @@ pub enum Pattern { #[error("I found an unexpected token '{0}'.")] #[diagnostic(help("Try removing it!"))] Token(Token), - #[error("I found an unexpected literal value.")] - #[diagnostic(help("Try removing it!"))] - Literal, - #[error("I found an unexpected type name.")] - #[diagnostic(help("Try removing it!"))] - TypeIdent, - #[error("I found an unexpected identifier.")] - #[diagnostic(help("Try removing it!"))] - TermIdent, #[error("I found an unexpected end of input.")] End, #[error("I found a malformed list spread pattern.")] @@ -295,11 +322,6 @@ pub enum Pattern { #[error("I found an out-of-bound byte literal.")] #[diagnostic(help("Bytes must be between 0-255."))] Byte, - #[error("I found an unexpected pattern.")] - #[diagnostic(help( - "If no label is provided then only variables\nmatching a field name are allowed." - ))] - RecordPunning, #[error("I found an unexpected label.")] #[diagnostic(help("You can only use labels surrounded by curly braces"))] Label, @@ -308,11 +330,27 @@ pub enum Pattern { Discard, } +impl Pattern { + fn to_aiken(&self) -> String { + use Pattern::*; + match self { + Token(tok) => tok.to_string(), + Char(c) => c.to_string(), + End => "".to_string(), + Match => "A pattern (a discard, a var, etc...)".to_string(), + Byte => "A byte between [0; 255]".to_string(), + Label => "A label".to_string(), + Discard => "_".to_string(), + } + } +} + impl From for Pattern { fn from(c: char) -> Self { Self::Char(c) } } + impl From for Pattern { fn from(tok: Token) -> Self { Self::Token(tok) diff --git a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs index be7c7d34..2cde45c8 100644 --- a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +++ b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs @@ -2,7 +2,7 @@ use crate::{ ast::TraceKind, expr::UntypedExpr, parser::{ - error::ParseError, + error::{ParseError, Pattern}, expr::{string, when::clause}, token::Token, }, diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index 8f1fdbfa..78c754ae 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -183,6 +183,6 @@ impl fmt::Display for Token { Token::Validator => "validator", Token::Via => "via", }; - write!(f, "\"{s}\"") + write!(f, "{s}") } } diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index b262ed11..b5398018 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -324,7 +324,7 @@ impl Diagnostic for Error { "Try moving the shared code to a separate module that the others can depend on\n- {}", modules.join("\n- ") ))), - Error::Parse { error, .. } => error.kind.help(), + Error::Parse { error, .. } => error.help(), Error::Type { error, .. } => error.help(), Error::StandardIo(_) => None, Error::MissingManifest { .. } => Some(Box::new( From 30ddfa23d9b107b7860a57a44c7e69a301b18634 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 12:26:26 +0200 Subject: [PATCH 09/10] Provide better parse errors in trace when using comma instead of colon. --- .../aiken-lang/src/parser/expr/fail_todo_trace.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs index 2cde45c8..aed2d6f3 100644 --- a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +++ b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs @@ -29,11 +29,22 @@ pub fn parser<'a>( just(Token::Trace) .ignore_then(choice((string::hybrid(), expression.clone()))) .then( - just(Token::Colon) - .ignore_then( + choice((just(Token::Colon), just(Token::Comma))) + .then( choice((string::hybrid(), expression.clone())) .separated_by(just(Token::Comma)), ) + .validate(|(token, arguments), span, emit| { + if token != Token::Colon { + emit(ParseError::expected_but_got( + Pattern::Token(Token::Colon), + Pattern::Token(token), + span.map(|start, _end| (start, start + 1)), + )) + } + + arguments + }) .or_not() .map(|opt| opt.unwrap_or_default()), ) From 89890f3b4bbf7c6e6afcbc1a45531e508dc49b56 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 12:44:12 +0200 Subject: [PATCH 10/10] Fill-in CHANGELOG --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a2b02d..4218628e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,41 @@ - **aiken-lang**: remove warning on discarded expect, allowing to keep 'side-effects' when necessary. See #967. @KtorZ +- **aiken-lang**: rework traces to be (1) variadic, (2) generic in its arguments and (3) structured. @KtorZ + + In more details: + 1. Enables the `trace` keyword to take one, two or any argument really separated by comma after the first. For example: + + ```ak + trace @"a classic trace" + + // .. + + trace @"condition_1": @"foo" + + // ... + + trace @"condition_2": @"foo", @"bar" + ``` + + 2. Enables the `trace` keyword to not only take strings as arguments; but any + data-type that is serialisable (i.e. that can be cast to Data). It is fundamentally identical to calling the [`cbor.diagnostic`](https://aiken-lang.github.io/stdlib/aiken/cbor.html#diagnostic) function from the standard lib; except that this is done and glued with the rest of the trace automatically. + + ```ak + trace @"condition_1": [1, 2, 3] + + // ... + + let my_var = Some("foo") + trace my_var + ``` + + 3. Changes the behavior of the `--trace-level compact` mode to now: + - remove trace-if-false (`?` operator) traces entirely in this mode; + - only keep the label (first trace argument) and error when it isn't a string. + + See also [#978](https://github.com/aiken-lang/aiken/pull/978). + ## v1.0.29-alpha - 2024-06-06 ### Added