Commit Graph

212 Commits

Author SHA1 Message Date
KtorZ e8d97028ad
Prevent constant evaluating to generic/unbound functions
Constants are like tiny programs, so they are bound by the same rules
  as validators and other programs. In fact, functions are slightly more
  flexible in that they allow generic constant expressions like
  `List<a>`.

  Yet, there is no way to contain such generic structure that contain
  inhabitants in a way that satisfies the type-checker. In the example
  of `List<a>`, the only inhabitant of that type that we can construct
  is the empty list. Anything else would require holding onto some
  generic value.

  In addition, we can't force literal values into generic annotation, as
  something like:

  ```
  const foo: List<a> = [1, 2, 3]
  ```

  wouldn't type-check either since the right-side would unify to
  `List<Int>`. And again, the only right-hand side that can type-check
  is the empty list without any inhabitant.

  The added restriction on generic function is necessary because while
  we allow constants to return lambda, we cannot (easily) generate UPLC
  that is generic in its argument. By the time we generate UPLC, the
  underlying types have to be known.
2024-10-01 18:37:40 +02:00
KtorZ 0060804d1a
Fix redundant warning when destructuring validator params
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.
2024-10-01 13:17:00 +02:00
KtorZ 5737556efc
Fix compiler crash around dangling expect/let in traces
Fixes #1029.
2024-10-01 12:24:31 +02:00
KtorZ a8b37820e8
Fix incorrect warning about unused variable when softcasting without explicit right-pattern.
See note added in code for a rationale.
2024-09-20 15:09:00 +02:00
KtorZ 83ac723a50
Fix extraneous space added after multiline alternative patterns 2024-09-19 20:24:59 +02:00
KtorZ 47a15cf8b2
Fix formatting of long multiline if/is expressions. 2024-09-15 14:59:47 +02:00
KtorZ 12c0d0bc04
Fix formatter adding extra unnecessary newlines after literal lists clause values or assignments. 2024-09-15 14:40:40 +02:00
KtorZ a9a7a4f977
Fix few formatter annoyances. 2024-09-13 18:53:26 +02:00
KtorZ 616dec8f03
Fix formatter getting rid of curly braces around multi-line constants. 2024-09-08 16:23:41 +02:00
KtorZ 0c0369ad61
Fix needed parentheses under trace-if-false disappearing when formatting. 2024-09-08 16:21:45 +02:00
KtorZ b6d99142f9
Fix formatting of multi-line alternative patterns. 2024-09-08 13:11:53 +02:00
KtorZ b7ea6ea391
Fix list-pattern formatting 2024-09-07 14:50:47 +02:00
KtorZ 75c059bf65
Fix module constant usage warnings. 2024-08-30 18:24:35 +02:00
rvcas d337e601cb
chore: add a test for by name with params 2024-08-27 18:10:46 -04:00
KtorZ efeda9a998
Prevent non-default fallback on exhaustive validator
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.
2024-08-27 20:16:45 +02:00
KtorZ 0510ca58f7
Implement record access syntax for validator handlers.
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.
2024-08-27 17:16:15 +02:00
KtorZ ee8f608c0b
Omit validator return annotation when formatting
Unless it is NOT bool as it deviates from the default and while wrong, we want to preserve it to provide a good error.
2024-08-27 14:53:07 +02:00
rvcas 4589c51cd3
feat: enforcement that spend first arg is option 2024-08-25 16:20:07 +02:00
rvcas c57009bf99
chore: fix some tests that now correctly fail due to arity checks 2024-08-25 16:20:07 +02:00
rvcas f94e40daf4
fix: more test and issues with scoping/names 2024-08-25 16:20:03 +02:00
rvcas 00907c2bcc
fix: format snapshot tests 2024-08-25 16:20:03 +02:00
rvcas fff90f7df5
feat: fix inference comp issues 2024-08-25 16:20:02 +02:00
KtorZ 9aa9070f56
Revise desugaring following feedback
- We now consistently desugar an expect in the last position as
    `Void`. Regardless of the pattern. Desugaring to a boolean value is
    deemed too confusing.

  - This commit also removes the desugaring for let-binding. It's only
    ever allowed for _expect_ which then behaves like a side effect.

  - We also now allow tests to return either `Bool` or `Void`. A test
    that returns `Void` is treated the same as a test returning `True`.
2024-08-23 16:04:40 +02:00
KtorZ fbe6f02fd1
Allow assignment as last expression
This is debatable, but I would argue that it's been sufficiently
  annoying for people and such a low-hanging fruit that we ought to do
  something about it.

  The strategy here is simple: when we find a sequence of expression
  that ends with an assignment (let or expect), we can simply desugar it
  into two expressions: the assignment followed by either `Void` or a
  boolean.

  The latter is used when the assignment pattern is itself a boolean;
  the next boolean becomes the expected value. The former, `Void`, is
  used for everything else. So said differently, any assignment
  implicitly _returns Void_, except for boolean which return the actual
  patterned bool.

  <table>
  <thead><tr><th>expression</th><th>desugar into</th></tr></thead>
  <tbody>
  <tr>
  <td>

  ```aiken
  fn expect_bool(data: Data) -> Void {
    expect _: Bool = data
  }
  ```
  </td>
  <td>

  ```aiken
  fn expect_bool(data: Data) -> Void {
    expect _: Bool = data
    Void
  }
  ```
  </td>
  </tr>
  <tr>
  <td>

  ```aiken
  fn weird_maths() -> Bool {
    expect 1 == 2
  }
  ```
  </td>
  <td>

  ```aiken
  fn weird_maths() -> Bool {
    expect True = 1 == 2
    True
  }
  ```
  </td>
  </tr>
  </tbody>
  </table>
2024-08-23 16:04:39 +02:00
KtorZ 5067aad0d8
Fix 'Pair' formatter inside forced unbroken components. 2024-08-13 17:05:41 +02:00
KtorZ aa872846bb
Re-generate insta snapshots, remove obsolete snapshots. 2024-08-06 19:25:35 +02:00
KtorZ 9d8fdf787c
Improve formatter on long-lines, in particular bin-ops. 2024-08-06 19:25:35 +02:00
KtorZ fbe2f82582
Thread down environment module from cli down to the type-checker
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.
2024-08-04 10:27:32 +02:00
KtorZ f14dfdf8e1
Allow pattern-matching on bytearrays
- 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).
2024-08-03 13:51:36 +02:00
KtorZ bf5a406ffb Remove clause guards.
Closes #886.
2024-08-02 00:16:27 -04:00
KtorZ 67493ad847
Fix casting inferrence on patterns
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).
2024-07-24 12:42:24 +02:00
KtorZ d6fd37c80e
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.
2024-07-19 12:28:08 +02:00
KtorZ f9719af23e
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.
2024-07-18 10:01:34 +02:00
KtorZ 976262c2e6
Allow discard in expect.
Fixes #967.
2024-07-17 18:11:09 +02:00
rvcas 5bdea11cc1 fix: add a new assignment kind instead of using a boolean 2024-06-25 18:50:00 -04:00
KtorZ 00d1927dad Add few more test cases for the parser and type-checker. 2024-06-25 18:50:00 -04:00
rvcas aeb079334e chore: this test doesn't make sense anymore 2024-06-25 18:50:00 -04:00
rvcas 1b8805825b feat: impl if/is
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.
2024-06-25 18:50:00 -04:00
KtorZ 858dfccc82
Authorize complete patterns as function args.
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.
2024-06-07 15:42:25 +02:00
KtorZ d7ec2131ef
Automatically merge import lines from same module.
I slightly altered the way we parse import definitions to ensure we
  merge imports from the same modules (that aren't aliased) together.

  This prevents an annoying warning with duplicated import lines and
  makes it just more convenient overall.

  As a trade-off, we can no longer interleave import definitions with
  other definitions. This should be a minor setback only since the
  formatter was already ensuring that all import definitions would be
  grouped at the top.

  ---

  Note that, I originally attempted to implement this in the formatter
  instead of the parser. As it felt more appropriate there. However, the
  formatter operates on (unmutable) borrowed definitions, which makes it
  annoyingly hard to perform any AST manipulations. The `Document`
  returns by the format carries a lifetime that prevents the creation of
  intermediate local values.

  So instead, slightly tweaking the parser felt like the right thing to
  do.
2024-06-04 10:48:42 +02:00
KtorZ 649e5163fc
Fix parsing of single hex-digits. 2024-05-30 17:19:48 +02:00
rvcas 7f38b55c1c
fix: comments in record patterns closes #946 2024-05-22 12:26:36 -04:00
rvcas 4ca73c4cdf
fix: closes #898
This is the best we can do for this without
rearchitecting when we rewrite backpassing to
plain ol' assignments. In this case, if we see
a var and there is no annotation (thus probably not a cast),
then it's safe to rewrite to a `let` instead of an `expect`.
This way, we don't get a warning that is **unfixable**.
We are not trying to solve every little warning edge
case with this fix. We simply just can't allow there
to be a warning that the user can't make go away through
some means. All other edge cases like pattern matching on
a single contructor type with expect warnings can be fixed
via other means.
2024-05-21 15:21:24 -04:00
KtorZ eadf709411
Fix scope management issue when deep-inferring callee.
Fixes #941.

  However, this currently breaks the stdlib somehow with some FreeUnique on the shrinker step of the optimizer.
2024-05-15 13:18:51 +02:00
KtorZ 81219cfbdd
Check for data-type serialisability after generic instantiation
Fixes #939.
2024-05-14 10:58:58 +02:00
KtorZ 26ef25ba8d
Make comparison of non-serialisable types illegal.
Fixes #940.
2024-05-14 10:45:15 +02:00
KtorZ 8c67be55ce
Fixes #921: top-level Miller-loop needs not to be serialisable
This is a bit tricky, but in a similar way where we allow functions to
  be returned by functions, this must also work for MillerLoopResult.
2024-05-10 13:52:23 +02:00
KtorZ b1f0dfdacd Implement parser & formatter for Pair annotations. 2024-05-04 14:04:12 -04:00
KtorZ 7cb548a749 Fix ordinal index on pairs 2024-05-04 14:04:12 -04:00
KtorZ 925a11be69
Check for args length when comparing types. Duh!
Fixes #917.
2024-05-01 10:48:15 +02:00