While this builtin is readily available through the Aiken syntax
`[head, ..tail]`, there's no reason to not support its builtin form
even though we may not encourage its usage. For completeness and to
avoid bad surprises, it is now supported.
Fixes#964.
The original goal for this commit was to allow casting from Data on
patterns without annotation. For example, given some custom type
'OrderDatum':
```
expect OrderDatum { requested_handle, destination, .. }: OrderDatum = datum
```
would work fine, but:
```
expect OrderDatum { requested_handle, destination, .. } = datum
```
Yet, the annotation feels unnecessary at this point because type can
be inferred from the pattern itself. So this commit allows, whenever
possible (ie when the pattern is neither a discard nor a var), to
infer the type from a pattern.
Along the way, I also found a couple of weird behaviours surrounding
this kind of assignments, in particular in combination with let. I'll
highlight those in the next PR (#979).
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!"
- 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.
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.
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.
This commit introduces a new feature into
the parser, typechecker, and formatter.
The work for code gen will be in the next commit.
I was able to leverage some existing infrastructure
by making using of `AssignmentPattern`. A new field
`is` was introduced into `IfBranch`. This field holds
a generic `Option<Is>` meaning a new generic has to be
introduced into `IfBranch`. When used in `UntypedExpr`,
`IfBranch` must use `AssignmentPattern`. When used in
`TypedExpr`, `IfBranch` must use `TypedPattern`.
The parser was updated such that we can support this
kind of psuedo grammar:
`if <expr:condition> [is [<pattern>: ]<annotation>]`
This can be read as, when parsing an `if` expression,
always expect an expression after the keyword `if`. And then
optionally there may be this `is` stuff, and within that you
may optionally expect a pattern followed by a colon. We will
always expect an annotation.
This first expression is still saved as the field
`condition` in `IfBranch`. If `pattern` is not there
AND `expr:condition` is `UntypedExpr::Var` we can set
the pattern to be `Pattern::Var` with the same name. From
there shadowing should allow this syntax sugar to feel
kinda magical within the `IfBranch` block that follow.
The typechecker doesn't need to be aware of the sugar
described above. The typechecker looks at `branch.is`
and if it's `Some(is)` then it'll use `infer_assignment`
for some help. Because of the way that `is` can inject
variables into the scope of the branch's block and since
it's basically just like how `expect` works minus the error
we get to re-use that helper method.
It's important to note that in the typechecker, if `is`
is `Some(_)` then we do not enforce that `condition` is
of type `Bool`. This is because the bool itself will be
whether or not the `is` itself holds true given a PlutusData
payload.
When `is` is None, we do exactly what was being done
previously so that plain `if` expressions remain unaffected
with no semantic changes.
The formatter had to be made aware of the new changes with
some simple changes that need no further explanation.
This is mainly a syntactic trick/sugar, but it's been pretty annoying
to me for a while that we can't simply pattern-match/destructure
single-variant constructors directly from the args list. A classic
example is when writing property tests:
```ak
test foo(params via both(bytearray(), int())) {
let (bytes, ix) = params
...
}
```
Now can be replaced simply with:
```
test foo((bytes, ix) via both(bytearray(), int())) {
...
}
```
If feels natural, especially coming from the JavaScript, Haskell or
Rust worlds and is mostly convenient. Behind the scene, the compiler
does nothing more than re-writing the AST as the first form, with
pre-generated arg names. Then, we fully rely on the existing
type-checking capabilities and thus, works in a seamless way as if we
were just pattern matching inline.
There's no reasons for this to be a property of only ArgName::Named to begin with. And now, with the extra indirection introduced for arg_name, it may leads to subtle issues when patterns args are used in validators.