Merge pull request #978 from aiken-lang/supercharge_traces
Supercharge traces
This commit is contained in:
commit
967c264bfe
35
CHANGELOG.md
35
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<FunctionAccessKey, TypedFunction> {
|
||||
pub fn prelude_functions(
|
||||
id_gen: &IdGenerator,
|
||||
module_types: &HashMap<String, TypeInfo>,
|
||||
) -> IndexMap<FunctionAccessKey, TypedFunction> {
|
||||
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<FunctionAccessKey, Ty
|
|||
},
|
||||
);
|
||||
|
||||
functions.insert(
|
||||
FunctionAccessKey {
|
||||
module_name: "".to_string(),
|
||||
function_name: "enumerate".to_string(),
|
||||
},
|
||||
aiken_fn!(
|
||||
&module_types,
|
||||
&id_gen,
|
||||
r#"
|
||||
fn enumerate(
|
||||
self: List<a>,
|
||||
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<Data, Data>, st: ByteArray) {
|
||||
let value = diagnostic(e.2nd, builtin.append_bytearray(#"2c20", st))
|
||||
diagnostic(e.1st, builtin.append_bytearray(#"3a20", value))
|
||||
},
|
||||
fn(e: Pair<Data, Data>, 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
|
||||
}
|
||||
|
||||
|
|
|
@ -548,7 +548,8 @@ pub enum UntypedExpr {
|
|||
kind: TraceKind,
|
||||
location: Span,
|
||||
then: Box<Self>,
|
||||
text: Box<Self>,
|
||||
label: Box<Self>,
|
||||
arguments: Vec<Self>,
|
||||
},
|
||||
|
||||
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 }
|
||||
|
|
|
@ -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("(..)")
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::<Vec<_>>()
|
||||
.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 => "<END OF FILE>".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<char> for Pattern {
|
||||
fn from(c: char) -> Self {
|
||||
Self::Char(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Token> for Pattern {
|
||||
fn from(tok: Token) -> Self {
|
||||
Self::Token(tok)
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
ast::TraceKind,
|
||||
expr::UntypedExpr,
|
||||
parser::{
|
||||
error::ParseError,
|
||||
error::{ParseError, Pattern},
|
||||
expr::{string, when::clause},
|
||||
token::Token,
|
||||
},
|
||||
|
@ -28,13 +28,36 @@ pub fn parser<'a>(
|
|||
.map_with_span(UntypedExpr::fail),
|
||||
just(Token::Trace)
|
||||
.ignore_then(choice((string::hybrid(), expression.clone())))
|
||||
.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()),
|
||||
)
|
||||
.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 +137,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 +175,22 @@ mod tests {
|
|||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_labelled() {
|
||||
assert_expr!(
|
||||
r#"
|
||||
trace foo: "bar"
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_variadic() {
|
||||
assert_expr!(
|
||||
r#"
|
||||
trace "foo": @"bar", baz
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ Trace {
|
|||
then: ErrorTerm {
|
||||
location: 0..11,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 5..11,
|
||||
value: "foo",
|
||||
},
|
||||
arguments: [],
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ Trace {
|
|||
then: ErrorTerm {
|
||||
location: 0..10,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 5..10,
|
||||
value: "foo",
|
||||
},
|
||||
arguments: [],
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ Trace {
|
|||
then: ErrorTerm {
|
||||
location: 0..11,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 5..11,
|
||||
value: "foo",
|
||||
},
|
||||
arguments: [],
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ Trace {
|
|||
then: ErrorTerm {
|
||||
location: 0..4,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 0..4,
|
||||
value: "aiken::todo",
|
||||
},
|
||||
arguments: [],
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ Trace {
|
|||
then: ErrorTerm {
|
||||
location: 0..10,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 5..10,
|
||||
value: "foo",
|
||||
},
|
||||
arguments: [],
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
}
|
|
@ -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: [],
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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: [],
|
||||
}
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -54,10 +54,11 @@ When {
|
|||
then: ErrorTerm {
|
||||
location: 47..68,
|
||||
},
|
||||
text: String {
|
||||
label: String {
|
||||
location: 52..68,
|
||||
value: "unimplemented",
|
||||
},
|
||||
arguments: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -183,6 +183,6 @@ impl fmt::Display for Token {
|
|||
Token::Validator => "validator",
|
||||
Token::Via => "via",
|
||||
};
|
||||
write!(f, "\"{s}\"")
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ fn check_module(
|
|||
ast: UntypedModule,
|
||||
extra: Vec<(String, UntypedModule)>,
|
||||
kind: ModuleKind,
|
||||
tracing: Tracing,
|
||||
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, 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<Warning>, TypedModule), (Vec<Warning>, 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<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
||||
check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::All(level))
|
||||
}
|
||||
|
||||
fn check_with_deps(
|
||||
ast: UntypedModule,
|
||||
extra: Vec<(String, UntypedModule)>,
|
||||
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
||||
check_module(ast, extra, ModuleKind::Lib)
|
||||
check_module(ast, extra, ModuleKind::Lib, Tracing::verbose())
|
||||
}
|
||||
|
||||
fn check_validator(
|
||||
ast: UntypedModule,
|
||||
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, 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 { .. }))
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -19,12 +19,13 @@ use crate::{
|
|||
UntypedRecordUpdateArg,
|
||||
},
|
||||
builtins::{
|
||||
bool, byte_array, function, g1_element, g2_element, int, list, pair, string, tuple, void,
|
||||
bool, byte_array, data, 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,
|
||||
|
@ -39,7 +40,6 @@ pub(crate) fn infer_function(
|
|||
module_name: &str,
|
||||
hydrators: &mut HashMap<String, Hydrator>,
|
||||
environment: &mut Environment<'_>,
|
||||
lines: &LineNumbers,
|
||||
tracing: Tracing,
|
||||
) -> Result<Function<Rc<Type>, TypedExpr, TypedArg>, Error> {
|
||||
if let Some(typed_fun) = environment.inferred_functions.get(&fun.name) {
|
||||
|
@ -94,7 +94,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();
|
||||
|
||||
|
@ -120,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());
|
||||
|
||||
|
@ -153,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?;
|
||||
|
@ -221,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
|
||||
|
@ -242,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,9 +508,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
UntypedExpr::Trace {
|
||||
location,
|
||||
then,
|
||||
text,
|
||||
label,
|
||||
arguments,
|
||||
kind,
|
||||
} => self.infer_trace(kind, *then, location, *text),
|
||||
..
|
||||
} => self.infer_trace(kind, *then, location, *label, arguments),
|
||||
|
||||
UntypedExpr::When {
|
||||
location,
|
||||
|
@ -677,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,
|
||||
|
@ -706,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(),
|
||||
|
@ -1793,7 +1778,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
})
|
||||
}
|
||||
|
||||
fn infer_fn(
|
||||
pub fn infer_fn(
|
||||
&mut self,
|
||||
args: Vec<UntypedArg>,
|
||||
expected_args: &[Rc<Type>],
|
||||
|
@ -2403,15 +2388,29 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
TypedExpr::ErrorTerm { location, tipo }
|
||||
}
|
||||
|
||||
fn infer_trace_arg(&mut self, arg: UntypedExpr) -> Result<TypedExpr, Error> {
|
||||
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,
|
||||
then: UntypedExpr,
|
||||
location: Span,
|
||||
text: UntypedExpr,
|
||||
label: UntypedExpr,
|
||||
arguments: Vec<UntypedExpr>,
|
||||
) -> Result<TypedExpr, Error> {
|
||||
let text = self.infer(text)?;
|
||||
self.unify(string(), text.tipo(), text.location(), false)?;
|
||||
let typed_arguments = arguments
|
||||
.into_iter()
|
||||
.map(|arg| self.infer_trace_arg(arg))
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
let then = self.infer(then)?;
|
||||
let tipo = then.tipo();
|
||||
|
@ -2425,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),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2784,3 +2799,115 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc<Type>, location: Span) -> R
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: expr.location(),
|
||||
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 = 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 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: byte_array(),
|
||||
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.clone(),
|
||||
label: DefaultFunction::AppendString.aiken_name(),
|
||||
module_name: BUILTIN.to_string(),
|
||||
module_alias: BUILTIN.to_string(),
|
||||
constructor: value_constructor.variant.to_module_value_constructor(
|
||||
value_constructor.tipo,
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Hydrator>,
|
||||
environment: &mut Environment<'_>,
|
||||
lines: &LineNumbers,
|
||||
tracing: Tracing,
|
||||
) -> Result<TypedDefinition, Error> {
|
||||
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();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Schema {
|
|||
Var {
|
||||
tipo: RefCell {
|
||||
value: Generic {
|
||||
id: 35,
|
||||
id: 64,
|
||||
},
|
||||
},
|
||||
alias: None,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Schema {
|
|||
Var {
|
||||
tipo: RefCell {
|
||||
value: Generic {
|
||||
id: 35,
|
||||
id: 64,
|
||||
},
|
||||
},
|
||||
alias: None,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue