* Fix: Deeply nested assignments would offset the new columns count calculation. Now we track relevant columns and their path to ensure each row has wildcards if they don't contain the relevant column
* Add test plus clippy fix
* Clippy fix
* New version clippy fix
This is not a "proper" fix as it simply get rid of the warning
altogether (whether you use or not the destructured values).
The reason for removing the warning entirely is because (1) it's
simpler, but more so (2) there's no impact on the final code produced
_anyway_. Redundant let bindings are already removed by the compiler;
and while it's an implicit behaviour that requires a proper warning
when it's coming from a user-defined assignment; here the redundant
assignment is introduced by the compiler to begin with as another
implicit behavior!
So we have an implicit behaviour triggering a warning on another
implicit behaviour. Truth is, there's no impact in having those
parameters destructured and unused. So since users are already not
aware that this results in an implicit let assignment being inserted
in place for them; there's no need for the warning at all.
This is only a start. It compiles, but with a few TODOs left open. In particular, it doesn't currently handle constants depending on other constants or functions; nor does it hoist constants.
Technically, we always need a fallback just because the way the UPLC
is going to work. The last case in the handler pattern matching is
always going to be else ...
We could optimize that away and when the validator is exhaustive, make
the last handler the fallback. Yet, it's really a micro optimization
that saves us one extra if/else. So the sake of getting things
working, we always assume that there's a fallback but, with the extra
condition that when the validator is exhaustive (i.e. there's a
handler covering all purposes), the fallback HAS TO BE the default
fallback (i.e. (_) => fail).
This allows us to gracefully format it out, and also raise an error in
case where there's an extraneous custom fallback.
When there's no type annotation in a validator handler signature, we
provide default annotation to help the type-checker. However, for
spend's datum and mint policy_id, those annotations mustn't be `Data`,
but rather Option<Data> and Bytearray.
Without that, when no annotation are provided, the compiler infer
invalid types and fails with incongruous errors.
This is a little trick which detects record access and replace them
with a simple var. The var itself is the validator handler name,
though since it contains dots, it cannot be referred to by users
explicitly. Yet fundamentally, it is semantically equivalent to just
calling the function by its name.
Note that this commit also removes the weird backdoor for allowing
importing validators in modules starting with `tests`. Allowing
validators handler to be used in importable module requires more work
and is arguably useful; so we will wait until someone complain and
reconsider the proper way to do it.
Unfortunately, as documented in:
https://github.com/IntersectMBO/cardano-ledger/issues/4571
Some Option fields in the script context certificates are going to
remain set to None, at least until the next Hard fork. There's a risk
that people permanently lock their funds if they expect deposits on
registration credentials to ever be `Some`.
So, we introduce a special type that emulate an `Option` that can only
ever be `None`. We call it `Never` and it is the first type of this
kind (i.e. with constructors indexes not starting at 0).
This isn't sufficient however, as the 'assignment' helper handling
code generation doesn't perform any check when patterns are vars. This
is curious, and need to be investigated further.
Let's consider the following case:
```
type Var =
Integer
type Vars =
List<Var>
```
This incorrectly reports an infinite cycle; due to the inability to
properly type-check `Var` which is also a dependent var of `Vars`. Yet
the real issue here being that `Integer` is an unknown type.
This commit also upgrades miette to 7.2.0, so that we can also display
a better error output when the problem is actually a cycle.
This is currently extremely limited as it only supports (UTF-8)
bytearrays and integers. We should seek to at least support hex bytes
sequences, as well as bools, lists and possibly options.
For the latter, we the rework on constant outlined in #992 is
necessary.
We figure out dependencies by looking at 'use' definition in parsed
modules. However, in the case of environment modules, we must consider
all of them when seeing "use env". Without that, the env modules are
simply compiled in parallel and may not yet have been compiled when
they are needed as actual dependencies.
We simply provide a flag with a free-form output which acts as
the module to lookup in the 'env' folder. The strategy is to replace
the environment module name on-the-fly when a user tries to import
'env'.
If the environment isn't found, an 'UnknownModule' error is raised
(which I will slightly adjust in a following commits to something more
related to environment)
There are few important consequences to this design which may not seem
immediately obvious:
1. We parse and type-check every env modules, even if they aren't
used. This ensures that code doesn't break with a compilation error
simply because people forgot to type-check a given env.
Note that compilation could still fail because the env module
itself could provide an invalid API. So it only prevents each
modules to be independently wrong when taken in isolation.
2. Technically, this also means that one can import env modules in
other env modules by their names. I don't know if it's a good or
bad idea at this point but it doesn't really do any wrong;
dependencies and cycles are handlded all-the-same.
- Doesn't allow pattern-matching on G1/G2 elements and strings,
because the use cases for those is unclear and it adds complexity to
the feature.
- We still _parse_ patterns on G1/G2 elements and strings, but emit an
error in those cases.
- The syntax is the same as for bytearray literals (i.e. supports hex,
utf-8 strings or plain arrays of bytes).
- 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.
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.
While we agree on the idea of having some ways of emitting events, the
design hasn't been completely fleshed out and it is unclear whether
events should have a well-defined format independent of the framework
/ compiler and what this format should be.
So we need more time discussing and agreeing about what use case we
are actually trying to solve with that.
Irrespective of that, some cleanup was also needed on the UPLC side
anyway since the PR introduced a lot of needless duplications.
Before this commit, we would parse 'Pair' as a user-defined
data-types, and thus piggybacking on that whole record system. While
perhaps handy for some things, it's also semantically wrong and
induces a lot more complexity in codegen which now needs to
systematically distinguish every data-type access between pairs, and
others.
So it's better to have it as a separate expression, and handle it
similar to tuples (since it's fundamentally a 2-tuple with a special
serialization).