Compare commits

..

28 Commits

Author SHA1 Message Date
Hans Svensson 311bf49505 Prepare v7.1.0 release (#438) 2023-02-24 09:40:06 +01:00
Denis Davidyuk 0e3bcba07d Fix markup in sophia_features.md (#437) 2023-02-15 13:27:29 +03:00
Marco Walz 699d1f7ab8 fix: use latest pygments version to generate docs (#435) 2023-02-02 08:32:54 +01:00
Marco Walz 1a40a93157 chore(deps): mkdocs version update (#434) 2023-02-02 10:16:27 +03:00
Gaith Hallak c078119bc4 Add hole expression (#433)
* Add hole expressions

* Fix the issue of unreported holes

* Add tests

* New line in the end of the test file

* Update CHANGELOG

* Add hole expression to the docs

* Do not treat hole as a special type

* Update docs

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2023-01-12 16:23:36 +03:00
Gaith Hallak 31fd8fe24f Hide warning when calling with non-0 value arg (#431)
* Hide warning when calling with non-0 value arg

* Update the tests

* Update CHANGELOG
2022-12-12 11:44:24 +03:00
Nikita Fuchs 9ad8e26e88 Add clarification for Chain.timestamp in the stdlib docs (#429) 2022-12-07 17:34:55 +03:00
Gaith Hallak 5adeb6c93e Ban using contracts as namespaces (#428)
* Ban calling contracts functions as functions namespaces

* Ban using contracts as namespaces

* Add tests

* Update CHANGELOG

* Separate guards with a semicolon
2022-11-23 12:03:24 +03:00
Gaith Hallak 256df25af4 Check contracts and entrypoints modifiers when implementing interfaces (#427)
* Check contracts and entrypoints modifiers when implementing interfaces

* Fix existing tests

* Add passing tests

* Add failing tests

* Update docs

* Update CHANGELOG
2022-11-17 11:40:57 +03:00
Gaith Hallak 83abfae32b Ban the unification of uvars and var_args functions (#423)
* Ban the unification of uvar and var_args function

* Update CHANGELOG

* Fix the tests

* Undo indent

* Change the error message for unify_varargs
2022-11-01 18:10:57 +02:00
Denis Davidyuk 4ca90feea0 Rename type_defs to typedefs in ACI to increase compatibility (#421) 2022-11-01 08:55:00 +02:00
Gaith Hallak 09638daa90 Return mapping from variables to registers in fate compilation (#411)
* Return mapping from variables to registers

* Fix dialyzer issues

* Record real names

* Report saved fresh names as part of fcode env

* Undo whitespace changes

* Fix dialyzer warnings

* Formatting fix

* Use function names as strings

* Manually handle making function names

* Update CHANGELOG

* Make variables registers optional

* Update docs about the new flag

* Remove empty saved_fresh_names map from fcode env
2022-10-25 09:42:02 +03:00
Gaith Hallak d59023a9f4 Allow calling a different instance of the current contract (#379)
Add functions as fields before inferring

Unbound untyped fields before binding typed ones

Fix failing tests

Make complex_types contract non-compatible with aevm

Reduce code duplication

Undo changes to test.aes

Remove special handling of __constructor__ field

Resolve field constraint by arity of contract function

Update CHANGELOG

Update CHANGELOG.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Split bind_field function

Add a comment about rebinding
2022-10-23 15:01:28 +03:00
Radosław Rowicki 34b52739fd Include all functions in the symbols map (#418)
* Include all functions in the symbols map

* .

* remove improper wording

* Use update_symbols exported from aebytecode

* Extract adding child symbols into a separate fun

* Make child contracts symbols optional

* Document include_child_contract_symbols option

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-10-07 15:57:37 +03:00
Gaith Hallak 1c83287d45 Add separate flags for each scode optimization (#410)
* Add separate flags for each scode optimization

* Add a list of available optimizations to docs

* Update CONTRIBUTING.md

* Update docs/aeso_compiler.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Prefix rules functions with optimize_ instead of r_

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-10-07 12:09:53 +03:00
Gaith Hallak da92ddbd5d Polymorphism fixes (#415)
* Assume that void is a supertype of all types

* Add test for void supertype

* Unify functions with decls from implemented interfaces

* Rename delete_if_implementation

* Match only with function name and without typesig
2022-10-04 12:40:50 +03:00
Gaith Hallak c1c169273c Add options to enable/disable certain optimizations (#409)
* Add flags to enable/disable specific optimizations

* Fix typos

* Enable/disable scode optimization

* Update CHANGELOG.md

* Remove optimize_all option
2022-08-30 10:14:46 +03:00
Gaith Hallak ad4c341a4a Bump version to 7.0.1 (#408)
* Fix broken link in CONTRIBUTING.md

* Bump version to 7.0.1 and update CHANGELOG.md
2022-08-04 19:38:24 +02:00
Gaith Hallak f964fa89a1 Add CONTRIBUTING.md (#406)
* Add CONTRIBUTING.md

* Include CONTRIBUTING.md in README.md

* Fix broken links

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Use "If a PR" instead of "If the PR"

* Mention fold and pretty printing

* Add missing precedence of the operator '|>'

* Add a note about tests

* Rename to Sophia

* Add missing using keyword

* Update the entire list of keywords in sophia syntax doc

* Add a section about creating a new aesophia release
2022-08-04 19:35:48 +04:00
Gaith Hallak 8d8d9c6b83 Update Sophia syntax docs to include missing information about existing syntax (#405)
* Add main contract, contract interface, and guards to the docs syntax

* Use Sep1 instead of Sep for the GuardedDef

* Add guarded case for switches

* Change '=' to '::=' in GuardedDef

* Add Using

* Add '|>' binary operator to aeso_syntax

* Add assign patter

* Fix typos

* Add polymorphism implmented interface syntax
2022-08-03 22:25:42 +02:00
Gaith Hallak c98ea25e8b Fix: Get the type of Chain.create() from its application (#407)
* Get the type of Chain.create() from its application

* Add test contract

* Update CHANGELOG.md

* Update the tests

* Update tests again
2022-08-03 22:24:22 +02:00
Radosław Rowicki 4dbc9858fb Prepare 7.0.0 release, upgrade to OTP24 (#402)
* Prepare v7.0.0 release

* OTP25

* Try OTP24

* Update aebytecode

* aeb 3.1.1

* Update CHANGELOG.md

Co-authored-by: Hans Svensson <hanssv@gmail.com>

* Update rebar.lock

Co-authored-by: Hans Svensson <hanssv@gmail.com>
Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-07-28 21:38:18 +02:00
Gaith Hallak 51f9eaa934 Update the documentation and changelog to include polymorphism (#396)
* Update CHANGELOG

* Docs: first part

* Docs: first part (subtyping)

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Change "the same type" to "compatible types"

* Formatting for subtyping rules

* Note about type variance

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-07-25 18:47:16 +02:00
Radosław Rowicki 0ebcf006e2 Prepare for new repl (#401)
Minor tweaks that are used by aerepl
2022-07-25 18:46:54 +02:00
Gaith Hallak 381a7c98cd Handle all user generated code errors in the type checker (#394)
* Move the missing_definition error to the type checker

* Move the parameterised_event and parameterised_state errors to the type checker

* Remove check_state_and_event_types from ast_to_fcode

* Move missing_init_function to the type checker

* Remove the code error last_declaration_must_be_main_contract

* Expand the tests for missing init function

* Remove found_void error

* Move the higher order entrypoint error to type checker

* Move invalid_aens_resolve_type error to type checker

* Add more tests for AENS.resolve

* Add test for AENS.resolve with using

* Move invalid_oracle_type error to type checker

* Move old code errors tests to compilable_contracts

* Remove the file aeso_code_errors.erl

* Add comment about state type
2022-07-25 19:48:46 +04:00
Radosław Rowicki 4bec4e5107 Added support for EXIT op (#397)
* EXIT op

* changelog

* docs

* Update CHANGELOG.md
2022-07-08 15:56:29 +02:00
Marco Walz 4dd247b159 Merge pull request #399 from aeternity/mkdocs-updates
chore: update deps and remove caching of python libs
2022-07-06 13:15:58 +02:00
marc0olo d926c4a7e3 chore: update deps and remove caching of python libs 2022-07-06 10:05:44 +02:00
82 changed files with 1406 additions and 658 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder:xenial-otp21
- image: aeternity/builder:bionic-otp24
user: builder
working_directory: ~/aesophia
-4
View File
@@ -13,10 +13,6 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
-4
View File
@@ -13,10 +13,6 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
+5 -5
View File
@@ -1,5 +1,5 @@
mkdocs==1.2.4
mkdocs-simple-hooks==0.1.3
mkdocs-material==7.1.9
mike==1.0.1
pygments==2.11.2
mkdocs==1.4.2
mkdocs-simple-hooks==0.1.5
mkdocs-material==9.0.9
mike==1.1.2
pygments==2.14.0
+40 -2
View File
@@ -6,7 +6,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
### Changed
### Removed
### Fixed
## [7.1.0]
### Added
- Options to enable/disable certain optimizations.
- The ability to call a different instance of the current contract
```
contract Main =
entrypoint spend(x : int) : int = x
entrypoint f(c : Main) : int = c.spend(10)
```
- Return a mapping from variables to FATE registers in the compilation output.
- Hole expression.
### Changed
- Type definitions serialised to ACI as `typedefs` field instead of `type_defs` to increase compatibility.
- Check contracts and entrypoints modifiers when implementing interfaces.
- Contracts can no longer be used as namespaces.
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
### Fixed
- Typechecker crashes if Chain.create or Chain.clone are used without arguments.
## [7.0.1]
### Added
- Add CONTRIBUTING.md file.
### Changed
- Update Sophia syntax docs to include missing information about existing syntax.
### Fixed
- [404](https://github.com/aeternity/aesophia/issues/404) Contract polymorphism crashes on non-obvious child contract typing.
## [7.0.0]
### Added
- Added support for `EXIT` opcode via `exit : (string) => 'a` function (behaves same as `ABORT`, but consumes all gas).
- Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
- The pipe operator |>
```
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
@@ -16,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
function sum(l : list(int)) : int = foldl((+), 0, l)
function logical_and(x, y) = (&&)(x, y)
```
- Contract interfaces polymorphism
### Changed
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
- Ban empty record definitions (e.g. `record r = {}` would give an error).
@@ -345,7 +380,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v6.1.0...HEAD
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0
[7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
+40
View File
@@ -0,0 +1,40 @@
# Contributing to Sophia
## Checklist For Creating New Pull Requests
The following points should be considered before creating a new PR to the Sophia compiler.
### Documentation
- The [Changelog](CHANGELOG.md) file should be updated for all PRs.
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
- If a PR introduces a new compiler option, the new option should be documented in the file
[aeso_compiler.md](docs/aeso_compiler.md).
### Tests
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the contract [all_syntax.aes](test/contracts/all_syntax.aes) should be updated to include the new syntax.
- If a PR fixes a bug, the code that replicates the bug should be added as a new passing test contract.
- If a PR introduces a new feature, add tests for both successful and failing usage of that feature. In order to run the entire compilation pipeline and to avoid erroring during intermediate steps, failing tests should not be mixed with the successful ones.
### Source Code
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the following code should be updated to handle the new syntax:
- The function `aeso_syntax_utils:fold/4` in the file [aeso_syntax_utils.erl](src/aeso_syntax_utils.erl).
- Any related pretty printing function in the file [aeso_pretty.erl](src/aeso_pretty.erl), depending on the type of the newly added syntax.
## Checklist For Creating a Release
- Update the version in the file [aesophia.app.src](src/aesophia.app.src).
- Update the version in the file [rebar.config](rebar.config).
- In the [Changelog](CHANGELOG.md):
- Update the `Unreleased` changes to be under the new version.
- Update the version at the bottom of the file.
- Commit and the changes and create a new PR (check the commit of [v6.1.0](https://github.com/aeternity/aesophia/commit/5ad5270e381f6e810d7b8b5cdc168d283e7a90bb) for reference).
- Create a release after merging the new PR to `master` branch.
- After releasing `aesophia`, refer to each of the following repositories and create new releases as well, using the new `aesophia` release:
- [aesophia_cli](https://github.com/aeternity/aesophia_cli)
- [aesophia_http](https://github.com/aeternity/aesophia_http)
- [aerepl](https://github.com/aeternity/aerepl)
+1
View File
@@ -14,6 +14,7 @@ The compiler is currently being used three places
* [Features](docs/sophia_features.md)
* [Standard library](docs/sophia_stdlib.md)
* [Contract examples](docs/sophia_examples.md)
* [Contributing](CONTRIBUTING.md)
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
+2 -2
View File
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
}
]
},
"type_defs": [
"typedefs": [
{
"name": "answers",
"typedef": {
@@ -138,7 +138,7 @@ be included inside another contract.
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
typedefs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
+30
View File
@@ -51,6 +51,36 @@ The **pp_** options all print to standard output the following:
`pp_assembler` - print the generated assembler code
The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers.
#### Options to control which compiler optimizations should run:
By default all optimizations are turned on, to disable an optimization, it should be
explicitly set to false and passed as a compiler option.
List of optimizations:
- optimize_inliner
- optimize_inline_local_functions
- optimize_bind_subexpressions
- optimize_let_floating
- optimize_simplifier
- optimize_drop_unused_lets
- optimize_push_consume
- optimize_one_shot_var
- optimize_write_to_dead_var
- optimize_inline_switch_target
- optimize_swap_push
- optimize_swap_pop
- optimize_swap_write
- optimize_constant_propagation
- optimize_prune_impossible_branches
- optimize_single_successful_branch
- optimize_inline_store
- optimize_float_switch_bod
#### check_call(ContractString, Options) -> CheckRet
Types
+183
View File
@@ -134,6 +134,166 @@ main contract IntHolderFactory =
In case of a presence of child contracts (`IntHolder` in this case), the main
contract must be pointed out with the `main` keyword as shown in the example.
### Contract interfaces and polymorphism
Contracts can implement one or multiple interfaces, the contract has to define
every entrypoint from the implemented interface and the entrypoints in both
the contract and implemented interface should have compatible types.
```
contract interface Animal =
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "Cat sound"
```
Contract interfaces can extend other interfaces. An extended interface has to
declare all entrypoints from every parent interface. All the declarations in the extended
interface must have types compatible with the declarations from the parent
interface.
```
contract interface II =
entrypoint f : () => unit
contract interface I : II =
entrypoint f : () => unit
entrypoint g : () => unit
contract C : I =
entrypoint f() = ()
entrypoint g() = ()
```
It is only possible to implement (or extend) an interface that has been already
defined earlier in the file (or in an included file). Therefore recursive
interface implementation is not allowed in Sophia.
```
// The following code would show an error
contract interface X : Z =
entrypoint x : () => int
contract interface Y : X =
entrypoint x : () => int
entrypoint y : () => int
contract interface Z : Y =
entrypoint x : () => int
entrypoint y : () => int
entrypoint z : () => int
contract C : Z =
entrypoint x() = 1
entrypoint y() = 1
entrypoint z() = 1
```
#### Adding or removing modifiers
When a `contract` or a `contract interface` implements another `contract interface`, the `payable` and `stateful` modifiers can be kept or changed, both in the contract and in the entrypoints, according to the following rules:
1. A `payable` contract or interface can implement a `payable` interface or a non-`payable` interface.
2. A non-`payable` contract or interface can only implement a non-`payable` interface, and cannot implement a `payable` interface.
3. A `payable` entrypoint can implement a `payable` entrypoint or a non-`payable` entrypoint.
4. A non-`payable` entrypoint can only implement a non-`payable` entrypoint, and cannot implement a `payable` entrypoint.
5. A non-`stateful` entrypoint can implement a `stateful` entrypoint or a non-`stateful` entrypoint.
6. A `stateful` entrypoint can only implement a `stateful` entrypoint, and cannot implement a non-`stateful` entrypoint.
#### Subtyping and variance
Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)),
>Variance refers to how subtyping between more complex types relates to subtyping between their components.
This concept plays an important role in complex types such as tuples, `datatype`s and functions. Depending on the context, it can apply to positions in the structure of a type, or type parameters of generic types. There are four kinds of variances:
- covariant
- contravariant
- invariant
- bivariant
A type is said to be on a "covariant" position when it describes output or a result of some computation. Analogously, position is "contravariant" when it is an input, or a parameter. Intuitively, when a part of the type is produced by values of it, it is covariant. When it is consumed, it is contravariant. When a type appears to be simultaneously input and output, it is described as invariant. If a type is neither of those (that is, it's unused) it's bivariant. Furthermore, whenever a complex type appears on a contravariant position, all its covariant components become contravariant and vice versa.
Variance influences how subtyping is applied. Types on covariant positions are subtyped normally, while contravariant the opposite way. Invariant types have to be exactly the same in order for subtyping to work. Bivariant types are always compatible.
A good example of where it matters can be pictured by subtyping of function types. Let us assume there is a contract interface `Animal` and two contracts that implement it: `Dog` and `Cat`.
```sophia
contract interface Animal =
entrypoint age : () => int
contract Dog : Animal =
entrypoint age() = // ...
entrypoint woof() = "woof"
contract Cat : Animal =
entrypoint age() = // ...
entrypoint meow() = "meow"
```
The assumption of this exercise is that cats do not bark (because `Cat` does not define the `woof` entrypoint). If subtyping rules were applied naively, that is if we let `Dog => Dog` be a subtype of `Animal => Animal`, the following code would break:
```sophia
let f : (Dog) => string = d => d.woof()
let g : (Animal) => string = f
let c : Cat = Chain.create()
g(c) // Cat barking!
```
That is because arguments of functions are contravariant, as opposed to return the type which is covariant. Because of that, the assignment of `f` to `g` is invalid - while `Dog` is a subtype of `Animal`, `Dog => string` is **not** a subtype of `Animal => string`. However, `Animal => string` **is** a subtype of `Dog => string`. More than that, `(Dog => Animal) => Dog` is a subtype of `(Animal => Dog) => Animal`.
This has consequences on how user-defined generic types work. A type variable gains its variance from its role in the type definition as shown in the example:
```sophia
datatype co('a) = Co('a) // co is covariant on 'a
datatype ct('a) = Ct('a => unit) // ct is contravariant on 'a
datatype in('a) = In('a => 'a) // in is invariant on 'a
datatype bi('a) = Bi // bi is bivariant on 'a
```
The following facts apply here:
- `co('a)` is a subtype of `co('b)` when `'a` is a subtype of `'b`
- `ct('a)` is a subtype of `ct('b)` when `'b` is a subtype of `'a`
- `in('a)` is a subtype of `in('b)` when `'a` is equal to `'b`
- `bi('a)` is a subtype of `bi('b)` always
That altogether induce the following rules of subtyping in Sophia:
- A function type `(Args1) => Ret1` is a subtype of `(Args2) => Ret2` when `Ret1`
is a subtype of `Ret2` and each argument type from `Args2` is a subtype of its
counterpart in `Args1`.
- A list type `list(A)` is a subtype of `list(B)` if `A` is a subtype of `B`.
- An option type `option(A)` is a subtype of `option(B)` if `A` is a subtype of `B`.
- A map type `map(A1, A2)` is a subtype of `map(B1, B2)` if `A1` is a subtype
of `B1`, and `A2` is a subtype of `B2`.
- An oracle type `oracle(A1, A2)` is a subtype of `oracle(B1, B2)` if `B1` is
a subtype of `A1`, and `A2` is a subtype of `B2`.
- An oracle_query type `oracle_query(A1, A2)` is a subtype of `oracle_query(B1, B2)`
if `A1` is a subtype of `B1`, and `A2` is a subtype of `B2`.
- A user-defined datatype `t(Args1)` is a subtype of `t(Args2)`
- When a user-defined type `t('a)` is covariant in `'a`, then `t(A)` is a
subtype of `t(B)` when `A` is a subtype of `B`.
- When a user-defined type `t('a)` is contravariant in `'a`, then `t(A)` is a
subtype of `t(B)` when `B` is a subtype of `A`.
- When a user-defined type `t('a)` is binvariant in `'a`, then `t(A)` is a
subtype of `t(B)` when either `A` is a subtype of `B` or when `B` is a subtype
of `A`.
- When a user-defined type `t('a)` is invariant in `'a`, then `t(A)` can never be
a subtype of `t(B)`.
## Mutable state
@@ -400,6 +560,19 @@ Sophia has the following types:
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
## Hole expression
Hole expressions, written as `???`, are expressions that are used as a placeholder. During compilation, the compiler will generate a type error indication the type of the hole expression.
```
include "List.aes"
contract C =
entrypoint f() =
List.sum(List.map(???, [1,2,3]))
```
A hole expression found in the example above will generate the error `` Found a hole of type `(int) => int` ``. This says that the compiler expects a function from `int` to `int` in place of the `???` placeholder.
## Arithmetic
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
@@ -862,6 +1035,16 @@ function require(b : bool, err : string) =
if(!b) abort(err)
```
Aside from that, there is an almost equivalent function `exit`
```sophia
exit(reason : string) : 'a
```
Just like `abort`, it breaks the execution with the given reason. The difference
however is in the gas consumption — while `abort` returns unused gas, a call to
`exit` burns it all.
## Delegation signature
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
+1 -1
View File
@@ -483,7 +483,7 @@ The address of the account that mined the current block.
Chain.timestamp : int
```
The timestamp of the current block.
The timestamp of the current block (unix time, milliseconds).
##### difficulty
+31 -11
View File
@@ -10,8 +10,9 @@ and `*/` and can be nested.
### Keywords
```
contract elif else entrypoint false function if import include let mod namespace
private payable stateful switch true type record datatype main interface
contract include let switch type record datatype if elif else function
stateful payable true false mod public entrypoint private indexed namespace
interface main using as for hiding
```
### Tokens
@@ -91,18 +92,30 @@ A Sophia file consists of a sequence of *declarations* in a layout block.
```c
File ::= Block(TopDecl)
TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
| 'namespace' Con '=' Block(Decl)
| '@compiler' PragmaOp Version
| 'include' String
TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
| 'namespace' Con '=' Block(Decl)
| '@compiler' PragmaOp Version
| 'include' String
| Using
Implement ::= ':' Sep1(Con, ',')
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
| 'record' Id ['(' TVar* ')'] '=' RecordType
| 'datatype' Id ['(' TVar* ')'] '=' DataType
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
| Using
FunDecl ::= Id ':' Type // Type signature
| Id Args [':' Type] '=' Block(Stmt) // Definition
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
Using ::= 'using' Con ['as' Con] [UsingParts]
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
| 'hiding' '[' Sep1(Id, ',') ']'
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
Version ::= Sep1(Int, '.')
@@ -172,12 +185,17 @@ Stmt ::= 'switch' '(' Expr ')' Block(Case)
| 'elif' '(' Expr ')' Block(Stmt)
| 'else' Block(Stmt)
| 'let' LetDef
| Using
| Expr
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
| Pattern '=' Block(Stmt) // Value definition
Case ::= Pattern '=>' Block(Stmt)
| Pattern Block(GuardedCase)
GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)
Pattern ::= Expr
```
@@ -215,10 +233,12 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
| '[' Expr '..' Expr ']' // List range [1..n]
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
| '(' Expr ')' // Parens (1 + 2) * 3
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
| AccountAddress | ContractAddress // Chain identifiers
| OracleAddress | OracleQueryId // Chain identifiers
| '???' // Hole expression 1 + ???
Generator ::= Pattern '<-' Expr // Generator
| 'if' '(' Expr ')' // Guard
@@ -244,12 +264,12 @@ UnOp ::= '-' | '!'
| Operators | Type
| --- | ---
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
| `!` `&&` `\|\|` | logical operators
| `!` `&&` `||` | logical operators
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
| `::` `++` | list operators
| `\|>` | functional operators
| `|>` | functional operators
## Operator precendences
## Operator precedence
In order of highest to lowest precedence.
@@ -263,5 +283,5 @@ In order of highest to lowest precedence.
| `::` `++` | right
| `<` `>` `=<` `>=` `==` `!=` | none
| `&&` | right
| `\|\|` | right
| `\|>` | left
| `||` | right
| `|>` | left
+3 -4
View File
@@ -2,11 +2,10 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"0699f35"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}}
, {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
]}.
{dialyzer, [
@@ -15,7 +14,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "6.1.0"},
{relx, [{release, {aesophia, "7.1.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+8 -5
View File
@@ -1,11 +1,11 @@
{"1.1.0",
{"1.2.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
@@ -14,7 +14,7 @@
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
@@ -24,5 +24,8 @@
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
{pkg_hash_ext,[
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
].
BIN
View File
Binary file not shown.
+4 -4
View File
@@ -91,7 +91,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs},
C1 = C0#{typedefs => Tdefs},
C2 = case Es of
[] -> C1;
@@ -111,7 +111,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
typedefs => Tdefs}}.
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
@@ -234,7 +234,7 @@ do_render_aci_json(Json) ->
decode_contract(#{contract := #{name := Name,
kind := Kind,
payable := Payable,
type_defs := Ts0,
typedefs := Ts0,
functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
@@ -246,7 +246,7 @@ decode_contract(#{contract := #{name := Name,
end,
io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
decode_contract(#{namespace := #{name := Name, typedefs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
+392 -87
View File
@@ -14,8 +14,13 @@
-export([ infer/1
, infer/2
, unfold_types_in_type/2
, unfold_types_in_type/3
, switch_scope/2
, pp_type/2
, lookup_env1/4
, name/1
, qname/1
]).
-include("aeso_utils.hrl").
@@ -87,7 +92,11 @@
-type byte_constraint() :: {is_bytes, utype()}
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint().
-type aens_resolve_constraint() :: {aens_resolve_type, utype()}.
-type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}.
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint()
| aens_resolve_constraint() | oracle_type_constraint().
-record(field_info,
{ ann :: aeso_syntax:ann()
@@ -155,6 +164,10 @@
%% -- Environment manipulation -----------------------------------------------
-spec switch_scope(qname(), env()) -> env().
switch_scope(Scope, Env) ->
Env#env{namespace = Scope}.
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
push_scope(Kind, Con, Env) ->
Ann = aeso_syntax:get_ann(Con),
@@ -216,7 +229,7 @@ force_bind_fun(X, Type, Env = #env{ what = What }) ->
NoCode = get_option(no_code, false),
Entry = if X == "init", What == contract, not NoCode ->
{reserved_init, Ann, Type};
What == contract_interface -> {contract_fun, Ann, Type};
What == contract; What == contract_interface -> {contract_fun, Ann, Type};
true -> {Ann, Type}
end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
@@ -257,15 +270,24 @@ bind_state(Env) ->
false -> Env1
end.
-spec bind_field(name(), field_info(), env()) -> env().
bind_field(X, Info, Env = #env{ fields = Fields }) ->
-spec bind_field_append(name(), field_info(), env()) -> env().
bind_field_append(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
Env#env{ fields = Fields1 }.
-spec bind_fields([{name(), field_info()}], env()) -> env().
bind_fields([], Env) -> Env;
bind_fields([{Id, Info} | Rest], Env) ->
bind_fields(Rest, bind_field(Id, Info, Env)).
-spec bind_field_update(name(), field_info(), env()) -> env().
bind_field_update(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun([_ | Infos]) -> [Info | Infos]; ([]) -> [Info] end, [Info], Fields),
Env#env{ fields = Fields1 }.
-spec bind_fields([{name(), field_info()}], typed | untyped, env()) -> env().
bind_fields([], _Typing, Env) -> Env;
bind_fields([{Id, Info} | Rest], Typing, Env) ->
NewEnv = case Typing of
untyped -> bind_field_append(Id, Info, Env);
typed -> bind_field_update(Id, Info, Env)
end,
bind_fields(Rest, Typing, NewEnv).
%% Contract entrypoints take three named arguments
%% gas : int = Call.gas_left()
@@ -283,26 +305,27 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
Named("protected", Typed({bool, Ann, false}, Id("bool")))],
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
-spec bind_contract(typed | untyped, aeso_syntax:decl(), env()) -> env().
bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
when ?IS_CONTRACT_HEAD(Contract) ->
Key = name(Id),
Sys = [{origin, system}],
Fields =
Key = name(Id),
Sys = [{origin, system}],
TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|| {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++
[ {field_t, AnnF, Entrypoint,
contract_call_type(
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
{fun_t, AnnF, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)})
}
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents,
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents,
Name =/= "init"
] ++
%% Predefined fields
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
contract_call_type(
case [ [ArgT || {typed, _, _, ArgT} <- Args]
case [ [TypeOrFresh(Arg) || Arg <- Args]
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args
@@ -324,7 +347,7 @@ bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
record_t = Id }}
|| {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ],
bind_type(Key, Ann, {[], {contract_t, Fields}},
bind_fields(FieldInfo, Env)).
bind_fields(FieldInfo, Typing, Env)).
%% What scopes could a given name come from?
-spec possible_scopes(env(), qname()) -> [qname()].
@@ -399,10 +422,11 @@ lookup_env(Env, Kind, Ann, Name) ->
end
end.
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info() | type_info()}.
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
Qual = lists:droplast(QName),
Name = lists:last(QName),
QNameIsEvent = lists:suffix(["Chain", "event"], QName),
AllowPrivate = lists:prefix(Qual, Current),
%% Get the scope
case maps:get(Qual, Scopes, false) of
@@ -418,6 +442,8 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
{reserved_init, Ann1, Type} ->
type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
{contract_fun, Ann1, Type} when AllowPrivate orelse QNameIsEvent ->
{QName, {Ann1, Type}};
{contract_fun, Ann1, Type} ->
type_error({contract_treated_as_namespace, Ann, QName}),
{QName, {Ann1, Type}};
@@ -434,6 +460,9 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
end
end.
fun_arity({fun_t, _, _, Args, _}) -> length(Args);
fun_arity(_) -> none.
-spec lookup_record_field(env(), name()) -> [field_info()].
lookup_record_field(Env, FieldName) ->
maps:get(FieldName, Env#env.fields, []).
@@ -444,6 +473,11 @@ lookup_record_field(Env, FieldName, Kind) ->
[ Fld || Fld = #field_info{ kind = K } <- lookup_record_field(Env, FieldName),
Kind == project orelse K /= contract ].
lookup_record_field_arity(Env, FieldName, Arity, Kind) ->
Fields = lookup_record_field(Env, FieldName, Kind),
[ Fld || Fld = #field_info{ field_t = FldType } <- Fields,
fun_arity(dereference_deep(FldType)) == Arity ].
%% -- Name manipulation ------------------------------------------------------
-spec qname(type_id()) -> qname().
@@ -539,8 +573,9 @@ global_env() ->
%% TTL constructors
{"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)},
%% Abort
%% Abort/exit
{"abort", Fun1(String, A)},
{"exit", Fun1(String, A)},
{"require", Fun([Bool, String], Unit)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
@@ -823,6 +858,7 @@ infer(Contracts, Options) ->
ets_new(type_vars, [set]),
ets_new(warnings, [bag]),
ets_new(type_vars_variance, [set]),
ets_new(functions_to_implement, [set]),
%% Set the variance for builtin types
ets_insert(type_vars_variance, {"list", [covariant]}),
ets_insert(type_vars_variance, {"option", [covariant]}),
@@ -857,7 +893,7 @@ infer(Contracts, Options) ->
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
infer1(Env0, [{Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
infer1(Env0, [Contract0 = {Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
when ?IS_CONTRACT_HEAD(Contract) ->
%% do type inference on each contract independently.
Env = Env0#env{ contract_parents = maps:put(name(ConName),
@@ -873,12 +909,16 @@ infer1(Env0, [{Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
contract -> ets_insert(defined_contracts, {qname(ConName)});
contract_interface -> ok
end,
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
check_contract_preserved_payability(Env, ConName, Ann, Impls, Acc, What),
populate_functions_to_implement(Env, ConName, Impls, Acc),
Env1 = bind_contract(untyped, Contract0, Env),
{Env2, Code1} = infer_contract_top(push_scope(contract, ConName, Env1), What, Code, Options),
report_unimplemented_functions(Env1, ConName),
Contract1 = {Contract, Ann, ConName, Impls, Code1},
check_implemented_interfaces(Env1, Contract1, Acc),
Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options);
Env3 = pop_scope(Env2),
%% Rebinding because the qualifications of types are added during type inference. Could we do better?
Env4 = bind_contract(typed, Contract1, Env3),
infer1(Env4, Rest, [Contract1 | Acc], Options);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
when_warning(warn_unused_includes,
fun() ->
@@ -895,54 +935,71 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
%% Pragmas are checked in check_modifiers
infer1(Env, Rest, Acc, Options).
check_implemented_interfaces(Env, {_Contract, _Ann, ConName, Impls, Code}, DefinedContracts) ->
-spec check_contract_preserved_payability(env(), Con, Ann, Impls, Contracts, Kind) -> ok | no_return() when
Con :: aeso_syntax:con(),
Ann :: aeso_syntax:ann(),
Impls :: [Con],
Contracts :: [aeso_syntax:decl()],
Kind :: contract | contract_interface.
check_contract_preserved_payability(Env, ContractName, ContractAnn, Impls, DefinedContracts, Kind) ->
Payable = proplists:get_value(payable, ContractAnn, false),
ImplsNames = [ name(I) || I <- Impls ],
Interfaces = [ Con || I = {contract_interface, _, Con, _, _} <- DefinedContracts,
lists:member(name(Con), ImplsNames),
aeso_syntax:get_ann(payable, I, false) ],
create_type_errors(),
AllInterfaces = [{name(IName), I} || I = {contract_interface, _, IName, _, _} <- DefinedContracts],
ImplsNames = lists:map(fun name/1, Impls),
[ type_error({unpreserved_payablity, Kind, ContractName, I}) || I <- Interfaces, Payable == false ],
destroy_and_report_type_errors(Env),
%% All implemented intrefaces should already be defined
lists:foreach(fun(Impl) -> case proplists:get_value(name(Impl), AllInterfaces) of
undefined -> type_error({referencing_undefined_interface, Impl});
_ -> ok
end
end, Impls),
ok.
ImplementedInterfaces = [I || I <- [proplists:get_value(Name, AllInterfaces) || Name <- ImplsNames],
I /= undefined],
Funs = [ Fun || Fun <- Code,
element(1, Fun) == letfun orelse element(1, Fun) == fun_decl ],
check_implemented_interfaces1(Env, ImplementedInterfaces, ConName, Funs, AllInterfaces),
%% Report all functions that were not implemented by the contract ContractName.
-spec report_unimplemented_functions(env(), ContractName) -> ok | no_return() when
ContractName :: aeso_syntax:con().
report_unimplemented_functions(Env, ContractName) ->
create_type_errors(),
[ type_error({unimplemented_interface_function, ContractName, name(I), FunName})
|| {FunName, I, _} <- ets_tab2list(functions_to_implement) ],
destroy_and_report_type_errors(Env).
%% Recursively check that all directly and indirectly referenced interfaces are implemented
check_implemented_interfaces1(_, [], _, _, _) ->
ok;
check_implemented_interfaces1(Env, [{contract_interface, _, IName, _, Decls} | Interfaces],
ConId, Impls, AllInterfaces) ->
Unmatched = match_impls(Env, Decls, ConId, name(IName), Impls),
check_implemented_interfaces1(Env, Interfaces, ConId, Unmatched, AllInterfaces).
%% Return a list of all function declarations to be implemented, given the list
%% of interfaces to be implemented Impls and all the previously defined
%% contracts DefinedContracts>
-spec functions_to_implement(Impls, DefinedContracts) -> [{InterfaceCon, FunDecl}] when
Impls :: [aeso_syntax:con()],
DefinedContracts :: [aeso_syntax:decl()],
InterfaceCon :: aeso_syntax:con(),
FunDecl :: aeso_syntax:fundecl().
functions_to_implement(Impls, DefinedContracts) ->
ImplsNames = [ name(I) || I <- Impls ],
Interfaces = [ I || I = {contract_interface, _, Con, _, _} <- DefinedContracts,
lists:member(name(Con), ImplsNames) ],
%% Match the functions of the contract with the interfaces functions, and return unmatched functions
match_impls(_, [], _, _, Impls) ->
Impls;
match_impls(Env, [{fun_decl, _, {id, _, FunName}, FunType = {fun_t, _, _, ArgsTypes, RetDecl}} | Decls], ConId, IName, Impls) ->
Match = fun({letfun, _, {id, _, FName}, Args, RetFun, _}) when FName == FunName ->
length(ArgsTypes) == length(Args) andalso
unify(Env#env{unify_throws = false}, RetDecl, RetFun, unknown) andalso
lists:all(fun({T1, {typed, _, _, T2}}) -> unify(Env#env{unify_throws = false}, T1, T2, unknown) end,
lists:zip(ArgsTypes, Args));
({fun_decl, _, {id, _, FName}, FunT}) when FName == FunName ->
unify(Env#env{unify_throws = false}, FunT, FunType, unknown);
(_) -> false
end,
UnmatchedImpls = case lists:search(Match, Impls) of
{value, V} ->
lists:delete(V, Impls);
false ->
type_error({unimplemented_interface_function, ConId, IName, FunName}),
Impls
end,
match_impls(Env, Decls, ConId, IName, UnmatchedImpls).
%% All implemented intrefaces should already be defined
InterfacesNames = [name(element(3, I)) || I <- Interfaces],
[ begin
Found = lists:member(name(Impl), InterfacesNames),
Found orelse type_error({referencing_undefined_interface, Impl})
end || Impl <- Impls
],
lists:flatten([ [ {Con, Decl} || Decl <- Decls] || {contract_interface, _, Con, _, Decls} <- Interfaces ]).
%% Fill the ets table functions_to_implement with functions from the implemented
%% interfaces Impls.
-spec populate_functions_to_implement(env(), ContractName, Impls, DefinedContracts) -> ok | no_return() when
ContractName :: aeso_syntax:con(),
Impls :: [aeso_syntax:con()],
DefinedContracts :: [aeso_syntax:decl()].
populate_functions_to_implement(Env, ContractName, Impls, DefinedContracts) ->
create_type_errors(),
[ begin
Inserted = ets_insert_new(functions_to_implement, {name(Id), I, Decl}),
[{_, I2, _}] = ets_lookup(functions_to_implement, name(Id)),
Inserted orelse type_error({interface_implementation_conflict, ContractName, I, I2, Id})
end || {I, Decl = {fun_decl, _, Id, _}} <- functions_to_implement(Impls, DefinedContracts) ],
destroy_and_report_type_errors(Env).
%% Asserts that the main contract is somehow defined.
identify_main_contract(Contracts, Options) ->
@@ -1013,6 +1070,9 @@ infer_contract(Env0, What, Defs0, Options) ->
contract -> bind_state(Env1) %% bind state and put
end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
[ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls,
What =:= contract,
get_option(no_code, false) =:= false ],
Env3 = bind_funs(ProtoSigs, Env2),
Functions = Get(function, Defs),
%% Check for duplicates in Functions (we turn it into a map below)
@@ -1027,8 +1087,10 @@ infer_contract(Env0, What, Defs0, Options) ->
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Remove namespaces used in the current namespace
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
%% Check that `init` doesn't read or write the state and that `init` is not missing
check_state(Env4, Defs1),
%% Check that entrypoints have first-order arg types and return types
check_entrypoints(Defs1),
destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}.
@@ -1095,6 +1157,7 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
case maps:get(Name, TypeMap, undefined) of
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
{type_def, Ann, D, Xs, Def0} ->
check_parameterizable(D, Xs),
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
Env1 = bind_type(Name, Ann, {Xs, Def}, Env),
@@ -1194,6 +1257,10 @@ check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con,
create_type_errors(),
type_error({using_undefined_namespace, Ann, qname(Con)}),
destroy_and_report_type_errors(Env);
#scope{kind = contract} ->
create_type_errors(),
type_error({using_undefined_namespace, Ann, qname(Con)}),
destroy_and_report_type_errors(Env);
Scope ->
Nsp = case Parts of
none ->
@@ -1347,9 +1414,16 @@ check_named_arg(Env, {named_arg_t, Ann, Id, Type, Default}) ->
-spec check_fields(env(), #{ name() => aeso_syntax:decl() }, type(), [aeso_syntax:field_t()]) -> env().
check_fields(Env, _TypeMap, _, []) -> Env;
check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
Env1 = bind_field(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
Env1 = bind_field_append(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
check_fields(Env1, TypeMap, RecTy, Fields).
check_parameterizable({id, Ann, "event"}, [_ | _]) ->
type_error({parameterized_event, Ann});
check_parameterizable({id, Ann, "state"}, [_ | _]) ->
type_error({parameterized_state, Ann});
check_parameterizable(_Name, _Xs) ->
ok.
check_event(Env, "event", Ann, Def) ->
case Def of
{variant_t, Cons} ->
@@ -1439,15 +1513,50 @@ check_reserved_entrypoints(Funs) ->
-spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}.
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
{{Name, {type_sig, Ann, none, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
TypeSig = {type_sig, Ann, none, Named, Args, Ret},
register_implementation(Id, TypeSig),
{{Name, TypeSig}, {fun_decl, Ann, Id, Type1}};
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
type_error({fundecl_must_have_funtype, Ann, Id, Type}),
{{Name, {type_sig, Ann, none, [], [], Type}}, check_type(Env, Type)}.
%% Register the function FunId as implemented by deleting it from the functions
%% to be implemented table if it is included there, or return true otherwise.
-spec register_implementation(FunId, FunSig) -> true | no_return() when
FunId :: aeso_syntax:id(),
FunSig :: typesig().
register_implementation(Id, Sig) ->
Name = name(Id),
case ets_lookup(functions_to_implement, Name) of
[{Name, Interface, Decl = {fun_decl, _, DeclId, _}}] ->
DeclStateful = aeso_syntax:get_ann(stateful, Decl, false),
DeclPayable = aeso_syntax:get_ann(payable, Decl, false),
SigEntrypoint = aeso_syntax:get_ann(entrypoint, Sig, false),
SigStateful = aeso_syntax:get_ann(stateful, Sig, false),
SigPayable = aeso_syntax:get_ann(payable, Sig, false),
[ type_error({function_should_be_entrypoint, Id, DeclId, Interface})
|| not SigEntrypoint ],
[ type_error({entrypoint_cannot_be_stateful, Id, DeclId, Interface})
|| SigStateful andalso not DeclStateful ],
[ type_error({entrypoint_must_be_payable, Id, DeclId, Interface})
|| not SigPayable andalso DeclPayable ],
ets_delete(functions_to_implement, Name);
[] ->
true;
_ ->
error("Ets set has multiple keys")
end.
infer_nonrec(Env, LetFun) ->
create_constraints(),
NewLetFun = infer_letfun(Env, LetFun),
NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun),
check_special_funs(Env, NewLetFun),
register_implementation(get_letfun_id(LetFun), Sig),
solve_then_destroy_and_report_unsolved_constraints(Env),
Result = {TypeSig, _} = instantiate(NewLetFun),
print_typesig(TypeSig),
@@ -1476,7 +1585,8 @@ infer_letrec(Env, Defs) ->
ExtendEnv = bind_funs(Funs, Env),
Inferred =
[ begin
Res = {{Name, TypeSig}, _} = infer_letfun(ExtendEnv, LF),
Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF),
register_implementation(get_letfun_id(LetFun), TypeSig),
Got = proplists:get_value(Name, Funs),
Expect = typesig_to_fun_t(TypeSig),
unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}),
@@ -1528,6 +1638,9 @@ infer_letfun1(Env0 = #env{ namespace = NS }, {letfun, Attrib, Fun = {id, NameAtt
{{Name, TypeSig},
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}.
get_letfun_id({fun_clauses, _, Id, _, _}) -> Id;
get_letfun_id({letfun, _, Id, _, _, _}) -> Id.
desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
NeedDesugar =
case Clauses of
@@ -1568,13 +1681,13 @@ app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}.
lookup_name(Env, As, Name) ->
lookup_name(Env, As, Name, []).
lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} = CurFn }, As, Id, Options) ->
lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Options) ->
case lookup_env(Env, term, As, qname(Id)) of
false ->
type_error({unbound_variable, Id}),
{Id, fresh_uvar(As)};
{QId, {_, Ty}} ->
when_warning(warn_unused_variables, fun() -> used_variable(NS, Fun, QId) end),
when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end),
when_warning(warn_unused_functions,
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
Freshen = proplists:get_value(freshen, Options, false),
@@ -1605,15 +1718,63 @@ check_stateful(#env { current_function = Fun }, _Id, _Type) ->
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
%% works since the user can't create functions with named arguments.
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
check_stateful_named_arg(#env{ stateful = Stateful, current_function = Fun }, {id, _, "value"}, Default) ->
case Default of
{int, _, 0} -> ok;
_ -> type_error({value_arg_not_allowed, Default, Fun})
_ ->
case Stateful of
true -> when_warning(warn_unused_stateful, fun() -> used_stateful(Fun) end);
false -> type_error({value_arg_not_allowed, Default, Fun})
end
end;
check_stateful_named_arg(_, _, _) -> ok.
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env, Defs) ->
check_entrypoints(Defs) ->
[ ensure_first_order_entrypoint(LetFun)
|| LetFun <- Defs,
aeso_syntax:get_ann(entrypoint, LetFun, false),
get_option(allow_higher_order_entrypoints, false) =:= false ].
ensure_first_order_entrypoint({letfun, Ann, Id = {id, _, Name}, Args, Ret, _}) ->
[ ensure_first_order(ArgType, {higher_order_entrypoint, AnnArg, Id, {argument, ArgId, ArgType}})
|| {typed, AnnArg, ArgId, ArgType} <- Args ],
[ ensure_first_order(Ret, {higher_order_entrypoint, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_first_order(Type, Err) ->
is_first_order(Type) orelse type_error(Err).
is_first_order({fun_t, _, _, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
ensure_monomorphic(Type, Err) ->
is_monomorphic(Type) orelse type_error(Err).
is_monomorphic({tvar, _, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
check_state_init(Env) ->
Top = Env#env.namespace,
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
case unfold_types_in_type(Env, StateType) of
false ->
ok;
{_, {_, {_, {alias_t, {tuple_t, _, []}}}}} -> %% type state = ()
ok;
_ ->
#scope{ ann = AnnCon } = get_scope(Env, Top),
type_error({missing_init_function, {con, AnnCon, lists:last(Top)}})
end.
%% Check that `init` doesn't read or write the state and that `init` is defined
%% when the state type is not unit
check_state(Env, Defs) ->
Top = Env#env.namespace,
GetState = Top ++ ["state"],
SetState = Top ++ ["put"],
@@ -1622,7 +1783,7 @@ check_state_dependencies(Env, Defs) ->
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of
false -> ok; %% No init, so nothing to check
false -> get_option(no_code, false) orelse check_state_init(Env);
_ ->
[ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ],
@@ -1682,6 +1843,10 @@ infer_expr(_Env, Body={contract_pubkey, As, _}) ->
{typed, As, Body, Con};
infer_expr(_Env, Body={id, As, "_"}) ->
{typed, As, Body, fresh_uvar(As)};
infer_expr(_Env, Body={id, As, "???"}) ->
T = fresh_uvar(As),
type_error({hole_found, As, T}),
{typed, As, Body, T};
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
{QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type};
@@ -1764,12 +1929,17 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
NewFun0 = infer_expr(Env, Fun),
NewArgs = [infer_expr(Env, A) || A <- Args],
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
NewFun1 = {typed, _, FunName, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
GeneralResultType = fresh_uvar(Ann),
ResultType = fresh_uvar(Ann),
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
[ add_constraint({aens_resolve_type, GeneralResultType})
|| element(3, FunName) =:= ["AENS", "resolve"] ],
[ add_constraint({oracle_type, Ann, OType})
|| OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)],
OType =/= false ],
add_constraint(
#dependent_type_constraint{ named_args_t = NamedArgsVar,
named_args = NamedArgs1,
@@ -2128,7 +2298,7 @@ next_count() ->
ets_tables() ->
[options, type_vars, constraints, freshen_tvars, type_errors,
defined_contracts, warnings, function_calls, all_functions,
type_vars_variance].
type_vars_variance, functions_to_implement].
clean_up_ets() ->
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
@@ -2164,10 +2334,18 @@ ets_delete(Name) ->
put(aeso_ast_infer_types, maps:remove(Name, Tabs)),
ets:delete(TabId).
ets_delete(Name, Key) ->
TabId = ets_tabid(Name),
ets:delete(TabId, Key).
ets_insert(Name, Object) ->
TabId = ets_tabid(Name),
ets:insert(TabId, Object).
ets_insert_new(Name, Object) ->
TabId = ets_tabid(Name),
ets:insert_new(TabId, Object).
ets_lookup(Name, Key) ->
TabId = ets_tabid(Name),
ets:lookup(TabId, Key).
@@ -2234,7 +2412,12 @@ solve_constraints(Env) ->
field_t = FieldType,
kind = Kind,
context = When }) ->
case lookup_record_field(Env, FieldName, Kind) of
Arity = fun_arity(dereference_deep(FieldType)),
FieldInfos = case Arity of
none -> lookup_record_field(Env, FieldName, Kind);
_ -> lookup_record_field_arity(Env, FieldName, Arity, Kind)
end,
case FieldInfos of
[] ->
type_error({undefined_field, Field}),
false;
@@ -2290,11 +2473,19 @@ destroy_and_report_unsolved_constraints(Env) ->
(#named_argument_constraint{}) -> true;
(_) -> false
end, OtherCs2),
{BytesCs, []} =
{BytesCs, OtherCs4} =
lists:partition(fun({is_bytes, _}) -> true;
({add_bytes, _, _, _, _, _}) -> true;
(_) -> false
end, OtherCs3),
{AensResolveCs, OtherCs5} =
lists:partition(fun({aens_resolve_type, _}) -> true;
(_) -> false
end, OtherCs4),
{OracleTypeCs, []} =
lists:partition(fun({oracle_type, _, _}) -> true;
(_) -> false
end, OtherCs5),
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
S == unsolved ],
@@ -2312,9 +2503,20 @@ destroy_and_report_unsolved_constraints(Env) ->
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
check_bytes_constraints(Env, BytesCs),
check_aens_resolve_constraints(Env, AensResolveCs),
check_oracle_type_constraints(Env, OracleTypeCs),
destroy_constraints().
get_oracle_type({qid, _, ["Oracle", "register"]}, _ , OType) -> OType;
get_oracle_type({qid, _, ["Oracle", "query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_question"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_answer"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check_query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "respond"]}, [OType| _], _ ) -> OType;
get_oracle_type(_Fun, _Args, _Ret) -> false.
%% -- Named argument constraints --
%% If false, a type error has been emitted, so it's safe to drop the constraint.
@@ -2441,6 +2643,32 @@ check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
end.
check_aens_resolve_constraints(_Env, []) ->
ok;
check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
{app_t, _, {id, _, "option"}, [Type2]} = Type1,
case Type2 of
{id, _, "string"} -> ok;
{id, _, "address"} -> ok;
{con, _, _} -> ok;
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
_ -> type_error({invalid_aens_resolve_type, aeso_syntax:get_ann(Type), Type2})
end,
check_aens_resolve_constraints(Env, Rest).
check_oracle_type_constraints(_Env, []) ->
ok;
check_oracle_type_constraints(Env, [{oracle_type, Ann, OType} | Rest]) ->
Type = unfold_types_in_type(Env, instantiate(OType)),
{app_t, _, {id, _, "oracle"}, [QType, RType]} = Type,
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
check_oracle_type_constraints(Env, Rest).
%% -- Field constraints --
check_record_create_constraints(_, []) -> ok;
@@ -2684,6 +2912,8 @@ unify0(Env, A, B, Variance, When) ->
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
true;
unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(Env, {uvar, A, R}, T, _Variance, When) ->
case occurs_check(R, T) of
true ->
@@ -2707,6 +2937,12 @@ unify1(Env, [A|B], [C|D], Variance, When) ->
unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When);
unify1(_Env, X, X, _Variance, _When) ->
true;
unify1(_Env, _A, {id, _, "void"}, Variance, _When)
when Variance == covariant orelse Variance == bivariant ->
true;
unify1(_Env, {id, _, "void"}, _B, Variance, _When)
when Variance == contravariant orelse Variance == bivariant ->
true;
unify1(_Env, {id, _, Name}, {id, _, Name}, _Variance, _When) ->
true;
unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) ->
@@ -3164,6 +3400,9 @@ mk_error({cannot_unify, A, B, Cxt, When}) ->
[pp(instantiate(A)), pp(instantiate(B))]),
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({hole_found, Ann, Type}) ->
Msg = io_lib:format("Found a hole of type `~s`", [pp(instantiate(Type))]),
mk_t_err(pos(Ann), Msg);
mk_error({unbound_variable, Id}) ->
Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]),
case Id of
@@ -3447,7 +3686,7 @@ mk_error({multiple_main_contracts, Ann}) ->
Msg = "Only one main contract can be defined.",
mk_t_err(pos(Ann), Msg);
mk_error({unify_varargs, When}) ->
Msg = "Cannot unify variable argument list.",
Msg = "Cannot infer types for variable argument list.",
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({clone_no_contract, Ann}) ->
@@ -3479,11 +3718,77 @@ mk_error({empty_record_definition, Ann, Name}) ->
Msg = io_lib:format("Empty record definitions are not allowed. Cannot define the record `~s`", [Name]),
mk_t_err(pos(Ann), Msg);
mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
Msg = io_lib:format("Unimplemented function `~s` from the interface `~s` in the contract `~s`", [FunName, InterfaceName, pp(ConId)]),
Msg = io_lib:format("Unimplemented entrypoint `~s` from the interface `~s` in the contract `~s`", [FunName, InterfaceName, pp(ConId)]),
mk_t_err(pos(ConId), Msg);
mk_error({referencing_undefined_interface, InterfaceId}) ->
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
mk_t_err(pos(InterfaceId), Msg);
mk_error({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function `~s`", [name(Id)]),
mk_t_err(pos(Id), Msg);
mk_error({parameterized_state, Ann}) ->
Msg = "The state type cannot be parameterized",
mk_t_err(pos(Ann), Msg);
mk_error({parameterized_event, Ann}) ->
Msg = "The event type cannot be parameterized",
mk_t_err(pos(Ann), Msg);
mk_error({missing_init_function, Con}) ->
Msg = io_lib:format("Missing `init` function for the contract `~s`.", [name(Con)]),
Cxt = "The `init` function can only be omitted if the state type is `unit`",
mk_t_err(pos(Con), Msg, Cxt);
mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
What = "higher-order (contains function types)",
ThingS = case Thing of
{argument, X, T} -> io_lib:format("argument\n~s`\n", [pp_typed(" `", X, T)]);
{result, T} -> io_lib:format("return type\n~s`\n", [pp_type(" `", T)])
end,
Bad = case Thing of
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint `~s` ~s",
[ThingS, Name, Bad]),
mk_t_err(pos(Ann), Msg);
mk_error({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of `AENS.resolve`:\n"
"~s`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)",
[pp_type(" `", T)]),
mk_t_err(pos(Ann), Msg);
mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s`", [pp_type(" `", Type)]),
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({interface_implementation_conflict, Contract, I1, I2, Fun}) ->
Msg = io_lib:format("Both interfaces `~s` and `~s` implemented by "
"the contract `~s` have a function called `~s`",
[name(I1), name(I2), name(Contract), name(Fun)]),
mk_t_err(pos(Contract), Msg);
mk_error({function_should_be_entrypoint, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` must be declared as an entrypoint instead of a function "
"in order to implement the entrypoint `~s` from the interface `~s`",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({entrypoint_cannot_be_stateful, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` cannot be stateful because the entrypoint `~s` in the "
"interface `~s` is not stateful",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({entrypoint_must_be_payable, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` must be payable because the entrypoint `~s` in the "
"interface `~s` is payable",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
KindStr = case Kind of
contract -> "contract";
contract_interface -> "interface"
end,
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
[KindStr, name(ContractCon), name(InterfaceCon)]),
mk_t_err(pos(ContractCon), Msg);
mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg).
+79 -156
View File
@@ -75,7 +75,6 @@
| {switch, fsplit()}
| {set_state, state_reg(), fexpr()}
| {get_state, state_reg()}
| {loop, fexpr(), var_name(), fexpr()} | {continue, fexpr()} | {break, fexpr()}
%% The following (unapplied top-level functions/builtins and
%% lambdas) are generated by the fcode compiler, but translated
%% to closures by the lambda lifter.
@@ -150,17 +149,18 @@
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
child_con_env := child_con_env(),
event_type => aeso_syntax:typedef(),
builtins := builtins(),
options := [option()],
state_layout => state_layout(),
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() }
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
child_con_env := child_con_env(),
event_type => aeso_syntax:typedef(),
builtins := builtins(),
options := [option()],
state_layout => state_layout(),
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() },
saved_fresh_names => #{ var_name() => var_name() }
}.
-define(HASH_BYTES, 32).
@@ -171,7 +171,7 @@
%% and produces Fate intermediate code.
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
ast_to_fcode(Code, Options) ->
init_fresh_names(),
init_fresh_names(Options),
{Env1, FCode1} = to_fcode(init_env(Options), Code),
FCode2 = optimize(FCode1, Options),
Env2 = Env1#{ child_con_env :=
@@ -179,13 +179,18 @@ ast_to_fcode(Code, Options) ->
fun (_, FC) -> optimize(FC, Options) end,
maps:get(child_con_env, Env1)
)},
clear_fresh_names(),
{Env2, FCode2}.
Env3 =
case proplists:get_value(debug_info, Options, false) of
true -> Env2#{ saved_fresh_names => get(saved_fresh_names) };
false -> Env2
end,
clear_fresh_names(Options),
{Env3, FCode2}.
optimize(FCode1, Options) ->
Verbose = lists:member(pp_fcode, Options),
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
FCode2 = optimize_fcode(FCode1),
FCode2 = optimize_fcode(FCode1, Options),
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
FCode3 = lambda_lift(FCode2),
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
@@ -243,7 +248,7 @@ builtins() ->
MkName = fun(NS, Fun) ->
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
end,
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
@@ -327,7 +332,7 @@ get_option(Opt, Env, Default) ->
%% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
when ?IS_CONTRACT_HEAD(Contract) ->
case Contract =:= contract_interface of
false ->
@@ -350,7 +355,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
event_type => EventType,
payable => Payable,
functions => add_init_function(
Env1, Con, StateType,
Env1,
add_event_function(Env1, EventType, Funs)) },
case Contract of
contract_main -> [] = Rest, {Env1, ConFcode};
@@ -363,8 +368,6 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
to_fcode(Env1, Rest)
end;
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
fcode_error({last_declaration_must_be_main_contract, NotMain});
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
to_fcode(Env1, Code).
@@ -378,22 +381,15 @@ decls_to_fcode(Env, Decls) ->
end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of
false -> fcode_error({missing_definition, Id});
true -> Env
end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs,
args => FArgs,
return => FRet,
@@ -402,8 +398,7 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Ar
Env#{ functions := NewFuns }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
Q = qname(Env, Name),
FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
@@ -467,14 +462,6 @@ compute_state_layout(R, [H | T]) ->
compute_state_layout(R, _) ->
{R + 1, {reg, R}}.
check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id});
_ -> ok
end;
check_state_and_event_types(_, _, _) -> ok.
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type).
@@ -492,8 +479,6 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
{bytes, N};
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
fcode_error({found_void, Ann});
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
maps:get(X, Sub, {tvar, X});
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
@@ -555,7 +540,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
expr_to_fcode(Env, Type, {qid, _, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
@@ -566,13 +551,11 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(Ann, OType, QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
validate_aens_resolve_type(Ann, ResType, AensType),
TypeArgs = [{lit, {typerep, AensType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = bytes_split, Ar} ->
@@ -652,22 +635,11 @@ expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Rec, Fields}) ->
expr_to_fcode(Env, _Type, {list, _, Es}) ->
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
nil, Es);
expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) ->
AV = fresh_name(), % var to keep B
WithA = fun(X) -> {'let', AV, expr_to_fcode(Env, A), X} end,
St = fresh_name(), % loop state
ItProj = {proj, {var, St}, 1},
AcProj = {proj, {var, St}, 0},
Init = {tuple, [nil, expr_to_fcode(Env, B)]},
Loop = {loop, Init, St,
make_if(
{op, '>=', [ItProj, {var, AV}]},
{continue, {tuple, [{op, '::', [ItProj, AcProj]},
{op, '-', [ItProj, {lit, {int, 1}}]}
]}},
{break, AcProj}
)},
WithA(Loop);
{def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]),
{def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) ->
{op, '::', [expr_to_fcode(Env, Yield), nil]};
expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) ->
@@ -727,7 +699,7 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end;
%% Function calls
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) ->
expr_to_fcode(Env, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of
@@ -836,53 +808,6 @@ get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
validate_oracle_type(Ann, Type, QType, RType) ->
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
ok.
validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
case FType of
string -> ok;
address -> ok;
contract -> ok;
{oracle, _, _} -> ok;
oracle_query -> ok;
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {{typed, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
[ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_monomorphic(Type, Err) ->
case is_monomorphic(Type) of
true -> ok;
false -> fcode_error(Err)
end.
ensure_first_order(Type, Err) ->
case is_first_order(Type) of
true -> ok;
false -> fcode_error(Err)
end.
is_monomorphic({tvar, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
is_first_order({function, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
%% -- Pattern matching --
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
@@ -1215,11 +1140,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
%% -- Init function --
add_init_function(Env, Main, StateType, Funs0) ->
add_init_function(Env, Funs0) ->
case is_no_code(Env) of
true -> Funs0;
false ->
Funs = add_default_init_function(Env, Main, StateType, Funs0),
Funs = add_default_init_function(Env, Funs0),
InitName = {entrypoint, <<"init">>},
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
@@ -1227,16 +1152,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
Funs1
end.
add_default_init_function(_Env, Main, StateType, Funs) ->
add_default_init_function(_Env, Funs) ->
InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of
%% Only add default init function if state is unit.
none when StateType == {tuple, []} ->
none ->
Funs#{ InitName => #{attrs => [],
args => [],
return => {tuple, []},
body => {tuple, []}} };
none -> fcode_error({missing_init_function, Main});
_ -> Funs
end.
@@ -1350,9 +1273,6 @@ lambda_lift_expr(Layout, Expr) ->
{proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I};
{set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)};
{op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)};
{loop, Init, I, Body} -> {loop, lambda_lift_expr(Layout, Init), I, lambda_lift_expr(Layout, Body)};
{break, E} -> {break, lambda_lift_expr(Layout, E)};
{continue, E} -> {continue, lambda_lift_expr(Layout, E)};
{'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)};
{funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)};
{set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)};
@@ -1373,20 +1293,28 @@ lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As].
%% - Constant propagation
%% - Inlining
-spec optimize_fcode(fcode()) -> fcode().
optimize_fcode(Code = #{ functions := Funs }) ->
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) },
-spec optimize_fcode(fcode(), [option()]) -> fcode().
optimize_fcode(Code = #{ functions := Funs }, Options) ->
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def, Options) end, Funs) },
eliminate_dead_code(Code1).
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body }) ->
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]),
Def#{ body := drop_unused_lets(
simplifier(
let_floating(
bind_subexpressions(
inline_local_functions(
inliner(Fcode, Fun, Body)))))) }.
-spec optimize_fun(fcode(), fun_name(), fun_def(), [option()]) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) ->
Inliner = proplists:get_value(optimize_inliner, Options, true),
InlineLocalFunctions = proplists:get_value(optimize_inline_local_functions, Options, true),
BindSubexpressions = proplists:get_value(optimize_bind_subexpressions, Options, true),
LetFloating = proplists:get_value(optimize_let_floating, Options, true),
Simplifier = proplists:get_value(optimize_simplifier, Options, true),
DropUnusedLets = proplists:get_value(optimize_drop_unused_lets, Options, true),
Body1 = if Inliner -> inliner (Fcode, Fun, Body0); true -> Body0 end,
Body2 = if InlineLocalFunctions -> inline_local_functions(Body1); true -> Body1 end,
Body3 = if BindSubexpressions -> bind_subexpressions (Body2); true -> Body2 end,
Body4 = if LetFloating -> let_floating (Body3); true -> Body3 end,
Body5 = if Simplifier -> simplifier (Body4); true -> Body4 end,
Body6 = if DropUnusedLets -> drop_unused_lets (Body5); true -> Body5 end,
Def#{ body := Body6 }.
%% --- Inlining ---
@@ -1668,9 +1596,6 @@ read_only({switch, Split}) -> read_only(Split);
read_only({split, _, _, Cases}) -> read_only(Cases);
read_only({nosplit, E}) -> read_only(E);
read_only({'case', _, Split}) -> read_only(Split);
read_only({loop, Init, _, Body}) -> read_only(Init) andalso read_only(Body);
read_only({break, E}) -> read_only(E);
read_only({continue, E}) -> read_only(E);
read_only({'let', _, A, B}) -> read_only([A, B]);
read_only({funcall, _, _}) -> false;
read_only({closure, _, _}) -> internal_error(no_closures_here);
@@ -1809,12 +1734,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
{{Fun, Ar}, _} -> {def_u, Fun, Ar}
end.
init_fresh_names() ->
init_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(),
put('%fresh', 0).
clear_fresh_names() ->
clear_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(),
erase('%fresh').
init_saved_fresh_names() ->
put(saved_fresh_names, #{}).
clear_saved_fresh_names() ->
erase(saved_fresh_names).
-spec fresh_name_save(string()) -> var_name().
fresh_name_save(Name) ->
Fresh = fresh_name(),
case get(saved_fresh_names) of
undefined -> ok;
Old -> put(saved_fresh_names, Old#{Fresh => Name})
end,
Fresh.
-spec fresh_name() -> var_name().
fresh_name() -> fresh_name("%").
@@ -1868,9 +1810,6 @@ free_vars(Expr) ->
{proj, A, _} -> free_vars(A);
{set_proj, A, _, B} -> free_vars([A, B]);
{op, _, As} -> free_vars(As);
{loop, Init, Var, Body} -> free_vars(Init) ++ (free_vars(Body) -- [Var]);
{break, E} -> free_vars(E);
{continue, E} -> free_vars(E);
{'let', X, A, B} -> free_vars([A, {lam, [X], B}]);
{funcall, A, Bs} -> free_vars([A | Bs]);
{set_state, _, A} -> free_vars(A);
@@ -1902,9 +1841,6 @@ used_defs(Expr) ->
{proj, A, _} -> used_defs(A);
{set_proj, A, _, B} -> used_defs([A, B]);
{op, _, As} -> used_defs(As);
{loop, I, _, B} -> used_defs(I) ++ used_defs(B);
{break, E} -> used_defs(E);
{continue, E} -> used_defs(E);
{'let', _, A, B} -> used_defs([A, B]);
{funcall, A, Bs} -> used_defs([A | Bs]);
{set_state, _, A} -> used_defs(A);
@@ -1941,9 +1877,6 @@ bottom_up(F, Env, Expr) ->
{get_state, _} -> Expr;
{closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)};
{switch, Split} -> {switch, bottom_up(F, Env, Split)};
{loop, Init, Var, Body} -> {loop, bottom_up(F, Env, Init), Var, bottom_up(F, Env, Body)};
{break, E} -> {break, bottom_up(F, Env, E)};
{continue, E} -> {continue, bottom_up(F, Env, E)};
{lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)};
{'let', X, E, Body} ->
E1 = bottom_up(F, Env, E),
@@ -1952,7 +1885,7 @@ bottom_up(F, Env, Expr) ->
(_) -> true end,
case ShouldFreshen(X) of
true ->
Z = fresh_name(),
Z = fresh_name_save(X),
Env1 = Env#{ Z => E1 },
{'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))};
false ->
@@ -2005,11 +1938,6 @@ rename(Ren, Expr) ->
{lam, Xs, B} ->
{Zs, Ren1} = rename_bindings(Ren, Xs),
{lam, Zs, rename(Ren1, B)};
{loop, Init, Var, Body} ->
{Z, Ren1} = rename_binding(Ren, Var),
{loop, rename(Ren, Init), Z, rename(Ren1, Body)};
{break, E} -> {break, rename(Ren, E)};
{continue, E} -> {continue, rename(Ren, E)};
{'let', X, E, Body} ->
{Z, Ren1} = rename_binding(Ren, X),
{'let', Z, rename(Ren, E), rename(Ren1, Body)}
@@ -2122,7 +2050,9 @@ setnth(I, X, Xs) ->
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
fcode_error(Error) ->
aeso_errors:throw(aeso_code_errors:format(Error)).
Pos = aeso_errors:pos(0, 0),
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Error])),
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
@@ -2201,13 +2131,6 @@ pp_fexpr({tuple, Es}) ->
pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es])));
pp_fexpr({proj, E, I}) ->
pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]);
pp_fexpr({loop, Init, Var, Body}) ->
pp_par(
[ pp_beside([pp_text("loop"), pp_fexpr(Init), pp_text("as"), pp_text(Var)])
, pp_fexpr(Body)
]);
pp_fexpr({break, E}) -> pp_beside([pp_text("break"), pp_fexpr(E)]);
pp_fexpr({continue, E}) -> pp_beside([pp_text("continue"), pp_fexpr(E)]);
pp_fexpr({lam, Xs, A}) ->
pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"),
prettypr:nest(2, pp_fexpr(A))]);
-94
View File
@@ -1,94 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Formatting of code generation errors.
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(aeso_code_errors).
-export([format/1, pos/1]).
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'",
[Kind, C]),
mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.",
mk_err(pos(Con), Msg, Cxt);
format({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function '~s'.", [pp_expr(Id)]),
mk_err(pos(Id), Msg);
format({parameterized_state, Decl}) ->
Msg = "The state type cannot be parameterized.",
mk_err(pos(Decl), Msg);
format({parameterized_event, Decl}) ->
Msg = "The event type cannot be parameterized.",
mk_err(pos(Decl), Msg);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)";
polymorphic -> "polymorphic (contains type variables)" end,
ThingS = case Thing of
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
end,
Bad = case Thing of
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.",
[ThingS, Name, Bad]),
case Why of
higher_order -> mk_err(pos(Ann), Msg)
end;
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
"It must be a string or a pubkey type (address, oracle, etc).",
[pp_type(2, T)]),
mk_err(pos(Ann), Msg);
format({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({var_args_not_set, Expr}) ->
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
, "When compiling " ++ pp_expr(Expr)
);
format({found_void, Ann}) ->
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
format(Err) ->
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
aeso_errors:pos(File, Line, Col).
pp_typed(E, T) ->
prettypr:format(prettypr:nest(2,
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
[aeso_pretty:expr(E), prettypr:text(" : "),
aeso_pretty:type(T)]))).
pp_expr(E) ->
pp_expr(0, E).
pp_expr(N, E) ->
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
pp_type(N, T) ->
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
mk_err(Pos, Msg) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
mk_err(Pos, Msg, Cxt) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).
+13 -5
View File
@@ -112,10 +112,12 @@ from_string(ContractString, Options) ->
from_string1(ContractString, Options) ->
#{ fcode := FCode
, fcode_env := #{child_con_env := ChildContracts}
, fcode_env := FCodeEnv
, folded_typed_ast := FoldedTypedAst
, warnings := Warnings } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
#{ child_con_env := ChildContracts } = FCodeEnv,
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
{FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
pp_assembler(FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
@@ -128,7 +130,13 @@ from_string1(ContractString, Options) ->
payable => maps:get(payable, FCode),
warnings => Warnings
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
ResDbg = Res#{variables_registers => VarsRegs},
FinalRes =
case proplists:get_value(debug_info, Options, false) of
true -> ResDbg;
false -> Res
end,
{ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
@@ -185,7 +193,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
#{fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts}
, ast := Ast} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
{FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
@@ -349,7 +357,7 @@ get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(C
"init" -> {ok, [], {tuple_t, [], []}};
_ ->
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
Pos = aeso_code_errors:pos(Ann),
Pos = aeso_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end
end;
+7
View File
@@ -34,6 +34,7 @@
, new/2
, new/3
, new/4
, pos/1
, pos/2
, pos/3
, pp/1
@@ -53,6 +54,12 @@ new(Type, Pos, Msg) ->
new(Type, Pos, Msg, Ctxt) ->
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
pos(File, Line, Col).
pos(Line, Col) ->
#pos{ line = Line, col = Col }.
+141 -141
View File
@@ -9,7 +9,7 @@
%%%-------------------------------------------------------------------
-module(aeso_fcode_to_fate).
-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
-ifdef(TEST).
-export([optimize_fun/4, to_basic_blocks/1]).
@@ -19,7 +19,6 @@
-type scode() :: [sinstr()].
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
| {loop, scode(), var(), scode(), reference(), reference()}
| switch_body
| loop
| tuple() | atom(). %% FATE instruction
@@ -46,7 +45,14 @@
-define(s(N), {store, N}).
-define(void, {var, 9999}).
-record(env, { contract, vars = [], locals = [], break_ref = none, cont_ref = none, loop_it = none, current_function, tailpos = true, child_contracts = #{}, options = []}).
-record(env, { contract,
vars = [],
locals = [],
current_function,
tailpos = true,
child_contracts = #{},
saved_fresh_names = #{},
options = [] }).
%% -- Debugging --------------------------------------------------------------
@@ -65,21 +71,34 @@ debug(Tag, Options, Fun) ->
-dialyzer({nowarn_function, [code_error/1]}).
code_error(Err) ->
aeso_errors:throw(aeso_code_errors:format(Err)).
Pos = aeso_errors:pos(0, 0),
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Err])),
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(FCode, Options) ->
compile(#{}, FCode, Options).
compile(ChildContracts, FCode, Options) ->
compile(FCode, SavedFreshNames, Options) ->
compile(#{}, FCode, SavedFreshNames, Options).
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
try
compile1(ChildContracts, FCode, SavedFreshNames, Options)
after
put(variables_registers, undefined)
end.
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
#{ contract_name := ContractName,
functions := Functions } = FCode,
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1),
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
FateCode.
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of
false -> FateCode;
true -> add_child_symbols(ChildContracts, FateCode)
end,
{FateCode1, get_variables_registers()}.
make_function_id(X) ->
aeb_fate_code:symbol_identifier(make_function_name(X)).
@@ -88,22 +107,48 @@ make_function_name(event) -> <<"Chain.event">>;
make_function_name({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
add_child_symbols(ChildContracts, FateCode) ->
Funs = lists:flatten([ maps:keys(ChildFuns) || {_, #{functions := ChildFuns}} <- maps:to_list(ChildContracts) ]),
Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]),
aeb_fate_code:update_symbols(FateCode, Symbols).
functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options) ->
FunNames = maps:keys(Functions),
maps:from_list(
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, SavedFreshNames, Options)}
|| {Name, #{args := Args,
body := Body,
attrs := Attrs,
return := Type}} <- maps:to_list(Functions)]).
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) ->
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options),
[ add_variables_register(Env, Arg, Register) ||
proplists:get_value(debug_info, Options, false),
{Arg, Register} <- Env#env.vars ],
SCode = to_scode(Env, Body),
{Attrs, {ArgTypes, ResType1}, SCode}.
get_variables_registers() ->
case get(variables_registers) of
undefined -> #{};
Vs -> Vs
end.
add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) ->
Olds = get_variables_registers(),
RealName = maps:get(Name, SavedFreshNames, Name),
FunName =
case Env#env.current_function of
event -> "Chain.event";
{entrypoint, BinName} -> binary_to_list(BinName);
{local_fun, QualName} -> lists:last(QualName)
end,
New = {Env#env.contract, FunName, RealName},
put(variables_registers, Olds#{New => Register}).
-define(tvars, '$tvars').
typesig_to_scode(Args, Res) ->
@@ -148,22 +193,21 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions --
init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) ->
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName,
child_contracts = ChildContracts,
locals = FunNames,
current_function = Name,
options = Options,
tailpos = true }.
tailpos = true,
saved_fresh_names = SavedFreshNames }.
next_var(#env{ vars = Vars }) ->
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
bind_loop(ContRef, BreakRef, It, Env) ->
Env#env{break_ref = BreakRef, cont_ref = ContRef, loop_it = It}.
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var),
Env#env{ vars = [{Name, Var} | Vars] }.
bind_local(Name, Env) ->
@@ -187,9 +231,10 @@ serialize_contract_code(Env, C) ->
end,
case maps:get(C, Cache, none) of
none ->
Options = Env#env.options,
FCode = maps:get(C, Env#env.child_contracts),
FateCode = compile(Env#env.child_contracts, FCode, Options),
Options = Env#env.options,
SavedFreshNames = Env#env.saved_fresh_names,
FCode = maps:get(C, Env#env.child_contracts),
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = aeso_compiler:version(),
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
@@ -372,21 +417,7 @@ to_scode1(Env, {set_state, Reg, Val}) ->
to_scode1(Env, {closure, Fun, FVs}) ->
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
to_scode1(Env, {loop, Init, It, Expr}) ->
ContRef = make_ref(),
BreakRef = make_ref(),
{ItV, Env1} = bind_local(It, Env),
InitS = [to_scode(notail(Env), Init),
{jump, ContRef}],
ExprS = [aeb_fate_ops:store({var, ItV}, {stack, 0}),
to_scode(bind_loop(ContRef, BreakRef, ItV, Env1), Expr),
{jump, BreakRef}],
[{loop, InitS, It, ExprS, ContRef, BreakRef}];
to_scode1(Env = #env{cont_ref = ContRef}, {continue, Expr}) ->
[to_scode1(notail(Env), Expr),
{jump, ContRef}];
to_scode1(Env, {break, Expr}) ->
to_scode1(Env, Expr);
to_scode1(Env, {switch, Case}) ->
split_to_scode(Env, Case).
@@ -525,6 +556,8 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
builtin_to_scode(Env, abort, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
builtin_to_scode(Env, exit, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:exit(?a), Args);
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
tuple(0)], Args);
@@ -723,15 +756,13 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
Funs).
flatten(missing) -> missing;
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
flatten_s({switch, Arg, Type, Alts, Catch}) ->
{switch, Arg, Type, [flatten(Alt) || Alt <- Alts], flatten(Catch)};
flatten_s({loop, Init, It, Body, BRef, CRef}) ->
{loop, flatten(Init), It, flatten(Body), BRef, CRef};
flatten_s(I) -> I.
-define(MAX_SIMPL_ITERATIONS, 10).
@@ -828,11 +859,6 @@ ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) ->
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
ann_live1(LiveTop, {loop, Init, It, Body, BRef, CRef}, LiveOut) ->
{Init1, LiveInit} = ann_live(LiveTop, Init, LiveOut),
{Body1, LiveBody} = ann_live(LiveTop, Body, LiveOut),
LiveIn = ordsets:union([It, LiveInit, LiveBody]), % TODO not sure about this
{{loop, Init1, It, Body1, BRef, CRef}, LiveIn};
ann_live1(_LiveTop, I, LiveOut) ->
#{ read := Reads0, write := W } = attributes(I),
Reads = lists:filter(fun is_reg/1, Reads0),
@@ -859,7 +885,6 @@ attributes(I) ->
case I of
loop -> Impure(pc, []);
switch_body -> Pure(none, []);
{jump, _} -> Impure(pc, []);
'RETURN' -> Impure(pc, []);
{'RETURNR', A} -> Impure(pc, A);
{'CALL', A} -> Impure(?a, [A]);
@@ -1049,7 +1074,6 @@ var_writes(I) ->
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
independent(_, {switch, _, _, _, _}) -> false;
independent(_, {loop, _, _, _, _, _}) -> false;
independent({i, _, I}, {i, _, J}) ->
#{ write := WI, read := RI, pure := PureI } = attributes(I),
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
@@ -1088,8 +1112,6 @@ live_in(R, {i, Ann, _}) -> live_in(R, Ann);
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
live_in(R, [{loop, Init, Var, Expr, _, _}]) ->
live_in(Var, Init) orelse (R /= Var andalso live_in(R, Expr));
live_in(_, missing) -> false;
live_in(_, []) -> false.
@@ -1105,8 +1127,6 @@ simplify([I | Code], Options) ->
simpl_s({switch, Arg, Type, Alts, Def}, Options) ->
{switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)};
simpl_s({loop, Init, Var, Expr, ContRef, BreakRef}, Options) ->
{loop, simplify(Init, Options), Var, simplify(Expr, Options), ContRef, BreakRef};
simpl_s(I, _) -> I.
%% Safe-guard against loops in the rewriting. Shouldn't happen so throw an
@@ -1119,7 +1139,8 @@ simpl_top(I, Code, Options) ->
simpl_top(0, I, Code, _Options) ->
code_error({optimizer_out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) ->
apply_rules(Fuel, rules(), I, Code, Options).
Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
apply_rules(Fuel, Rules, I, Code, Options).
apply_rules(Fuel, Rules, I, Code, Options) ->
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
@@ -1146,29 +1167,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
-define(RULE(Name), {Name, fun Name/2}).
merge_rules() ->
[?RULE(r_push_consume),
?RULE(r_one_shot_var),
?RULE(r_write_to_dead_var),
?RULE(r_inline_switch_target)
[?RULE(optimize_push_consume),
?RULE(optimize_one_shot_var),
?RULE(optimize_write_to_dead_var),
?RULE(optimize_inline_switch_target)
].
rules() ->
merge_rules() ++
[?RULE(r_swap_push),
?RULE(r_swap_pop),
?RULE(r_swap_write),
?RULE(r_constant_propagation),
?RULE(r_prune_impossible_branches),
?RULE(r_single_successful_branch),
?RULE(r_inline_store),
?RULE(r_float_switch_body)
[?RULE(optimize_swap_push),
?RULE(optimize_swap_pop),
?RULE(optimize_swap_write),
?RULE(optimize_constant_propagation),
?RULE(optimize_prune_impossible_branches),
?RULE(optimize_single_successful_branch),
?RULE(optimize_inline_store),
?RULE(optimize_float_switch_body)
].
%% Removing pushes that are immediately consumed.
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
inline_push(Ann1, A, 0, Code, []);
%% Writing directly to memory instead of going through the accumulator.
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
IsPush =
case op_view(I) of
{_, ?a, _} -> true;
@@ -1180,7 +1201,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
end,
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
true -> false end;
r_push_consume(_, _) -> false.
optimize_push_consume(_, _) -> false.
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
@@ -1213,7 +1234,7 @@ split_stack_arg(N, [A | As], Acc) ->
split_stack_arg(N1, As, [A | Acc]).
%% Move PUSHes past non-stack instructions.
r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
case op_view(PushI) of
{_, ?a, _} ->
case independent(Push, I) of
@@ -1224,10 +1245,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
end;
_ -> false
end;
r_swap_push(_, _) -> false.
optimize_swap_push(_, _) -> false.
%% Move non-stack instruction past POPs.
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
case independent(IA, JA) of
true ->
case {op_view(I), op_view(J)} of
@@ -1235,7 +1256,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
{_, false} -> false;
{{_, IR, IAs}, {_, RJ, JAs}} ->
NonStackI = not lists:member(?a, [IR | IAs]),
%% RJ /= ?a to not conflict with r_swap_push
%% RJ /= ?a to not conflict with optimize_swap_push
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
case NonStackI andalso PopJ of
false -> false;
@@ -1246,22 +1267,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
end;
false -> false
end;
r_swap_pop(_, _) -> false.
optimize_swap_pop(_, _) -> false.
%% Match up writes to variables with instructions further down.
r_swap_write(I = {i, _, _}, [J | Code]) ->
optimize_swap_write(I = {i, _, _}, [J | Code]) ->
case {var_writes(I), independent(I, J)} of
{[_], true} ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1], I1, Code);
optimize_swap_write([J1], I1, Code);
_ -> false
end;
r_swap_write(_, _) -> false.
optimize_swap_write(_, _) -> false.
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code);
r_swap_write(Pre, I, Code0 = [J | Code]) ->
optimize_swap_write([J1 | Pre], I1, Code);
optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
case apply_rules_once(merge_rules(), I, Code0) of
{_Rule, New, Rest} ->
{lists:reverse(Pre) ++ New, Rest};
@@ -1270,27 +1291,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
false -> false;
true ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code)
optimize_swap_write([J1 | Pre], I1, Code)
end
end;
r_swap_write(_, _, _) -> false.
optimize_swap_write(_, _, _) -> false.
%% Precompute instructions with known values
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(false)}},
Cons1 = case R of
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
_ -> Cons
end,
{[Cons1, Store], Code};
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(true)}},
Nil1 = case R of
?a -> {i, Ann1, {'NIL', ?void}};
_ -> Nil
end,
{[Nil1, Store], Code};
r_constant_propagation({i, Ann, I}, Code) ->
optimize_constant_propagation({i, Ann, I}, Code) ->
case op_view(I) of
false -> false;
{Op, R, As} ->
@@ -1304,7 +1325,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
end
end
end;
r_constant_propagation(_, _) -> false.
optimize_constant_propagation(_, _) -> false.
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
@@ -1323,12 +1344,12 @@ eval_op('NOT', [false]) -> true;
eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
case pick_branch(Type, V, Alts) of
false -> false;
Alt -> {Alt, Code}
end;
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
Alts1 = [if V -> missing; true -> False end,
if V -> True; true -> missing end],
case Alts == Alts1 of
@@ -1339,7 +1360,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
end
end;
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
case {R, lists:nth(Tag + 1, Alts)} of
{_, missing} ->
@@ -1355,7 +1376,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
false -> {Alt, Code}
end
end;
r_prune_impossible_branches(_, _) -> false.
optimize_prune_impossible_branches(_, _) -> false.
pick_branch(boolean, V, [False, True]) when V == true; V == false ->
Alt = if V -> True; true -> False end,
@@ -1368,7 +1389,7 @@ pick_branch(_Type, _V, _Alts) ->
%% If there's a single branch that doesn't abort we can push the code for that
%% out of the switch.
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
case push_code_out_of_switch([Def | Alts]) of
{_, none} -> false;
{_, many} -> false;
@@ -1376,7 +1397,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
{[Def1 | Alts1], PushedOut} ->
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
end;
r_single_successful_branch(_, _) -> false.
optimize_single_successful_branch(_, _) -> false.
push_code_out_of_switch([]) -> {[], none};
push_code_out_of_switch([Alt | Alts]) ->
@@ -1409,12 +1430,10 @@ does_abort({i, _, {'EXIT', _}}) -> true;
does_abort(missing) -> true;
does_abort({switch, _, _, Alts, Def}) ->
lists:all(fun does_abort/1, [Def | Alts]);
does_abort({loop, Init, _, Expr, _, _}) ->
does_abort(Init) orelse does_abort(Expr);
does_abort(_) -> false.
%% STORE R A, SWITCH R --> SWITCH A
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
optimize_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
Ann1 =
case is_reg(A) of
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
@@ -1433,18 +1452,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
end;
_ -> false %% impossible
end;
r_inline_switch_target(_, _) -> false.
optimize_inline_switch_target(_, _) -> false.
%% Float switch-body to closest switch
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
{J1, I1} = swap_instrs(I, J),
{[], [J1, I1 | Code]};
r_float_switch_body(_, _) -> false.
optimize_float_switch_body(_, _) -> false.
%% Inline stores
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
{[], Code};
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
%% Not when A is var unless updating the annotations properly.
Inline = case A of
{arg, _} -> true;
@@ -1452,13 +1471,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
{store, _} -> true;
_ -> false
end,
if Inline -> r_inline_store([I], false, R, A, Code);
if Inline -> optimize_inline_store([I], false, R, A, Code);
true -> false end;
r_inline_store(_, _) -> false.
optimize_inline_store(_, _) -> false.
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
r_inline_store([I | Acc], Progress, R, A, Code);
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
optimize_inline_store([I | Acc], Progress, R, A, Code);
optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
#{ write := W } = attributes(I),
Inl = fun(X) when X == R -> A; (X) -> X end,
case live_in(R, Ann) of
@@ -1478,14 +1497,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
case lists:member(W, [R, A]) of
true when Progress1 -> {lists:reverse(Acc1), Code};
true -> false;
false -> r_inline_store(Acc1, Progress1, R, A, Code)
false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
end
end;
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
r_inline_store(_, false, _, _, _) -> false.
optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
optimize_inline_store(_, false, _, _, _) -> false.
%% Shortcut write followed by final read
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
case op_view(I) of
{Op, R = {var, _}, As} ->
Copy = case J of
@@ -1499,11 +1518,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
end;
_ -> false
end;
r_one_shot_var(_, _) -> false.
optimize_one_shot_var(_, _) -> false.
%% Remove writes to dead variables
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
r_write_to_dead_var({i, Ann, I}, Code) ->
optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
optimize_write_to_dead_var({i, Ann, I}, Code) ->
#{ pure := Pure } = attributes(I),
case op_view(I) of
{_Op, R, As} when R /= ?a, Pure ->
@@ -1516,9 +1535,10 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
end;
_ -> false
end;
r_write_to_dead_var(_, _) -> false.
optimize_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
op_view(T) when is_tuple(T) ->
[Op, R | As] = tuple_to_list(T),
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
@@ -1538,8 +1558,6 @@ from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
(missing) -> missing.
unannotate({switch, Arg, Type, Alts, Def}) ->
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
unannotate({loop, Init, It, Body, BRef, CRef}) ->
[{loop, unannotate(Init), It, unannotate(Body), BRef, CRef}];
unannotate(missing) -> missing;
unannotate(Code) when is_list(Code) ->
lists:flatmap(fun unannotate/1, Code);
@@ -1556,8 +1574,6 @@ desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
desugar({switch, Arg, Type, Alts, Def}) ->
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
desugar({loop, Init, Var, Expr, ContRef, BreakRef}) ->
[{loop, desugar(Init), Var, desugar(Expr), ContRef, BreakRef}];
desugar(missing) -> missing;
desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code);
@@ -1604,7 +1620,7 @@ bb(_Name, Code) ->
-type bb() :: {bbref(), bcode()}.
-type bcode() :: [binstr()].
-type binstr() :: {jump, bbref()}
| {jumpif, term(), bbref()}
| {jumpif, bbref()}
| tuple(). %% FATE instruction
-spec blocks(scode()) -> [bb()].
@@ -1618,29 +1634,25 @@ blocks([], Acc) ->
blocks([Blk | Blocks], Acc) ->
block(Blk, [], Blocks, Acc).
fresh_block(C, Ca) ->
R = make_ref(),
{R, [#blk{ref = R, code = C, catchall = Ca}]}.
-spec block(#blk{}, bcode(), [#blk{}], [bb()]) -> [bb()].
block(#blk{ref = Ref, code = []}, CodeAcc, Blocks, BlockAcc) ->
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
block(Blk = #blk{code = [{loop, Init, _, Expr, ContRef, BreakRef} | Code], catchall = Catchall}, Acc, Blocks, BlockAcc) ->
LoopBlock = #blk{ref = ContRef, code = Expr, catchall = none},
BreakBlock = #blk{ref = BreakRef, code = Code, catchall = Catchall},
block(Blk#blk{code = Init}, Acc, [LoopBlock, BreakBlock | Blocks], BlockAcc);
block(Blk = #blk{code = [switch_body | Code]}, Acc, Blocks, BlockAcc) ->
%% Reached the body of a switch. Clear catchall ref.
block(Blk#blk{code = Code, catchall = none}, Acc, Blocks, BlockAcc);
block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
catchall = Catchall}, Acc, Blocks, BlockAcc) ->
{RestRef, RestBlk} = fresh_block(Code, Catchall),
FreshBlk = fun(C, Ca) ->
R = make_ref(),
{R, [#blk{ref = R, code = C, catchall = Ca}]}
end,
{RestRef, RestBlk} = FreshBlk(Code, Catchall),
{DefRef, DefBlk} =
case Default of
missing when Catchall == none ->
fresh_block([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
missing -> {Catchall, []};
_ -> fresh_block(Default ++ [{jump, RestRef}], Catchall)
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
%% ^ fall-through to the outer catchall
end,
%% If we don't generate a switch, we need to pop the argument if on the stack.
@@ -1652,7 +1664,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
{ThenRef, ThenBlk} =
case TrueCode of
missing -> {DefRef, []};
_ -> fresh_block(TrueCode ++ [{jump, RestRef}], DefRef)
_ -> FreshBlk(TrueCode ++ [{jump, RestRef}], DefRef)
end,
ElseCode =
case FalseCode of
@@ -1687,7 +1699,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
false ->
MkBlk = fun(missing) -> {DefRef, []};
(ACode) -> fresh_block(ACode ++ [{jump, RestRef}], DefRef)
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
end,
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
@@ -1703,7 +1715,7 @@ block(Blk = #blk{code = [I | Code]}, Acc, Blocks, BlockAcc) ->
optimize_blocks(Blocks) ->
%% We need to look at the last instruction a lot, so reverse all blocks.
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
RBlocks = [{Ref, crop_jumps(Code)} || {Ref, Code} <- Blocks],
RBlocks = Rev(Blocks),
RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
@@ -1785,18 +1797,6 @@ tweak_returns(['RETURN' | Code = [{'EXIT', _} | _]]) -> Code;
tweak_returns(['RETURN' | Code = [loop | _]]) -> Code;
tweak_returns(Code) -> Code.
%% -- Remove instructions that appear after jumps. Returns reversed code.
%% This is useful for example when bb emitter adds continuation jumps
%% for switch expressions, but some of the branches
crop_jumps(Code) ->
crop_jumps(Code, []).
crop_jumps([], Acc) ->
Acc;
crop_jumps([I = {jump, _}|_], Acc) ->
[I|Acc];
crop_jumps([I|Code], Acc) ->
crop_jumps(Code, [I|Acc]).
%% -- Split basic blocks at CALL instructions --
%% Calls can only return to a new basic block. Also splits at JUMPIF instructions.
+3
View File
@@ -359,9 +359,12 @@ exprAtom() ->
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
, letpat()
, hole()
])
end).
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
comprehension_exp() ->
?LAZY_P(choice(
[ comprehension_bind()
+6 -1
View File
@@ -261,6 +261,8 @@ type(Type, Options) ->
with_options(Options, fun() -> type(Type) end).
-spec type(aeso_syntax:type()) -> doc().
type(F = {fun_t, _, _, var_args, _}) ->
type(setelement(4, F, [var_args]));
type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, Ret}) ->
@@ -288,7 +290,9 @@ type(T = {id, _, _}) -> name(T);
type(T = {qid, _, _}) -> name(T);
type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T).
type(T = {tvar, _, _}) -> name(T);
type(var_args) -> text("var_args").
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
@@ -430,6 +434,7 @@ lc_bind(Let) ->
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('|>') -> {150, 150, 200};
bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500};
+4 -3
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]).
@@ -26,7 +26,7 @@
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
| stateful | private | payable | main | interface].
| stateful | private | payable | main | interface | entrypoint].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -42,6 +42,7 @@
| {contract_child, ann(), con(), [con()], [decl()]}
| {contract_interface, ann(), con(), [con()], [decl()]}
| {namespace, ann(), con(), [decl()]}
| {include, ann(), {string, ann(), string()}}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()}
@@ -105,7 +106,7 @@
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '||' | '&&' | '..'.
| '||' | '&&' | '..' | '|>'.
-type un_op() :: '-' | '!'.
-type expr()
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Compiler for Aeternity Sophia language"},
{vsn, "6.1.0"},
{vsn, "7.1.0"},
{registered, []},
{applications,
[kernel,
+4 -4
View File
@@ -21,7 +21,7 @@ test_cases(1) ->
" payable stateful entrypoint a(i : int) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>,
type_defs => [],
typedefs => [],
payable => true,
kind => contract_main,
functions =>
@@ -43,7 +43,7 @@ test_cases(2) ->
MapACI = #{contract =>
#{name => <<"C">>, payable => false,
kind => contract_main,
type_defs =>
typedefs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
@@ -76,7 +76,7 @@ test_cases(3) ->
name => <<"C">>, payable => false, kind => contract_main,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>,
type_defs =>
typedefs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
@@ -127,7 +127,7 @@ check_stub(Stub, Options) ->
Ast ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
aeso_ast_infer_types:infer(Ast, [no_code])
catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
+1 -1
View File
@@ -29,7 +29,7 @@ calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString, [no_code]),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
FateExprs = ast_exprs(ContractACI, Fun, Args),
+205 -89
View File
@@ -45,12 +45,6 @@ simple_compile_test_() ->
check_errors(ExpectedErrors, Errors)
end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing code generation error messages of " ++ ContractName,
fun() ->
Errors = compile(ContractName),
check_errors([ExpectedError], Errors)
end} ||
{ContractName, ExpectedError} <- failing_code_gen_contracts()] ++
[ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
@@ -208,6 +202,25 @@ compilable_contracts() ->
"polymorphism_contract_interface_extensions",
"polymorphism_contract_interface_same_decl_multi_interface",
"polymorphism_contract_interface_same_name_same_type",
"polymorphism_variance_switching_chain_create",
"polymorphism_variance_switching_void_supertype",
"polymorphism_variance_switching_unify_with_interface_decls",
"polymorphism_preserve_or_add_payable_contract",
"polymorphism_preserve_or_add_payable_entrypoint",
"polymorphism_preserve_or_remove_stateful_entrypoint",
"missing_init_fun_state_unit",
"complex_compare_leq",
"complex_compare",
"higher_order_compare",
"higher_order_map_keys",
"higher_order_state",
"polymorphic_compare",
"polymorphic_entrypoint",
"polymorphic_entrypoint_return",
"polymorphic_map_keys",
"unapplied_contract_call",
"unapplied_named_arg_builtin",
"resolve_field_constraint_by_arity",
"test" % Custom general-purpose test file. Keep it last on the list.
].
@@ -289,34 +302,26 @@ failing_contracts() ->
%% Type errors
, ?TYPE_ERROR(name_clash,
[<<?Pos(14, 3)
[<<?Pos(4, 3)
"Duplicate definitions of `double_def` at\n"
" - line 3, column 3\n"
" - line 4, column 3">>,
<<?Pos(7, 3)
"Duplicate definitions of `abort` at\n"
" - (builtin location)\n"
" - line 14, column 3">>,
<<?Pos(15, 3)
" - line 7, column 3">>,
<<?Pos(8, 3)
"Duplicate definitions of `require` at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<?Pos(11, 3)
"Duplicate definitions of `double_def` at\n"
" - line 10, column 3\n"
" - line 11, column 3">>,
<<?Pos(5, 3)
"Duplicate definitions of `double_proto` at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<?Pos(8, 3)
"Duplicate definitions of `proto_and_def` at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<?Pos(16, 3)
<<?Pos(9, 3)
"Duplicate definitions of `put` at\n"
" - (builtin location)\n"
" - line 16, column 3">>,
<<?Pos(17, 3)
" - line 9, column 3">>,
<<?Pos(10, 3)
"Duplicate definitions of `state` at\n"
" - (builtin location)\n"
" - line 17, column 3">>])
" - line 10, column 3">>])
, ?TYPE_ERROR(type_errors,
[<<?Pos(17, 23)
"Unbound variable `zz`">>,
@@ -732,10 +737,22 @@ failing_contracts() ->
"Conflicting updates for field 'foo'">>])
, ?TYPE_ERROR(factories_type_errors,
[<<?Pos(10,18)
"Chain.clone requires `ref` named argument of contract type.">>,
"Chain.clone requires `ref` named argument of contract type.">>,
<<?Pos(11,18)
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => 'b`\n"
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)`\n"
"when checking contract construction of type\n"
" (gas : int, value : int, protected : bool) =>\n"
" if(protected, option(void), void) (at line 11, column 18)\n"
"against the expected type\n"
" (gas : int, value : int, protected : bool, int, bool) =>\n"
" if(protected, option(void), void)">>,
<<?Pos(11,18)
"Cannot unify `Bakoom` and `Kaboom`\n"
"when checking that contract construction of type\n"
" Bakoom\n"
"arising from resolution of variadic function `Chain.clone`\n"
"matches the expected type\n"
" Kaboom">>,
<<?Pos(12,37)
"Cannot unify `int` and `bool`\n"
"when checking named argument `gas : int` against inferred type `bool`">>,
@@ -848,25 +865,25 @@ failing_contracts() ->
"Trying to implement or extend an undefined interface `Z`">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type,
[<<?Pos(4,20)
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>])
[<<?Pos(9,5)
"Duplicate definitions of `f` at\n"
" - line 8, column 5\n"
" - line 9, column 5">>])
, ?TYPE_ERROR(polymorphism_contract_missing_implementation,
[<<?Pos(4,20)
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>
"Unimplemented entrypoint `f` from the interface `I1` in the contract `I2`">>
])
, ?TYPE_ERROR(polymorphism_contract_same_decl_multi_interface,
[<<?Pos(7,10)
"Unimplemented function `f` from the interface `J` in the contract `C`">>
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
])
, ?TYPE_ERROR(polymorphism_contract_undefined_interface,
[<<?Pos(1,14)
"Trying to implement or extend an undefined interface `I`">>
])
, ?TYPE_ERROR(polymorphism_contract_same_name_different_type_multi_interface,
[<<?Pos(9,5)
"Duplicate definitions of `f` at\n"
" - line 8, column 5\n"
" - line 9, column 5">>
[<<?Pos(7,10)
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface,
[<<?Pos(1,24)
@@ -981,7 +998,8 @@ failing_contracts() ->
"when checking the type of the pattern `r10 : rec_inv(Animal)` against the expected type `Main.rec_inv(Cat)`">>,
<<?Pos(41,13)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the pattern `r11 : rec_inv(Cat)` against the expected type `Main.rec_inv(Animal)`">>])
"when checking the type of the pattern `r11 : rec_inv(Cat)` against the expected type `Main.rec_inv(Animal)`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching_oracles,
[<<?Pos(15,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
@@ -1024,58 +1042,156 @@ failing_contracts() ->
"when checking the type of the pattern `q14 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Animal, Cat)`">>,
<<?Pos(44,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>])
].
-define(Path(File), "code_errors/" ??File).
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
-define(FATE_ERR(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
failing_code_gen_contracts() ->
[ ?FATE_ERR(missing_definition, 2, 14,
"Missing definition of function 'foo'.")
, ?FATE_ERR(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?FATE_ERR(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?FATE_ERR(missing_init_function, 1, 10,
"Missing init function for the contract 'MissingInitFunction'.\n"
"The 'init' function can only be omitted if the state type is 'unit'.")
, ?FATE_ERR(parameterised_state, 3, 8,
"The state type cannot be parameterized.")
, ?FATE_ERR(parameterised_event, 3, 12,
"The event type cannot be parameterized.")
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n"
" 'a\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?FATE_ERR(bad_aens_resolve, 6, 5,
"Invalid return type of AENS.resolve:\n"
" list(int)\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?FATE_ERR(polymorphic_query_type, 3, 5,
"Invalid oracle type\n"
" oracle('a, 'b)\n"
"The query type must not be polymorphic (contain type variables).")
, ?FATE_ERR(polymorphic_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, 'r)\n"
"The response type must not be polymorphic (contain type variables).")
, ?FATE_ERR(higher_order_query_type, 3, 5,
"Invalid oracle type\n"
" oracle((int) => int, string)\n"
"The query type must not be higher-order (contain function types).")
, ?FATE_ERR(higher_order_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, (int) => int)\n"
"The response type must not be higher-order (contain function types).")
, ?FATE_ERR(child_with_decls, 2, 14,
"Missing definition of function 'f'.")
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching_chain_create_fail,
[<<?Pos(9,22)
"I is not implemented.\n"
"when resolving arguments of variadic function `Chain.create`">>,
<<?Pos(10,13)
"Cannot unify `I` and `C` in a covariant context\n"
"when checking the type of the pattern `c2 : C` against the expected type `I`">>,
<<?Pos(10,22)
"I is not implemented.\n"
"when resolving arguments of variadic function `Chain.create`">>,
<<?Pos(11,22)
"I is not implemented.\n"
"when resolving arguments of variadic function `Chain.create`">>
])
, ?TYPE_ERROR(missing_definition,
[<<?Pos(2,14)
"Missing definition of function `foo`">>
])
, ?TYPE_ERROR(child_with_decls,
[<<?Pos(2,14)
"Missing definition of function `f`">>
])
, ?TYPE_ERROR(parameterised_state,
[<<?Pos(3,8)
"The state type cannot be parameterized">>
])
, ?TYPE_ERROR(parameterised_event,
[<<?Pos(3,12)
"The event type cannot be parameterized">>
])
, ?TYPE_ERROR(missing_init_fun_alias_to_type,
[<<?Pos(1,10)
"Missing `init` function for the contract `AliasToType`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
, ?TYPE_ERROR(missing_init_fun_alias_to_alias_to_type,
[<<?Pos(1,10)
"Missing `init` function for the contract `AliasToAliasToType`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
, ?TYPE_ERROR(higher_order_entrypoint,
[<<?Pos(2,20)
"The argument\n"
" `f : (int) => int`\n"
"of entrypoint `apply` has a higher-order (contains function types) type">>
])
, ?TYPE_ERROR(higher_order_entrypoint_return,
[<<?Pos(2,3)
"The return type\n"
" `(int) => int`\n"
"of entrypoint `add` is higher-order (contains function types)">>
])
, ?TYPE_ERROR(polymorphic_aens_resolve,
[<<?Pos(4,5)
"Invalid return type of `AENS.resolve`:\n"
" `'a`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve,
[<<?Pos(6,5)
"Invalid return type of `AENS.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve_using,
[<<?Pos(7,5)
"Invalid return type of `AENS.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(polymorphic_query_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle('a, 'b)`\n"
"The query type must not be polymorphic (contain type variables)">>,
<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle('a, 'b)`\n"
"The response type must not be polymorphic (contain type variables)">>
])
, ?TYPE_ERROR(polymorphic_response_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle(string, 'r)`\n"
"The response type must not be polymorphic (contain type variables)">>
])
, ?TYPE_ERROR(higher_order_query_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle((int) => int, string)`\n"
"The query type must not be higher-order (contain function types)">>
])
, ?TYPE_ERROR(higher_order_response_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle(string, (int) => int)`\n"
"The response type must not be higher-order (contain function types)">>
])
, ?TYPE_ERROR(var_args_unify_let,
[<<?Pos(3,9)
"Cannot infer types for variable argument list.\n"
"when checking the type of the pattern `x : 'a` against the expected type `(gas : int, value : int, protected : bool, ref : 'b, var_args) => 'b`">>
])
, ?TYPE_ERROR(var_args_unify_fun_call,
[<<?Pos(6,5)
"Cannot infer types for variable argument list.\n"
"when checking the application of\n"
" `g : (() => 'b) => 'b`\n"
"to arguments\n"
" `Chain.create : (value : int, var_args) => 'c`">>
])
, ?TYPE_ERROR(polymorphism_add_stateful_entrypoint,
[<<?Pos(5,25)
"`f` cannot be stateful because the entrypoint `f` in the interface `I` is not stateful">>
])
, ?TYPE_ERROR(polymorphism_change_entrypoint_to_function,
[<<?Pos(6,14)
"`f` must be declared as an entrypoint instead of a function in order to implement the entrypoint `f` from the interface `I`">>
])
, ?TYPE_ERROR(polymorphism_non_payable_contract_implement_payable,
[<<?Pos(4,10)
"Non-payable contract `C` cannot implement payable interface `I`">>
])
, ?TYPE_ERROR(polymorphism_non_payable_interface_implement_payable,
[<<?Pos(4,20)
"Non-payable interface `H` cannot implement payable interface `I`">>
])
, ?TYPE_ERROR(polymorphism_remove_payable_entrypoint,
[<<?Pos(5,16)
"`f` must be payable because the entrypoint `f` in the interface `I` is payable">>
])
, ?TYPE_ERROR(calling_child_contract_entrypoint,
[<<?Pos(5,20)
"Invalid call to contract entrypoint `F.g`.\n"
"It must be called as `c.g` for some `c : F`.">>])
, ?TYPE_ERROR(using_contract_as_namespace,
[<<?Pos(5,3)
"Cannot use undefined namespace F">>])
, ?TYPE_ERROR(hole_expression,
[<<?Pos(5,13)
"Found a hole of type `bool`">>,
<<?Pos(6,17)
"Found a hole of type `string`">>,
<<?Pos(9,37)
"Found a hole of type `(int) => int`">>,
<<?Pos(13,20)
"Found a hole of type `'a`">>
])
].
validation_test_() ->
+13 -2
View File
@@ -1,5 +1,7 @@
contract C = entrypoint init() = ()
// AENS tests
contract AENSTest =
main contract AENSTest =
// Name resolution
@@ -9,10 +11,19 @@ contract AENSTest =
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key)
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) =
AENS.resolve(name, key)
// Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash
chash : hash) : unit = // Commitment hash
AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
+1
View File
@@ -80,3 +80,4 @@ contract AllSyntax =
let sh : shakespeare(shakespeare(int)) =
{wolfgang = state}
sh{wolfgang.wolfgang = sh.wolfgang} // comment
exit("hope you had fun reading this")
@@ -0,0 +1,9 @@
contract BadAENSresolve =
using AENS
type t('a) = option(list('a))
function fail() : t(int) =
resolve("foo.aet", "whatever")
entrypoint main_fun() = ()
@@ -0,0 +1,5 @@
contract F =
entrypoint g() = 1
main contract C =
entrypoint f() = F.g()
@@ -1,3 +0,0 @@
contract MissingInitFunction =
type state = int * int
+1 -10
View File
@@ -1,16 +1,7 @@
contract interface Remote =
entrypoint up_to : (int) => list(int)
entrypoint sum : (list(int)) => int
entrypoint some_string : () => string
entrypoint pair : (int, string) => int * string
entrypoint squares : (int) => list(int * int)
entrypoint filter_some : (list(option(int))) => list(int)
entrypoint all_some : (list(option(int))) => option(list(int))
contract ComplexTypes =
record state = { worker : Remote }
record state = { worker : ComplexTypes }
entrypoint init(worker) = {worker = worker}
+1 -6
View File
@@ -1,14 +1,9 @@
// Testing primitives for accessing the block chain environment
contract interface Interface =
entrypoint contract_address : () => address
entrypoint call_origin : () => address
entrypoint call_caller : () => address
entrypoint call_value : () => int
contract Environment =
record state = {remote : Interface}
record state = {remote : Environment}
entrypoint init(remote) = {remote = remote}
+13
View File
@@ -0,0 +1,13 @@
include "List.aes"
contract C =
entrypoint f() =
let ??? = true
let v = ???
let q = v == "str"
let xs = [1, 2, 3, 4]
switch (List.first(List.map(???, xs)))
Some(x) => x + 1
None => 0
function g() = ???
@@ -0,0 +1,3 @@
contract AliasToAliasToType =
type alias = int * int
type state = alias
@@ -0,0 +1,2 @@
contract AliasToType =
type state = int * int
@@ -0,0 +1,9 @@
contract AliasToAliasToUnit =
type alias = unit
type state = alias
contract AliasToUnit =
type state = unit
main contract ImplicitState =
type sometype = int
+1 -8
View File
@@ -1,12 +1,5 @@
contract NameClash =
entrypoint double_proto : () => int
entrypoint double_proto : () => int
entrypoint proto_and_def : int => int
entrypoint proto_and_def(n) = n + 1
entrypoint double_def(x) = x
entrypoint double_def(y) = 0
@@ -14,4 +7,4 @@ contract NameClash =
entrypoint abort() : int = 0
entrypoint require(b, err) = if(b) abort(err)
entrypoint put(x) = x
entrypoint state(x, y) = x + y
entrypoint state(x, y) = x + y
@@ -0,0 +1,5 @@
contract interface I =
entrypoint f : () => int
contract C : I =
stateful entrypoint f() = 1
@@ -0,0 +1,6 @@
contract interface I =
entrypoint f : () => int
contract C : I =
entrypoint init() = ()
function f() = 1
@@ -0,0 +1,5 @@
payable contract interface I =
payable entrypoint f : () => int
contract C : I =
entrypoint f() = 123
@@ -0,0 +1,8 @@
payable contract interface I =
payable entrypoint f : () => int
contract interface H : I =
payable entrypoint f : () => int
payable contract C : H =
entrypoint f() = 123
@@ -0,0 +1,14 @@
contract interface F =
entrypoint f : () => int
payable contract interface G : F =
payable entrypoint f : () => int
entrypoint g : () => int
payable contract interface H =
payable entrypoint h : () => int
payable contract C : G, H =
payable entrypoint f() = 1
payable entrypoint g() = 2
payable entrypoint h() = 3
@@ -0,0 +1,7 @@
contract interface I =
payable entrypoint f : () => int
entrypoint g : () => int
contract C : I =
payable entrypoint f() = 1
payable entrypoint g() = 2
@@ -0,0 +1,7 @@
contract interface I =
stateful entrypoint f : () => int
stateful entrypoint g : () => int
contract C : I =
stateful entrypoint f() = 1
entrypoint g() = 2
@@ -0,0 +1,5 @@
contract interface I =
payable entrypoint f : () => int
contract C : I =
entrypoint f() = 1
@@ -0,0 +1,29 @@
contract interface I =
entrypoint f : () => int
contract C1 : I =
entrypoint f() = 123
contract C2 : I =
entrypoint f() = 888
namespace Make =
stateful function new1() : I = Chain.create() : C1
stateful function new2() : I = Chain.create() : C2
stateful function new(c : I) : int = c.f()
main contract Main =
stateful entrypoint test1() =
let c = Make.new1()
Make.new(c)
stateful entrypoint test2() =
let c = Make.new2()
Make.new(c)
stateful entrypoint test3() =
let c1 = Chain.create() : C1 // succeeds
let c2 : I = Chain.create() : C1 // succeeds
let c3 : C1 = Chain.create() // succeeds
()
@@ -0,0 +1,12 @@
contract interface I =
entrypoint f : () => int
contract C : I =
entrypoint f() = 123
main contract Main =
stateful entrypoint test() =
let c1 : I = Chain.create() // fails
let c2 : C = Chain.create() : I // fails
let c3 = Chain.create() : I // fails
()
@@ -0,0 +1,5 @@
payable contract interface SalesOffer =
entrypoint init : (address, address, int, int) => unit
payable contract Test : SalesOffer =
entrypoint init(_, _, _, _) = ()
@@ -0,0 +1,5 @@
payable contract interface SalesOffer =
entrypoint init : (address, address, int, int) => void
payable contract Test : SalesOffer =
entrypoint init(_ : address, _ : address, _ : int, _ : int) = ()
@@ -0,0 +1,9 @@
contract First =
entrypoint print_num(x) = 1 + x
contract Second =
entrypoint print_num() = 1
main contract Test =
entrypoint f(c) = c.print_num(1)
entrypoint g(c) = c.print_num()
-3
View File
@@ -1,7 +1,4 @@
contract interface SpendContract =
entrypoint withdraw : (int) => int
contract SpendTest =
stateful entrypoint spend(to, amount) =
@@ -0,0 +1,7 @@
contract F =
entrypoint g() = 1
main contract C =
using F for [g]
entrypoint f() = g()
@@ -0,0 +1,7 @@
contract C =
stateful function g(h) =
h()
stateful entrypoint f() =
g(Chain.create)
123
+4
View File
@@ -0,0 +1,4 @@
main contract C =
stateful entrypoint f() =
let x = Chain.clone
123
+8 -1
View File
@@ -12,7 +12,7 @@ namespace UnusedNamespace =
// Unused
private function h() = 3
contract Warnings =
main contract Warnings =
type state = int
@@ -58,3 +58,10 @@ namespace FunctionsAsArgs =
private function inc(n : int) : int = n + 1
// Never used
private function dec(n : int) : int = n - 1
contract Remote =
entrypoint id(_) = 0
contract C =
payable stateful entrypoint
call_missing_con() : int = (ct_1111111111111111111111111111112JF6Dz72 : Remote).id(value = 1, 0)