diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index dcdee61..e3044b9 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -2,4 +2,4 @@ mkdocs==1.4.2 mkdocs-simple-hooks==0.1.5 mkdocs-material==9.0.9 mike==1.1.2 -pygments==2.14.0 \ No newline at end of file +pygments==2.17.2 diff --git a/.gitignore b/.gitignore index 095c8b0..b0d35ee 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ current_counterexample.eqc test/contracts/test.aes __pycache__ .docssite/docs/*.md +.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a4b93..8e35c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added a check for number of type variables in a type signature; it is serialized using 8 bits, + so the upper limit is 256. +### Changed +### Removed + +## [8.0.1] +### Changed +- Upgrade aebytecode to v3.4.1 to fix C warnings + +## [8.0.0] +### Added +- Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. +- `Int.mulmod` - combined builtin operation for multiplication and modulus. +- `Crypto.poseidon` - a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field). +- `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.). +- Raw data pointers added to AENS. In short we have introduced a new namespace + `AENSv2`; they contain types similar to the old `AENS`; `AENS.name` and + `AENS.pointee`, where the latter now has a constructor `DataPt(bytes())`. All + AENS actions have been moved to `AENSv2`, and `AENSv2.lookup` and + `AENSv2.update` consume and produce the new types. The old `AENS` namespace + only contains the old datatypes, that can be used to interface existing + contracts. Standard library `AENSCompat` is added to convert between old and + new pointers. +- Introduce arbitrary sized binary arrays (type `bytes()`); adding `Bytes.split_any`, + `Bytes.to_fixed_size`, `Bytes.to_any_size`, `Bytes.size`, `String.to_bytes`, + and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary + sized byte arrays. +- `Chain.network_id` - a function to get hold of the Chain's network id. +- Allowing `Bytes.to_any_size` in calldata creation, to enable creation of arguments + with arbitray size. +- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`). +- Support for OTP-27 - no changes in behavior. +### Changed +- `Crypto.verify_sig` is changed to have `msg : bytes()`. I.e. the + signed data can be of any length (used to be limited to `bytes(32)`/`hash`). +- System aliases are handled explicitly when converting to a Sophia value, this is only + observable for `signature` where a value of type `signature` is now represented as a + (new) signature literal. +- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`. +### Removed +- `Bitwise.aes` standard library is removed - the builtin operations are superior. + +## [7.4.1] +### Changed +- Improve how includes with relative paths are resolved during parsing/compilation. Relative + include paths are now always relative to the file containing the `include` statement. +### Fixed +- Disable unused type warnings for types used inside of records. + +## [7.4.0] +### Changed +- Names of lifted lambdas now consist of parent function's name and their + position in the source code. +### Fixed +- Lifted lambdas get their names assigned deterministically. + +## [7.3.0] +### Fixed +- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function. +- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions. + +## [7.2.1] +### Fixed +- Fixed bugs with the newly added debugging symbols + +## [7.2.0] +### Added - Toplevel compile-time constants ``` namespace N = @@ -13,8 +80,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 contract C = let cc = 2 ``` -### Changed +- API functions for encoding/decoding Sophia values to/from FATE. ### Removed +- Remove the mapping from variables to FATE registers from the compilation output. ### Fixed - Warning about unused include when there is no include. @@ -388,7 +456,14 @@ 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/v7.1.0...HEAD +[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.1...HEAD +[8.0.1]: https://github.com/aeternity/aesophia/compare/v8.0.0...v8.0.1 +[8.0.0]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0 +[7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1 +[7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0 +[7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0 +[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1 +[7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0 [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 diff --git a/docs/aeso_compiler.md b/docs/aeso_compiler.md index 61ee7aa..4798ad4 100644 --- a/docs/aeso_compiler.md +++ b/docs/aeso_compiler.md @@ -53,8 +53,6 @@ The **pp_** options all print to standard output the following: 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 diff --git a/docs/sophia_features.md b/docs/sophia_features.md index e8170c0..dfbfe59 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -84,7 +84,7 @@ the return value of the call. ```sophia contract interface VotingType = - entrypoint : vote : string => unit + entrypoint vote : string => unit contract Voter = entrypoint tryVote(v : VotingType, alt : string) = @@ -204,7 +204,7 @@ When a `contract` or a `contract interface` implements another `contract interfa #### 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)), +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. @@ -224,11 +224,11 @@ A good example of where it matters can be pictured by subtyping of function type ```sophia contract interface Animal = entrypoint age : () => int - + contract Dog : Animal = entrypoint age() = // ... entrypoint woof() = "woof" - + contract Cat : Animal = entrypoint age() = // ... entrypoint meow() = "meow" @@ -295,6 +295,11 @@ of `A`. - When a user-defined type `t('a)` is invariant in `'a`, then `t(A)` can never be a subtype of `t(B)`. +#### Type variable limitation + +Because of how FATE represents types as values there is a fixed upper limit (256) +of type variables that can be used in a single type signature. + ## Mutable state Sophia does not have arbitrary mutable state, but only a limited form of state @@ -493,6 +498,24 @@ the file, except that error messages will refer to the original source locations. The language will try to include each file at most one time automatically, so even cyclic includes should be working without any special tinkering. +### Include files using relative paths + +When including code from another file using the `include` statement, the path +is relative to _the file that includes it_. Consider the following file tree: +``` +c1.aes +c3.aes +dir1/c2.aes +dir1/c3.aes +``` + +If `c1.aes` contains `include "c3.aes"` it will include the top level `c3.aes`, +while if `c2.aes` contained the same line it would as expected include +`dir1/c3.aes`. + +Note: Prior to v7.5.0, it would consider the include path relative to _the main +contract file_ (or any explicitly set include path). + ## Standard library Sophia offers [standard library](sophia_stdlib.md) which exposes some @@ -540,6 +563,7 @@ Sophia has the following types: ## Literals | Type | Constant/Literal example(s) | | ---------- | ------------------------------- | +| unit | () | | int | `-1`, `2425`, `4598275923475723498573485768` | | address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` | | bool | `true`, `false` | @@ -554,7 +578,7 @@ Sophia has the following types: | state | `state{ owner = Call.origin, magic_key = #a298105f }` | | event | `EventX(0, "Hello")` | | hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | -| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| signature | `sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj`, `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | | Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` | | oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` | | oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` | @@ -610,14 +634,28 @@ arithmetic operations: - remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y` - exponentiation (`x ^ y`) -All operations are *safe* with respect to overflow and underflow. +All operations are *safe* with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero. +Sophia arbitrary-sized integers (FATE) also supports the following bitwise operations: +- bitwise and (`x band y`) +- bitwise or (`x bor y`) +- bitwise xor (`x bxor y`) +- bitwise not (`bnot x`) +- arithmetic bitshift left (`x << n`) +- arithmetic bitshift right (`x >> n`) + +Note: Arithmetic bitshift treats the number as a signed integer (in 2s +complement), and "retains" the topmost bit. I.e. shifting in zeros if the +topmost bit was 0, and ones if it was one. + ## Bit fields -Sophia integers do not support bit arithmetic. Instead there is a separate -type `bits`. See the standard library [documentation](sophia_stdlib.md#bits). +Originally Sophia integers did not support bit arithmetic. Instead we used a +separate type `bits` (see the standard library +[documentation](sophia_stdlib.md#bits)) - it is still provided as an +alternative to bit arithmetic. A bit field can be of arbitrary size (but it is still represented by the corresponding integer, so setting very high bits can be expensive). @@ -922,16 +960,18 @@ functions are provided. ## AENS interface -Contracts can interact with the -[æternity naming system](https://github.com/aeternity/protocol/blob/master/AENS.md). -For this purpose the [AENS](sophia_stdlib.md#aens) library was exposed. +Contracts can interact with the [æternity naming +system](https://github.com/aeternity/protocol/blob/master/AENS.md). For this +purpose the [AENS](sophia_stdlib.md#aens) and later the +[AENSv2](sophia_stdlib.md#aensv2) library was exposed. ### Example In this example we assume that the name `name` already exists, and is owned by an account with address `addr`. In order to allow a contract `ct` to handle -`name` the account holder needs to create a -[signature](#delegation-signature) `sig` of `addr | name.hash | ct.address`. +`name` the account holder needs to create a [delegation +signature](#delegation-signature) `sig` from the name owner address `addr`, the +name hash and the contract address. Armed with this information we can for example write a function that extends the name if it expires within 1000 blocks: @@ -1073,8 +1113,34 @@ however is in the gas consumption — while `abort` returns unused gas, a call t ## Delegation signature -Some chain operations (`Oracle.` and `AENS.`) have an +Some chain operations (`Oracle.` and `AENSv2.`) have an optional delegation signature. This is typically used when a user/accounts -would like to allow a contract to act on it's behalf. The exact data to be -signed varies for the different operations, but in all cases you should prepend -the signature data with the `network_id` (`ae_mainnet` for the æternity mainnet, etc.). +would like to allow a contract to act on it's behalf. + +### From Ceres + +From the Ceres protocol version the delegation signatures have more structure, +including a unique tag, `network_id` and identifiers; there are five different +delegation signatures: + + - AENS wildcard - the user signs: `owner account + contract` + - `AENS_PRECLAIM` - the user signs: `owner account + contract` + - `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `owner account + name hash + contract` + - `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `owner account + contract` + - `ORACLE_RESPOND` - the user signs: `query id + contract` + +See [Serialized signature +data](https://github.com/aeternity/protocol/blob/master/contracts/fate.md#from-ceres-serialized-signature-data) +for the exact structure used. + +### Before ceres + +The exact data to be signed varies for the different operations, but in all +cases you should prepend the signature data with the `network_id` (`ae_mainnet` +for the æternity mainnet, etc.). + +There are four different delegation signatures: + - `AENS_PRECLAIM` - the user signs: owner `network_id + account + contract` + - `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `network_id + owner account + name hash + contract` + - `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `network_id + owner account + contract` + - `ORACLE_RESPOND` - the user signs: `network_id + query id + contract` diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 450568c..8cbcf1f 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -14,6 +14,7 @@ The out-of-the-box namespaces are: - [Address](#address) - [AENS](#aens) +- [AENSv2](#aensv2) - [Auth](#auth) - [Bits](#bits) - [Bytes](#bytes) @@ -31,6 +32,7 @@ The following ones need to be included as regular files with `.aes` suffix, for include "List.aes" ``` +- [AENSCompat](#aenscompat) - [Bitwise](#bitwise) - [BLS12_381](#bls12_381) - [Func](#func) @@ -55,6 +57,12 @@ Address.to_str(a : address) : string Base58 encoded string +#### to_bytes +``` +Address.to_bytes(a : address) : bytes(32) +``` + +The binary representation of the address. #### is_contract ``` @@ -90,13 +98,10 @@ Cast address to contract type C (where `C` is a contract) ### AENS -The following functionality is available for interacting with the æternity -naming system (AENS). -If `owner` is equal to `Contract.address` the signature `signature` is -ignored, and can be left out since it is a named argument. Otherwise we need -a signature to prove that we are allowed to do AENS operations on behalf of -`owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), -i.e. the signature material should be prefixed by the network id. +The old AENS namespace, kept in the compiler to be able to interact with +contracts from before Ceres, compiled using aesophia compiler version 7.x and +earlier. Used in [AENSCompat](#aenscompat) when converting between old and new +pointers. #### Types @@ -113,12 +118,41 @@ datatype pointee = AccountPt(address) | OraclePt(address) | ContractPt(address) | ChannelPt(address) ``` +### AENSv2 + +Note: introduced in v8.0 + +The following functionality is available for interacting with the æternity +naming system (AENS). If `owner` is equal to `Contract.address` the signature +`signature` is ignored, and can be left out since it is a named argument. +Otherwise we need a signature to prove that we are allowed to do AENS +operations on behalf of `owner`. The [signature is tied to a network +id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), +i.e. the signature material should be prefixed by the network id. + +#### Types + +##### name +``` +datatype name = Name(address, Chain.ttl, map(string, AENSv2.pointee)) +``` + + +##### pointee + +``` +datatype pointee = AccountPt(address) | OraclePt(address) + | ContractPt(address) | ChannelPt(address) | DataPt(bytes()) +``` + +Note: on-chain there is a maximum length enforced for `DataPt`, it is 1024 bytes. +Sophia itself does _not_ check for this. #### Functions ##### resolve ``` -AENS.resolve(name : string, key : string) : option('a) +AENSv2.resolve(name : string, key : string) : option('a) ``` Name resolution. Here `name` should be a registered name and `key` one of the attributes @@ -129,74 +163,107 @@ type checked against this type at run time. ##### lookup ``` -AENS.lookup(name : string) : option(AENS.name) +AENSv2.lookup(name : string) : option(AENSv2.name) ``` -If `name` is an active name `AENS.lookup` returns a name object. +If `name` is an active name `AENSv2.lookup` returns a name object. The three arguments to `Name` are `owner`, `expiry` and a map of the `pointees` for the name. Note: the expiry of the name is always a fixed TTL. For example: ``` -let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") +let Some(AENSv2.Name(owner, FixedTTL(expiry), ptrs)) = AENSv2.lookup("example.chain") ``` +Note: Changed to produce `AENSv2.name` in v8.0 (Ceres protocol upgrade). ##### preclaim ``` -AENS.preclaim(owner : address, commitment_hash : hash, ) : unit +AENSv2.preclaim(owner : address, commitment_hash : hash, ) : unit ``` -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `Contract.address` (concatenated as byte arrays). +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `Contract.address`. ##### claim ``` -AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit +AENSv2.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `name_hash` + `Contract.address` -(concatenated as byte arrays) -using the private key of the `owner` account for signing. +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `name_hash`, and +`Contract.address`. ##### transfer ``` -AENS.transfer(owner : address, new_owner : address, name : string, ) : unit +AENSv2.transfer(owner : address, new_owner : address, name : string, ) : unit ``` Transfers name to the new owner. -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `name_hash` + `Contract.address` -(concatenated as byte arrays) -using the private key of the `owner` account for signing. +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `name_hash`, and +`Contract.address`. ##### revoke ``` -AENS.revoke(owner : address, name : string, ) : unit +AENSv2.revoke(owner : address, name : string, ) : unit ``` Revokes the name to extend the ownership time. -The [signature](./sophia_features.md#delegation-signature) should be over -`network id` + `owner address` + `name_hash` + `Contract.address` -(concatenated as byte arrays) -using the private key of the `owner` account for signing. +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `name_hash`, and +`Contract.address`. ##### update ``` -AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), - new_ptrs : map(string, AENS.pointee), ) : unit +AENSv2.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : option(map(string, AENSv2.pointee)), ) : unit ``` Updates the name. If the optional parameters are set to `None` that parameter will not be updated, for example if `None` is passed as `expiry` the expiry block of the name is not changed. +Note: Changed to consume `AENSv2.pointee` in v8.0 (Ceres protocol upgrade). + +The [signature](./sophia_features.md#delegation-signature) should be a +serialized structure containing `network id`, `owner address`, and +`Contract.address`. Using the private key of `owner address` for signing. + +From Ceres (i.e. FATE VM version 3) the +[signature](./sophia_features.md#delegation-signature) can also be generic +(allowing _all_, existing and future, names to be delegated with one +signature), i.e. containing `network id`, `owner address`, `name_hash`, and +`Contract.address`. ### Auth @@ -236,7 +303,10 @@ namespace Chain = Auth.tx_hash : option(hash) ``` -Gets the transaction hash during authentication. +Gets the transaction hash during authentication. Note: `Auth.tx_hash` +computation differs between protocol versions (changed in Ceres!), see +[aeserialisation](https://github.com/aeternity/protocol/blob/master/serializations.md) +specification for details. ### Bits @@ -315,7 +385,7 @@ Each bit is true if and only if it was 1 in `a` and 0 in `b` ### Bytes -#### to_int +#### to\_int ``` Bytes.to_int(b : bytes(n)) : int ``` @@ -323,7 +393,7 @@ Bytes.to_int(b : bytes(n)) : int Interprets the byte array as a big endian integer -#### to_str +#### to\_str ``` Bytes.to_str(b : bytes(n)) : string ``` @@ -336,7 +406,8 @@ Returns the hexadecimal representation of the byte array Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) ``` -Concatenates two byte arrays +Concatenates two byte arrays. If `m` and `n` are known at compile time, the +result can be used as a fixed size byte array, otherwise it has type `bytes()`. #### split @@ -346,6 +417,38 @@ Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) Splits a byte array at given index +#### split\_any +``` +Bytes.split_any(a : bytes(), at : int) : option(bytes() * bytes(n)) +``` + +Splits an arbitrary size byte array at index `at`. If `at` is positive split +from the beginning of the array, if `at` is negative, split `abs(at)` from the +_end_ of the array. If the array is shorter than `abs(at)` then `None` is +returned. + +#### to\_fixed\_size +``` +Bytes.to_fixed_size(a : bytes()) : option(bytes(n)) +``` + +Converts an arbitrary size byte array to a fix size byte array. If `a` is +not `n` bytes, `None` is returned. + +#### to\_any\_size +``` +Bytes.to_any_size(a : bytes(n)) : bytes() +``` + +Converts a fixed size byte array to an arbitrary size byte array. This is a +no-op at run-time, and only used during type checking. + +#### size +``` +Bytes.size(a : bytes()) : int +``` + +Computes the lenght/size of a byte array. ### Call @@ -381,6 +484,12 @@ Call.gas_price : int The gas price of the current call. +#### mulmod +``` +Int.mulmod : (a : int, b : int, q : int) : int +``` + +Combined multiplication and modulus, returns `(a * b) mod q`. #### fee ``` @@ -469,39 +578,6 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). - -##### coinbase -``` -Chain.coinbase : address -``` - -The address of the account that mined the current block. - - -##### timestamp -``` -Chain.timestamp : int -``` - -The timestamp of the current block (unix time, milliseconds). - - -##### difficulty -``` -Chain.difficulty : int -``` - -The difficulty of the current block. - - -##### gas -``` -Chain.gas_limit : int -``` - -The gas limit of the current block. - - ##### bytecode_hash ``` Chain.bytecode_hash : 'c => option(hash) @@ -538,7 +614,6 @@ charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in `Contract.balance`, however. - The type `'c` must be instantiated with a contract. @@ -565,6 +640,7 @@ main contract Market = The typechecker must be certain about the created contract's type, so it is worth writing it explicitly as shown in the example. + ##### clone ``` Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... @@ -603,8 +679,8 @@ Example usage: ``` payable contract interface Auction = entrypoint init : (int, string) => void - stateful payable entrypoint buy : (int) => () - stateful entrypoint sell : (int) => () + stateful payable entrypoint buy : (int) => unit + stateful entrypoint sell : (int) => unit main contract Market = type state = list(Auction) @@ -623,11 +699,71 @@ implementation of the `init` function does not actually return `state`, but calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`. +##### coinbase +``` +Chain.coinbase : address +``` + +The address of the account that mined the current block. + + +##### difficulty +``` +Chain.difficulty : int +``` + +The difficulty of the current block. + + ##### event ``` Chain.event(e : event) : unit ``` -Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract. + +Emits the event. To use this function one needs to define the `event` type as a +`datatype` in the contract. + + +##### gas\_limit +``` +Chain.gas_limit : int +``` + +The gas limit of the current block. + + +##### network\_id +``` +Chain.network\_id : string +``` + +The network id of the chain. + + +#### poseidon +``` +Crypto.poseidon(x1 : int, x2 : int) : int +``` + +Hash two integers (in the scalar field of BLS12-381) to another integer (in the scalar +field of BLS12-281). This is a ZK/SNARK-friendly hash function. + + +##### spend +``` +Chain.spend(to : address, amount : int) : unit +``` + +Spend `amount` tokens to `to`. Will fail (and abort the contract) if contract +doesn't have `amount` tokens to transfer, or, if `to` is not `payable`. + + +##### timestamp +``` +Chain.timestamp : int +``` + +The timestamp of the current block (unix time, milliseconds). ### Char @@ -705,11 +841,14 @@ Hash any object to blake2b #### verify_sig ``` -Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool +Crypto.verify_sig(msg : bytes(), pubkey : address, sig : signature) : bool ``` Checks if the signature of `msg` was made using private key corresponding to -the `pubkey` +the `pubkey`. + +Note: before v8 of the compiler, `msg` had type `hash` (i.e. `bytes(32)`). + #### ecverify_secp256k1 ``` @@ -742,12 +881,21 @@ Verifies a standard 64-byte ECDSA signature (`R || S`). ### Int -#### to_str +#### to\_str ``` -Int.to_str : int => string +Int.to_str(n : int) : string ``` -Casts integer to string using decimal representation +Casts the integer to a string (in decimal representation). + +#### to\_bytes +``` +Int.to_bytes(n : int, size : int) : bytes() +``` + +Casts the integer to a byte array with `size` bytes (big endian, truncating if +necessary not preserving signedness). I.e. if you try to squeeze `-129` into a +single byte that will be indistinguishable from `127`. ### Map @@ -806,11 +954,11 @@ Oracle.register(, acct : address, qfee : int, ttl : Chain Registers new oracle answering questions of type `'a` with answers of type `'b`. * The `acct` is the address of the oracle to register (can be the same as the contract). -* `signature` is a signature proving that the contract is allowed to register the account - - the `network id` + `account address` + `contract address` (concatenated as byte arrays) is - [signed](./sophia_features.md#delegation-signature) with the - private key of the account, proving you have the private key of the oracle to be. If the - address is the same as the contract `sign` is ignored and can be left out entirely. +* The [signature](./sophia_features.md#delegation-signature) should be a + serialized structure containing `network id`, `account address`, and + `contract address`. Using the private key of `account address` for signing. + Proving you have the private key of the oracle to be. If the address is the same + as the contract `sign` is ignored and can be left out entirely. * The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. * The `ttl` is the Time To Live for the oracle in key blocks, either relative to the current key block height (`RelativeTTL(delta)`) or a fixed key block height (`FixedTTL(height)`). @@ -824,7 +972,7 @@ Examples: ``` -#### get_question +#### get\_question ``` Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a ``` @@ -837,12 +985,11 @@ Checks what was the question of query `q` on oracle `o` Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit ``` -Responds to the question `q` on `o`. -Unless the contract address is the same as the oracle address the `signature` -(which is an optional, named argument) +Responds to the question `q` on `o`. Unless the contract address is the same +as the oracle address the `signature` (which is an optional, named argument) needs to be provided. Proving that we have the private key of the oracle by -[signing](./sophia_features.md#delegation-signature) -the `network id` + `oracle query id` + `contract address` +[signing](./sophia_features.md#delegation-signature) should be a serialized +structure containing `network id`, `oracle query id`, and `contract address`. #### extend @@ -855,7 +1002,7 @@ Extends TTL of an oracle. * `o` is the oracle being extended * `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. -#### query_fee +#### query\_fee ``` Oracle.query_fee(o : oracle('a, 'b)) : int ``` @@ -876,7 +1023,7 @@ Asks the oracle a question. The call fails if the oracle could expire before an answer. -#### get_answer +#### get\_answer ``` Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) ``` @@ -914,88 +1061,21 @@ It returns `true` iff the oracle query exist and has the expected type. These need to be explicitly included (with `.aes` suffix) -### Bitwise +### AENSCompat -Bitwise operations on arbitrary precision integers. - -#### bsr +#### pointee\_to\_V2 ``` -Bitwise.bsr(n : int, x : int) : int +AENSCompat.pointee_to_V2(p : AENS.pointee) : AENSv2.pointee ``` -Logical bit shift `x` right `n` positions. +Translate old pointee format to new, this is always possible. - -#### bsl +#### pointee\_from\_V2 ``` -Bitwise.bsl(n : int, x : int) : int +AENSCompat.pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) ``` -Logical bit shift `x` left `n` positions. - - -#### bsli -``` -Bitwise.bsli(n : int, x : int, lim : int) : int -``` - -Logical bit shift `x` left `n` positions, limit to `lim` bits. - - -#### band -``` -Bitwise.band(x : int, y : int) : int -``` - -Bitwise `and` of `x` and `y`. - - -#### bor -``` -Bitwise.bor(x : int, y : int) : int -``` - -Bitwise `or` of `x` and `y`. - - -#### bxor -``` -Bitwise.bxor(x : int, y : int) : int -``` - -Bitwise `xor` of `x` and `y`. - - -#### bnot -``` -Bitwise.bnot(x : int) : int -``` - -Bitwise `not` of `x`. Defined and implemented as `bnot(x) = bxor(x, -1)`. - - -#### uband -``` -Bitwise.uband(x : int, y : int) : int -``` - -Bitwise `and` of _non-negative_ numbers `x` and `y`. - - -#### ubor -``` -Bitwise.ubor(x : int, y : int) : int -``` - -Bitwise `or` of _non-negative_ `x` and `y`. - - -#### ubxor -``` -Bitwise.ubxor(x : int, y : int) : int -``` - -Bitwise `xor` of _non-negative_ `x` and `y`. +Translate new pointee format to old, `DataPt` can't be translated, so `None` is returned in this case. ### BLS12\_381 @@ -2391,6 +2471,15 @@ to_int(s : string) : option(int) Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into an integer. If the string doesn't contain a valid number `None` is returned. +#### to\_bytes +``` +to_bytes(s : string) : bytes() +``` + +Converts string into byte array. String is UTF-8 encoded. I.e. +`String.length(s)` is not guaranteed to be equal to +`Bytes.size(String.to_bytes(s))`. + #### sha3 ``` sha3(s : string) : hash diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index f0df9e6..6a9ae86 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -30,6 +30,7 @@ interface main using as for hiding - `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix - `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix - `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix +- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix Valid string escape codes are @@ -239,6 +240,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' | AccountAddress | ContractAddress // Chain identifiers | OracleAddress | OracleQueryId // Chain identifiers + | Signature // Signature | '???' // Hole expression 1 + ??? Generator ::= Pattern '<-' Expr // Generator @@ -256,8 +258,8 @@ Path ::= Id // Record field BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' - | '|>' -UnOp ::= '-' | '!' + | 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>' +UnOp ::= '-' | '!' | 'bnot' ``` ## Operators types @@ -265,10 +267,11 @@ UnOp ::= '-' | '!' | Operators | Type | --- | --- | `-` `+` `*` `/` `mod` `^` | arithmetic operators -| `!` `&&` `||` | logical operators +| `!` `&&` `\|\|` | logical operators +| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators | `==` `!=` `<` `>` `=<` `>=` | comparison operators | `::` `++` | list operators -| `|>` | functional operators +| `\|>` | functional operators ## Operator precedence @@ -276,13 +279,17 @@ In order of highest to lowest precedence. | Operators | Associativity | --- | --- -| `!` | right +| `!` `bnot`| right | `^` | left | `*` `/` `mod` | left | `-` (unary) | right | `+` `-` | left +| `<<` `>>` | left | `::` `++` | right | `<` `>` `=<` `>=` `==` `!=` | none +| `band` | left +| `bxor` | left +| `bor` | left | `&&` | right -| `||` | right -| `|>` | left +| `\|\|` | right +| `\|>` | left diff --git a/priv/stdlib/AENSCompat.aes b/priv/stdlib/AENSCompat.aes new file mode 100644 index 0000000..baaa05a --- /dev/null +++ b/priv/stdlib/AENSCompat.aes @@ -0,0 +1,17 @@ +namespace AENSCompat = + // Translate old format to new format - always possible + function pointee_to_V2(p : AENS.pointee) : AENSv2.pointee = + switch(p) + AENS.AccountPt(a) => AENSv2.AccountPt(a) + AENS.OraclePt(a) => AENSv2.OraclePt(a) + AENS.ContractPt(a) => AENSv2.ContractPt(a) + AENS.ChannelPt(a) => AENSv2.ChannelPt(a) + + // Translate new format to old format - option type! + function pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) = + switch(p2) + AENSv2.AccountPt(a) => Some(AENS.AccountPt(a)) + AENSv2.OraclePt(a) => Some(AENS.OraclePt(a)) + AENSv2.ContractPt(a) => Some(AENS.ContractPt(a)) + AENSv2.ChannelPt(a) => Some(AENS.ChannelPt(a)) + AENSv2.DataPt(_) => None diff --git a/priv/stdlib/Bitwise.aes b/priv/stdlib/Bitwise.aes deleted file mode 100644 index cc273f0..0000000 --- a/priv/stdlib/Bitwise.aes +++ /dev/null @@ -1,126 +0,0 @@ -@compiler >= 4.3 - -namespace Bitwise = - - // bit shift 'x' right 'n' postions - function bsr(n : int, x : int) : int = - let step = 2^n - let res = x / step - if (x >= 0 || x mod step == 0) - res - else - res - 1 - - // bit shift 'x' left 'n' positions - function bsl(n : int, x : int) : int = - x * 2^n - - // bit shift 'x' left 'n' positions, limit at 'lim' bits - function bsli(n : int, x : int, lim : int) : int = - (x * 2^n) mod (2^lim) - - // bitwise 'and' for arbitrary precision integers - function band(a : int, b : int) : int = - if (a >= 0 && b >= 0) - uband_(a, b) - elif (b >= 0) - ubnand_(b, -1 - a) - elif (a >= 0) - ubnand_(a, -1 - b) - else - -1 - ubor_(-1 - a, -1 - b) - - // bitwise 'or' for arbitrary precision integers - function - bor : (int, int) => int - bor(0, b) = b - bor(a, 0) = a - bor(a : int, b : int) : int = - if (a >= 0 && b >= 0) - ubor_(a, b) - elif (b >= 0) - -1 - ubnand_(-1 - a, b) - elif (a >= 0) - -1 - ubnand_(-1 - b, a) - else - -1 - uband_(-1 - a, -1 - b) - - // bitwise 'xor' for arbitrary precision integers - function - bxor : (int, int) => int - bxor(0, b) = b - bxor(a, 0) = a - bxor(a, b) = - if (a >= 0 && b >= 0) - ubxor_(a, b) - elif (b >= 0) - -1 - ubxor_(-1 - a, b) - elif (a >= 0) - -1 - ubxor_(a, -1 - b) - else - ubxor_(-1 - a, -1 - b) - - // bitwise 'not' for arbitrary precision integers - function bnot(a : int) = bxor(a, -1) - - // Bitwise 'and' for non-negative integers - function uband(a : int, b : int) : int = - require(a >= 0 && b >= 0, "uband is only defined for non-negative integers") - switch((a, b)) - (0, _) => 0 - (_, 0) => 0 - _ => uband__(a, b, 1, 0) - - private function uband_(a, b) = uband__(a, b, 1, 0) - - private function - uband__(0, b, val, acc) = acc - uband__(a, 0, val, acc) = acc - uband__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 2 => uband__(a / 2, b / 2, val * 2, acc + val) - _ => uband__(a / 2, b / 2, val * 2, acc) - - // Bitwise 'or' for non-negative integers - function ubor(a, b) = - require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers") - switch((a, b)) - (0, _) => b - (_, 0) => a - _ => ubor__(a, b, 1, 0) - - private function ubor_(a, b) = ubor__(a, b, 1, 0) - - private function - ubor__(0, 0, val, acc) = acc - ubor__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 0 => ubor__(a / 2, b / 2, val * 2, acc) - _ => ubor__(a / 2, b / 2, val * 2, acc + val) - - //Bitwise 'xor' for non-negative integers - function - ubxor : (int, int) => int - ubxor(0, b) = b - ubxor(a, 0) = a - ubxor(a, b) = - require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers") - ubxor__(a, b, 1, 0) - - private function ubxor_(a, b) = ubxor__(a, b, 1, 0) - - private function - ubxor__(0, 0, val, acc) = acc - ubxor__(a, b, val, acc) = - switch(a mod 2 + b mod 2) - 1 => ubxor__(a / 2, b / 2, val * 2, acc + val) - _ => ubxor__(a / 2, b / 2, val * 2, acc) - - private function ubnand_(a, b) = ubnand__(a, b, 1, 0) - - private function - ubnand__(0, b, val, acc) = acc - ubnand__(a, b, val, acc) = - switch((a mod 2, b mod 2)) - (1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val) - _ => ubnand__(a / 2, b / 2, val * 2, acc) diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index e0201d0..59bbdf1 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -282,9 +282,9 @@ namespace List = private function asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a)) asc(lt, x, acc, h::t) = - if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t) + if(lt(h, x)) reverse(x::acc) :: monotonic_subs(lt, h::t) else asc(lt, h, x::acc, t) - asc(_, x, acc, []) = [List.reverse(x::acc)] + asc(_, x, acc, []) = [reverse(x::acc)] /** Merges list of sorted lists */ diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes index 33f813b..9927774 100644 --- a/priv/stdlib/String.aes +++ b/priv/stdlib/String.aes @@ -1,5 +1,8 @@ include "List.aes" namespace String = + // Gives a bytes() representation of the string + function to_bytes(s : string) : bytes() = StringInternal.to_bytes(s) + // Computes the SHA3/Keccak hash of the string function sha3(s : string) : hash = StringInternal.sha3(s) // Computes the SHA256 hash of the string. diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..ed141dc --- /dev/null +++ b/rebar.config @@ -0,0 +1,22 @@ +%% -*- mode: erlang; indent-tabs-mode: nil -*- + +{erl_opts, [debug_info]}. + +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.4.1"}}} + , {eblake2, "1.0.0"} + , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} + ]}. + +{dialyzer, [ + {warnings, [unknown]}, + {plt_apps, all_deps}, + {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} + ]}. + +{relx, [{release, {aesophia, "8.0.1"}, + [aesophia, aebytecode]}, + + {dev_mode, true}, + {include_erts, false}, + + {extended_start_script, true}]}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..8a19f04 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,31 @@ +{"1.2.0", +[{<<"aebytecode">>, + {git,"https://github.com/aeternity/aebytecode.git", + {ref,"6bd6f82c70d800950ea1a2c70c364a4181ff5291"}}, + 0}, + {<<"aeserialization">>, + {git,"https://github.com/aeternity/aeserialization.git", + {ref,"b26e6d105424748ba1c27917267b7cff07f37802"}}, + 1}, + {<<"base58">>, + {git,"https://github.com/aeternity/erl-base58.git", + {ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}}, + 2}, + {<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, + {<<"enacl">>, + {git,"https://github.com/aeternity/enacl.git", + {ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}, + 2}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1}, + {<<"jsx">>, + {git,"https://github.com/talentdeficit/jsx.git", + {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, + 0}]}. +[ +{pkg_hash,[ + {<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>}, + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}, +{pkg_hash_ext,[ + {<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>}, + {<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]} +]. diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 7741b23..9c72e90 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -199,7 +199,8 @@ encode_expr({bytes, _, B}) -> <> = B, list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N]))); encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id; - Lit == contract_pubkey; Lit == account_pubkey -> + Lit == contract_pubkey; Lit == account_pubkey; + Lit == signature -> aeser_api_encoder:encode(Lit, L); encode_expr({app, _, {'-', _}, [{int, _, N}]}) -> encode_expr({int, [], -N}); @@ -308,6 +309,8 @@ decode_type(#{list := [Et]}) -> decode_type(#{map := Ets}) -> Ts = decode_types(Ets), ["map",$(,lists:join(",", Ts),$)]; +decode_type(#{bytes := any}) -> + ["bytes()"]; decode_type(#{bytes := Len}) -> ["bytes(", integer_to_list(Len), ")"]; decode_type(#{variant := Ets}) -> diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 971389b..d72698d 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -90,8 +90,9 @@ -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. --type byte_constraint() :: {is_bytes, utype()} - | {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}. +-type byte_constraint() :: {is_bytes, term(), utype()} + | {is_fixed_bytes, term(), utype()} + | {add_bytes, aeso_syntax:ann(), concat | split | split_any, utype(), utype(), utype()}. -type aens_resolve_constraint() :: {aens_resolve_type, utype()}. -type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}. @@ -155,7 +156,6 @@ , in_pattern = false :: boolean() , in_guard = false :: boolean() , stateful = false :: boolean() - , unify_throws = true :: boolean() , current_const = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id() , what = top :: top | namespace | contract | contract_interface @@ -352,11 +352,11 @@ bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env) Sys = [{origin, system}], TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end, Fields = - [ {field_t, AnnF, Entrypoint, contract_call_type(Type)} + [ {field_t, AnnF, Entrypoint, contract_call_type(aeso_syntax:set_ann(Sys, Type))} || {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++ [ {field_t, AnnF, Entrypoint, contract_call_type( - {fun_t, AnnF, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)}) + {fun_t, Sys, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)}) } || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents, Name =/= "init" @@ -595,6 +595,8 @@ global_env() -> TTL = {qid, Ann, ["Chain", "ttl"]}, Pointee = {qid, Ann, ["AENS", "pointee"]}, AENSName = {qid, Ann, ["AENS", "name"]}, + PointeeV2 = {qid, Ann, ["AENSv2", "pointee"]}, + AENSNameV2 = {qid, Ann, ["AENSv2", "name"]}, Fr = {qid, Ann, ["MCL_BLS12_381", "fr"]}, Fp = {qid, Ann, ["MCL_BLS12_381", "fp"]}, Fp2 = {tuple_t, Ann, [Fp, Fp]}, @@ -652,6 +654,7 @@ global_env() -> {"block_height", Int}, {"difficulty", Int}, {"gas_limit", Int}, + {"network_id", String}, {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, {"create", Stateful( FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} @@ -730,14 +733,7 @@ global_env() -> AENSScope = #scope { funs = MkDefs( - [{"resolve", Fun([String, String], option_t(Ann, A))}, - {"preclaim", SignFun([Address, Hash], Unit)}, - {"claim", SignFun([Address, String, Int, Int], Unit)}, - {"transfer", SignFun([Address, Address, String], Unit)}, - {"revoke", SignFun([Address, String], Unit)}, - {"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, Pointee))], Unit)}, - {"lookup", Fun([String], option_t(Ann, AENSName))}, - %% AENS pointee constructors + [%% AENS pointee constructors {"AccountPt", Fun1(Address, Pointee)}, {"OraclePt", Fun1(Address, Pointee)}, {"ContractPt", Fun1(Address, Pointee)}, @@ -747,6 +743,26 @@ global_env() -> ]) , types = MkDefs([{"pointee", 0}, {"name", 0}]) }, + AENSv2Scope = #scope + { funs = MkDefs( + [{"resolve", Fun([String, String], option_t(Ann, A))}, + {"preclaim", SignFun([Address, Hash], Unit)}, + {"claim", SignFun([Address, String, Int, Int], Unit)}, + {"transfer", SignFun([Address, Address, String], Unit)}, + {"revoke", SignFun([Address, String], Unit)}, + {"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, PointeeV2))], Unit)}, + {"lookup", Fun([String], option_t(Ann, AENSNameV2))}, + %% AENS pointee constructors v2 + {"AccountPt", Fun1(Address, PointeeV2)}, + {"OraclePt", Fun1(Address, PointeeV2)}, + {"ContractPt", Fun1(Address, PointeeV2)}, + {"ChannelPt", Fun1(Address, PointeeV2)}, + {"DataPt", Fun1(Bytes(any), PointeeV2)}, + %% Name object constructor v2 + {"Name", Fun([Address, TTL, Map(String, PointeeV2)], AENSNameV2)} + ]) + , types = MkDefs([{"pointee", 0}, {"name", 0}]) }, + MapScope = #scope { funs = MkDefs( [{"from_list", Fun1(List(Pair(K, V)), Map(K, V))}, @@ -760,13 +776,14 @@ global_env() -> %% Crypto/Curve operations CryptoScope = #scope { funs = MkDefs( - [{"verify_sig", Fun([Hash, Address, SignId], Bool)}, + [{"verify_sig", Fun([Bytes('_'), Address, SignId], Bool)}, {"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)}, {"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)}, {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, - {"blake2b", Fun1(A, Hash)}]) }, + {"blake2b", Fun1(A, Hash)}, + {"poseidon", Fun([Int, Int], Int)}]) }, %% Fancy BLS12-381 crypto operations MCL_BLS12_381_Scope = #scope @@ -814,6 +831,7 @@ global_env() -> [{"length", Fun1(String, Int)}, {"concat", Fun([String, String], String)}, {"to_list", Fun1(String, List(Char))}, + {"to_bytes", Fun1(String, Bytes(any))}, {"from_list", Fun1(List(Char), String)}, {"to_upper", Fun1(String, String)}, {"to_lower", Fun1(String, String)}, @@ -844,21 +862,28 @@ global_env() -> %% Bytes BytesScope = #scope { funs = MkDefs( - [{"to_int", Fun1(Bytes(any), Int)}, - {"to_str", Fun1(Bytes(any), String)}, - {"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))}, - {"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))} + [{"to_int", Fun1(Bytes('_'), Int)}, + {"to_str", Fun1(Bytes('_'), String)}, + {"to_fixed_size", Fun1(Bytes(any), Option(Bytes(fixed)))}, + {"to_any_size", Fun1(Bytes(fixed), Bytes(any))}, + {"size", Fun1(Bytes('_'), Int)}, + {"concat", FunC(bytes_concat, [Bytes('_'), Bytes('_')], Bytes('_'))}, + {"split", FunC1(bytes_split, Bytes(fixed), Pair(Bytes(fixed), Bytes(fixed)))}, + {"split_any", Fun([Bytes(any), Int], Option(Pair(Bytes(any), Bytes(any))))} ]) }, %% Conversion - IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, + IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, + {"to_bytes", Fun([Int, Int], Bytes(any))}, + {"mulmod", Fun([Int, Int, Int], Int)}]) }, + AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, + {"to_bytes", Fun1(Address, Bytes(32))}, {"to_contract", FunC(address_to_contract, [Address], A)}, {"is_oracle", Fun1(Address, Bool)}, {"is_contract", Fun1(Address, Bool)}, {"is_payable", Fun1(Address, Bool)}]) }, - #env{ scopes = #{ [] => TopScope , ["Chain"] => ChainScope @@ -866,6 +891,7 @@ global_env() -> , ["Call"] => CallScope , ["Oracle"] => OracleScope , ["AENS"] => AENSScope + , ["AENSv2"] => AENSv2Scope , ["Map"] => MapScope , ["Auth"] => AuthScope , ["Crypto"] => CryptoScope @@ -926,14 +952,14 @@ infer(Contracts, Options) -> {Env1, Decls} = infer1(Env, Contracts1, [], Options), when_warning(warn_unused_functions, fun() -> destroy_and_report_unused_functions() end), when_option(warn_error, fun() -> destroy_and_report_warnings_as_type_errors() end), - WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), - Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), {Env2, DeclsFolded, DeclsUnfolded} = case proplists:get_value(dont_unfold, Options, false) of true -> {Env1, Decls, Decls}; false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), {E, Decls, unfold_record_types(E, Decls)} end, + WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), + Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), case proplists:get_value(return_env, Options, false) of false -> {DeclsFolded, DeclsUnfolded, Warnings}; true -> {Env2, DeclsFolded, DeclsUnfolded, Warnings} @@ -1136,7 +1162,7 @@ infer_contract(Env0, What, Defs0, Options) -> _ = bind_funs(lists:map(FunBind, Functions), #env{}), FunMap = maps:from_list([ {FunName(Def), Def} || Def <- Functions ]), check_reserved_entrypoints(FunMap), - DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap), + DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Env3#env.namespace, Def) end, FunMap), SCCs = aeso_utils:scc(DepGraph), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), %% Remove namespaces used in the current namespace @@ -1583,7 +1609,7 @@ check_reserved_entrypoints(Funs) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), TypeSig = {type_sig, Ann, none, Named, Args, Ret}, - register_implementation(Id, TypeSig), + register_implementation(Env, 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}), @@ -1591,13 +1617,16 @@ check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, 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 +-spec register_implementation(env(), FunId, FunSig) -> true | no_return() when FunId :: aeso_syntax:id(), FunSig :: typesig(). -register_implementation(Id, Sig) -> +register_implementation(Env, Id, Sig) -> Name = name(Id), case ets_lookup(functions_to_implement, Name) of - [{Name, Interface, Decl = {fun_decl, _, DeclId, _}}] -> + [{Name, Interface, Decl = {fun_decl, _, DeclId, FunT}}] -> + When = {implement_interface_fun, aeso_syntax:get_ann(Sig), Name, name(Interface)}, + unify(Env, typesig_to_fun_t(Sig), FunT, When), + DeclStateful = aeso_syntax:get_ann(stateful, Decl, false), DeclPayable = aeso_syntax:get_ann(payable, Decl, false), @@ -1625,7 +1654,7 @@ infer_nonrec(Env, LetFun) -> create_constraints(), NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun), check_special_funs(Env, NewLetFun), - register_implementation(get_letfun_id(LetFun), Sig), + register_implementation(Env, get_letfun_id(LetFun), Sig), solve_then_destroy_and_report_unsolved_constraints(Env), Result = {TypeSig, _} = instantiate(NewLetFun), print_typesig(TypeSig), @@ -1655,11 +1684,11 @@ infer_letrec(Env, Defs) -> Inferred = [ begin Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF), - register_implementation(get_letfun_id(LetFun), TypeSig), + register_implementation(Env, 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}), - solve_constraints(Env), + solve_all_constraints(Env), ?PRINT_TYPES("Checked ~s : ~s\n", [Name, pp(dereference_deep(Got))]), Res @@ -1732,8 +1761,27 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) -> end. print_typesig({Name, TypeSig}) -> + assert_tvars(Name, TypeSig), ?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]). +assert_tvars(Name, TS) -> + TVars = assert_tvars_(TS, #{}), + case maps:size(TVars) > 256 of + true -> + type_error({too_many_tvars, Name, TS}); + false -> + ok + end. + +assert_tvars_({tvar, _, TV}, TVars) -> + TVars#{TV => ok}; +assert_tvars_(T, TVars) when is_tuple(T) -> + assert_tvars_(tuple_to_list(T), TVars); +assert_tvars_(Ts, TVars) when is_list(Ts) -> + lists:foldl(fun(T, TVars1) -> assert_tvars_(T, TVars1) end, TVars, Ts); +assert_tvars_(_, TVars) -> + TVars. + arg_type(ArgAnn, {id, Ann, "_"}) -> case aeso_syntax:get_ann(origin, Ann, user) of system -> fresh_uvar(ArgAnn); @@ -1772,8 +1820,8 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of - {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty); - _ when Freshen -> freshen_type(As, Ty); + {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty, [{fun_name, Id}]); + _ when Freshen -> freshen_type(As, Ty, [{fun_name, Id}]); _ -> Ty end, {set_qname(QId, Id), Ty1} @@ -1907,6 +1955,8 @@ infer_expr(_Env, Body={bytes, As, Bin}) -> {typed, As, Body, {bytes_t, As, byte_size(Bin)}}; infer_expr(_Env, Body={account_pubkey, As, _}) -> {typed, As, Body, {id, As, "address"}}; +infer_expr(_Env, Body={signature, As, Bin}) when byte_size(Bin) == 64 -> + {typed, As, Body, {bytes_t, As, 64}}; infer_expr(_Env, Body={oracle_pubkey, As, _}) -> Q = fresh_uvar(As), R = fresh_uvar(As), @@ -2015,7 +2065,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> 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"] ], + || element(3, FunName) =:= ["AENSv2", "resolve"] ], [ add_constraint({oracle_type, Ann, OType}) || OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)], OType =/= false ], @@ -2145,6 +2195,8 @@ check_valid_const_expr({bytes, _, _}) -> true; check_valid_const_expr({account_pubkey, _, _}) -> true; +check_valid_const_expr({signature, _, _}) -> + true; check_valid_const_expr({oracle_pubkey, _, _}) -> true; check_valid_const_expr({oracle_query_id, _, _}) -> @@ -2395,6 +2447,11 @@ infer_infix({IntOp, As}) IntOp == '^'; IntOp == 'mod' -> Int = {id, As, "int"}, {fun_t, As, [], [Int, Int], Int}; +infer_infix({BitOp, As}) + when BitOp == 'band'; BitOp == 'bor'; BitOp == 'bxor'; + BitOp == '<<'; BitOp == '>>' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int, Int], Int}; infer_infix({RelOp, As}) when RelOp == '=='; RelOp == '!='; RelOp == '<'; RelOp == '>'; @@ -2422,6 +2479,9 @@ infer_infix({'|>', As}) -> infer_prefix({'!',As}) -> Bool = {id, As, "bool"}, {fun_t, As, [], [Bool], Bool}; +infer_prefix({BitOp,As}) when BitOp =:= 'bnot' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int], Int}; infer_prefix({IntOp,As}) when IntOp =:= '-' -> Int = {id, As, "int"}, {fun_t, As, [], [Int], Int}. @@ -2574,61 +2634,124 @@ get_constraints() -> destroy_constraints() -> ets_delete(constraints). --spec solve_constraints(env()) -> ok. -solve_constraints(Env) -> - %% First look for record fields that appear in only one type definition - IsAmbiguous = - fun(#field_constraint{ - record_t = RecordType, - field = Field={id, _Attrs, FieldName}, - field_t = FieldType, - kind = Kind, - context = When }) -> - 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; - [#field_info{field_t = FldType, record_t = RecType}] -> - create_freshen_tvars(), - FreshFldType = freshen(FldType), - FreshRecType = freshen(RecType), - destroy_freshen_tvars(), - unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), - unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}), - false; - _ -> - %% ambiguity--need cleverer strategy - true - end; - (_) -> true - end, - AmbiguousConstraints = lists:filter(IsAmbiguous, get_constraints()), +%% Solve all constraints by iterating until no-progress - % The two passes on AmbiguousConstraints are needed - solve_ambiguous_constraints(Env, AmbiguousConstraints ++ AmbiguousConstraints). +-spec solve_all_constraints(env()) -> ok. +solve_all_constraints(Env) -> + Constraints = [C || C <- get_constraints(), not one_shot_field_constraint(Env, C) ], + solve_constraints_top(Env, Constraints). --spec solve_ambiguous_constraints(env(), [constraint()]) -> ok. -solve_ambiguous_constraints(Env, Constraints) -> - Unknown = solve_known_record_types(Env, Constraints), - if Unknown == [] -> ok; - length(Unknown) < length(Constraints) -> - %% progress! Keep trying. - solve_ambiguous_constraints(Env, Unknown); +solve_constraints_top(Env, Constraints) -> + UnsolvedCs = solve_constraints(Env, Constraints), + Progress = solve_unknown_record_constraints(Env, UnsolvedCs), + + if length(UnsolvedCs) < length(Constraints) orelse Progress == true -> + solve_constraints_top(Env, UnsolvedCs); true -> - case solve_unknown_record_types(Env, Unknown) of - true -> %% Progress! - solve_ambiguous_constraints(Env, Unknown); - _ -> ok %% No progress. Report errors later. - end + ok end. +-spec solve_constraints(env(), [constraint()]) -> [constraint()]. +solve_constraints(Env, Constraints) -> + [ C1 || C <- Constraints, C1 <- [dereference_deep(C)], not solve_constraint(Env, C1) ]. + +solve_unknown_record_constraints(Env, Constraints) -> + FieldCs = lists:filter(fun(#field_constraint{record_t = {uvar, _, _}}) -> true; (_) -> false end, Constraints), + FieldCsUVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- FieldCs]), + + FieldConstraint = fun(#field_constraint{ field = F, kind = K, context = Ctx }) -> {K, Ctx, F} end, + FieldsForUVar = fun(UVar) -> + [ FieldConstraint(FC) || FC = #field_constraint{record_t = U} <- FieldCs, U == UVar ] + end, + + + Solutions = [ solve_for_uvar(Env, UVar, FieldsForUVar(UVar)) || UVar <- FieldCsUVars ], + case lists:member(true, Solutions) of + true -> true; + false -> Solutions + end. + +%% -- Simple constraints -- +%% Returns true if solved (unified or type error) +solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) -> + false; +solve_constraint(Env, #field_constraint{record_t = RecordType, + field = Field = {id, _As, FieldName}, + field_t = FieldType, + context = When}) -> + RecId = record_type_name(RecordType), + Attrs = aeso_syntax:get_ann(RecId), + case lookup_type(Env, RecId) of + {_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t -> + FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields], + case proplists:get_value(FieldName, FieldTypes) of + undefined -> + type_error({missing_field, Field, RecId}); + FldType -> + solve_field_constraint(Env, FieldType, FldType, RecordType, app_t(Attrs, RecId, Formals), When) + end; + _ -> + type_error({not_a_record_type, instantiate(RecordType), When}) + end, + true; +solve_constraint(Env, C = #dependent_type_constraint{}) -> + check_named_argument_constraint(Env, C); +solve_constraint(Env, C = #named_argument_constraint{}) -> + check_named_argument_constraint(Env, C); +solve_constraint(_Env, {is_bytes, _, _}) -> false; +solve_constraint(_Env, {is_fixed_bytes, _, _}) -> false; +solve_constraint(Env, {add_bytes, Ann, Action, A0, B0, C0}) -> + A = unfold_types_in_type(Env, dereference(A0)), + B = unfold_types_in_type(Env, dereference(B0)), + C = unfold_types_in_type(Env, dereference(C0)), + case {A, B, C} of + {{bytes_t, _, M}, {bytes_t, _, N}, _} when is_integer(M), is_integer(N) -> + unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); + {{bytes_t, _, M}, _, {bytes_t, _, R}} when is_integer(M), is_integer(R), R >= M -> + unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); + {_, {bytes_t, _, N}, {bytes_t, _, R}} when is_integer(N), is_integer(R), R >= N -> + unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + {{bytes_t, _, _}, {bytes_t, _, _}, _} when Action == concat -> + unify(Env, {bytes_t, Ann, any}, C, {at, Ann}); + _ -> false + end; +solve_constraint(_, _) -> false. + +one_shot_field_constraint(Env, #field_constraint{record_t = RecordType, + field = Field = {id, _As, FieldName}, + field_t = FieldType, + kind = Kind, + context = When}) -> + 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}), + true; + [#field_info{field_t = FldType, record_t = RecType}] -> + solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When), + true; + _ -> + false + end; +one_shot_field_constraint(_Env, _Constraint) -> + false. + + +solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When) -> + create_freshen_tvars(), + FreshFldType = freshen(FldType), + FreshRecType = freshen(RecType), + destroy_freshen_tvars(), + unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), + unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}). + solve_then_destroy_and_report_unsolved_constraints(Env) -> - solve_constraints(Env), + solve_all_constraints(Env), destroy_and_report_unsolved_constraints(Env). destroy_and_report_unsolved_constraints(Env) -> @@ -2646,7 +2769,8 @@ destroy_and_report_unsolved_constraints(Env) -> (_) -> false end, OtherCs2), {BytesCs, OtherCs4} = - lists:partition(fun({is_bytes, _}) -> true; + lists:partition(fun({is_bytes, _, _}) -> true; + ({is_fixed_bytes, _, _}) -> true; ({add_bytes, _, _, _, _, _}) -> true; (_) -> false end, OtherCs3), @@ -2659,21 +2783,10 @@ destroy_and_report_unsolved_constraints(Env) -> (_) -> false end, OtherCs5), - Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ], - S == unsolved ], - [ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ], - - Unknown = solve_known_record_types(Env, FieldCs), - if Unknown == [] -> ok; - true -> - case solve_unknown_record_types(Env, Unknown) of - true -> ok; - Errors -> [ type_error(Err) || Err <- Errors ] - end - end, - + check_field_constraints(Env, FieldCs), check_record_create_constraints(Env, CreateCs), check_is_contract_constraints(Env, ContractCs), + check_named_args_constraints(Env, NamedArgCs), check_bytes_constraints(Env, BytesCs), check_aens_resolve_constraints(Env, AensResolveCs), check_oracle_type_constraints(Env, OracleTypeCs), @@ -2691,20 +2804,21 @@ 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. --spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved. +%% True if solved (unified or type error), false otherwise +-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false. check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) -> - unsolved; + false; check_named_argument_constraint(Env, C = #named_argument_constraint{ args = Args, name = Id = {id, _, Name}, type = Type }) -> case [ T || {named_arg_t, _, {id, _, Name1}, T, _} <- Args, Name1 == Name ] of [] -> - type_error({bad_named_argument, Args, Id}), - false; - [T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true - end; + type_error({bad_named_argument, Args, Id}); + [T] -> + unify(Env, T, Type, {check_named_arg_constraint, C}) + end, + true; check_named_argument_constraint(Env, #dependent_type_constraint{ named_args_t = NamedArgsT0, named_args = NamedArgs, @@ -2721,10 +2835,11 @@ check_named_argument_constraint(Env, ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]), GenType1 = specialize_dependent_type(ArgEnv, GenType), - unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}), - true; - _ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true - end. + unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}); + _ -> + unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}) + end, + true. specialize_dependent_type(Env, Type) -> case dereference(Type) of @@ -2740,70 +2855,44 @@ specialize_dependent_type(Env, Type) -> _ -> Type %% Currently no deep dependent types end. -%% -- Bytes constraints -- +check_field_constraints(Env, Constraints) -> + UnsolvedFieldCs = solve_constraints(Env, Constraints), + case solve_unknown_record_constraints(Env, UnsolvedFieldCs) of + true -> ok; + Errors -> [ type_error(Err) || Err <- Errors ] + end. -solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) -> - not_solved; -solve_constraint(Env, C = #field_constraint{record_t = RecType, - field = FieldName, - field_t = FieldType, - context = When}) -> - RecId = record_type_name(RecType), - Attrs = aeso_syntax:get_ann(RecId), - case lookup_type(Env, RecId) of - {_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t -> - FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields], - {id, _, FieldString} = FieldName, - case proplists:get_value(FieldString, FieldTypes) of - undefined -> - type_error({missing_field, FieldName, RecId}), - not_solved; - FldType -> - create_freshen_tvars(), - FreshFldType = freshen(FldType), - FreshRecType = freshen(app_t(Attrs, RecId, Formals)), - destroy_freshen_tvars(), - unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}), - unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}), - C - end; - _ -> - type_error({not_a_record_type, instantiate(RecType), When}), - not_solved - end; -solve_constraint(Env, C = #dependent_type_constraint{}) -> - check_named_argument_constraint(Env, C); -solve_constraint(Env, C = #named_argument_constraint{}) -> - check_named_argument_constraint(Env, C); -solve_constraint(_Env, {is_bytes, _}) -> ok; -solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> - A = unfold_types_in_type(Env, dereference(A0)), - B = unfold_types_in_type(Env, dereference(B0)), - C = unfold_types_in_type(Env, dereference(C0)), - case {A, B, C} of - {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); - {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); - {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); - _ -> ok - end; -solve_constraint(_, _) -> ok. +check_named_args_constraints(Env, Constraints) -> + UnsolvedNamedArgCs = solve_constraints(Env, Constraints), + [ type_error({unsolved_named_argument_constraint, C}) || C <- UnsolvedNamedArgCs ]. check_bytes_constraints(Env, Constraints) -> InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints, T <- [A, B, C], element(1, T) /= bytes_t ], + InSplitConstraint = [ T || {add_bytes, _, split, A, B, C} <- Constraints, + T <- [A, B, C], + element(1, T) /= bytes_t ], %% Skip is_bytes constraints for types that occur in add_bytes constraints %% (no need to generate error messages for both is_bytes and add_bytes). - Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint); + Skip = fun({is_bytes, _, T}) -> lists:member(T, InAddConstraint); + ({is_fixed_bytes, _, T}) -> lists:member(T, InSplitConstraint); (_) -> false end, [ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ]. -check_bytes_constraint(Env, {is_bytes, Type}) -> +check_bytes_constraint(Env, {is_bytes, Ann, Type}) -> Type1 = unfold_types_in_type(Env, instantiate(Type)), case Type1 of - {bytes_t, _, _} -> ok; + {bytes_t, _, N} when is_integer(N); N == any -> ok; _ -> - type_error({unknown_byte_length, Type}) + type_error({unknown_byte_type, Ann, Type}) + end; +check_bytes_constraint(Env, {is_fixed_bytes, Ann, Type}) -> + Type1 = unfold_types_in_type(Env, instantiate(Type)), + case Type1 of + {bytes_t, _, N} when is_integer(N) -> ok; + _ -> + type_error({unknown_byte_length, Ann, Type}) end; check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) -> A = unfold_types_in_type(Env, instantiate(A0)), @@ -2883,36 +2972,11 @@ check_is_contract_constraints(Env, [C | Cs]) -> end, check_is_contract_constraints(Env, Cs). --spec solve_unknown_record_types(env(), [field_constraint()]) -> true | [tuple()]. -solve_unknown_record_types(Env, Unknown) -> - UVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- Unknown]), - Solutions = [solve_for_uvar(Env, UVar, [{Kind, When, Field} - || #field_constraint{record_t = U, field = Field, kind = Kind, context = When} <- Unknown, - U == UVar]) - || UVar <- UVars], - case lists:member(true, Solutions) of - true -> true; - false -> Solutions - end. - -%% This will solve all kinds of constraints but will only return the -%% unsolved field constraints --spec solve_known_record_types(env(), [constraint()]) -> [field_constraint()]. -solve_known_record_types(Env, Constraints) -> - DerefConstraints = lists:map(fun(C = #field_constraint{record_t = RecordType}) -> - C#field_constraint{record_t = dereference(RecordType)}; - (C) -> dereference_deep(C) - end, Constraints), - SolvedConstraints = lists:map(fun(C) -> solve_constraint(Env, dereference_deep(C)) end, DerefConstraints), - Unsolved = DerefConstraints--SolvedConstraints, - lists:filter(fun(#field_constraint{}) -> true; (_) -> false end, Unsolved). - record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) -> RecId; record_type_name(RecId) when ?is_type_id(RecId) -> RecId; record_type_name(_Other) -> - %% io:format("~p is not a record type\n", [Other]), {id, [{origin, system}], "not_a_record_type"}. solve_for_uvar(Env, UVar = {uvar, Attrs, _}, Fields0) -> @@ -3020,7 +3084,8 @@ unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) -> %% Like the case above, but for types without parameters. when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, 0) end), - UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), + UnfoldSysAlias = not proplists:get_value(not_unfold_system_alias_types, Options, false), + UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false), case lookup_type(Env, Id) of {_, {_, {[], {record_t, Fields}}}} when UnfoldRecords -> @@ -3028,7 +3093,12 @@ unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) -> {_, {_, {[], {variant_t, Constrs}}}} when UnfoldVariants -> {variant_t, unfold_types_in_type(Env, Constrs, Options)}; {_, {_, {[], {alias_t, Type1}}}} -> - unfold_types_in_type(Env, Type1, Options); + case aeso_syntax:get_ann(Type1) of + [{origin, system}] when not UnfoldSysAlias -> + Id; + _ -> + unfold_types_in_type(Env, Type1, Options) + end; _ -> %% Not a record type, or ill-formed record type Id @@ -3085,16 +3155,12 @@ 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) -> + type_error({unify_varargs, When}), + false; +unify1(_Env, {uvar, A, R}, T, _Variance, When) -> case occurs_check(R, T) of true -> - if - Env#env.unify_throws -> - cannot_unify({uvar, A, R}, T, none, When); - true -> - ok - end, + cannot_unify({uvar, A, R}, T, none, When), false; false -> ets_insert(type_vars, {R, T}), @@ -3104,9 +3170,9 @@ unify1(Env, T, {uvar, A, R}, Variance, When) -> unify1(Env, {uvar, A, R}, T, Variance, When); unify1(_Env, {tvar, _, X}, {tvar, _, X}, _Variance, _When) -> true; %% Rigid type variables unify1(Env, [A|B], [C|D], [V|Variances], When) -> - unify0(Env, A, C, V, When) andalso unify0(Env, B, D, Variances, When); + unify0(Env, A, C, V, When) and unify0(Env, B, D, Variances, When); unify1(Env, [A|B], [C|D], Variance, When) -> - unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When); + unify0(Env, A, C, Variance, When) and unify0(Env, B, D, Variance, When); unify1(_Env, X, X, _Variance, _When) -> true; unify1(_Env, _A, {id, _, "void"}, Variance, _When) @@ -3121,18 +3187,13 @@ unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) -> case is_subtype(Env, NameA, NameB, Variance) of true -> true; false -> - if - Env#env.unify_throws -> - IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse - is_subtype(Env, NameA, NameB, covariant), - Cxt = case IsSubtype of - true -> Variance; - false -> none - end, - cannot_unify(A, B, Cxt, When); - true -> - ok - end, + IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse + is_subtype(Env, NameA, NameB, covariant), + Cxt = case IsSubtype of + true -> Variance; + false -> none + end, + cannot_unify(A, B, Cxt, When), false end; unify1(_Env, {qid, _, Name}, {qid, _, Name}, _Variance, _When) -> @@ -3146,13 +3207,15 @@ unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, unify0(Env, Else1, Else2, Variance, When); unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) -> - type_error({unify_varargs, When}); + type_error({unify_varargs, When}), + false; unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) -> - type_error({unify_varargs, When}); + type_error({unify_varargs, When}), + false; unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When) when length(Args1) == length(Args2) -> - unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso - unify0(Env, Args1, Args2, opposite_variance(Variance), When) andalso + unify0(Env, Named1, Named2, opposite_variance(Variance), When) and + unify0(Env, Args1, Args2, opposite_variance(Variance), When) and unify0(Env, Result1, Result2, Variance, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, Variance, When) when length(Args1) == length(Args2), Tag == id orelse Tag == qid -> @@ -3170,7 +3233,7 @@ unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, Variance, When) when length(As) == length(Bs) -> unify0(Env, As, Bs, Variance, When); unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, Variance, When) -> - unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}), + unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}) andalso unify1(Env, Type1, Type2, Variance, When); %% The grammar is a bit inconsistent about whether types without %% arguments are represented as applications to an empty list of @@ -3179,13 +3242,8 @@ unify1(Env, {app_t, _, T, []}, B, Variance, When) -> unify0(Env, T, B, Variance, When); unify1(Env, A, {app_t, _, T, []}, Variance, When) -> unify0(Env, A, T, Variance, When); -unify1(Env, A, B, _Variance, When) -> - if - Env#env.unify_throws -> - cannot_unify(A, B, none, When); - true -> - ok - end, +unify1(_Env, A, B, _Variance, When) -> + cannot_unify(A, B, none, When), false. is_subtype(_Env, NameA, NameB, invariant) -> @@ -3266,49 +3324,59 @@ create_freshen_tvars() -> destroy_freshen_tvars() -> ets_delete(freshen_tvars). -freshen_type(Ann, Type) -> +freshen_type(Ann, Type, Ctx) -> create_freshen_tvars(), - Type1 = freshen(Ann, Type), + Type1 = freshen(Ann, Type, Ctx), destroy_freshen_tvars(), Type1. freshen(Type) -> - freshen(aeso_syntax:get_ann(Type), Type). + freshen(aeso_syntax:get_ann(Type), Type, none). -freshen(Ann, {tvar, _, Name}) -> +freshen(Ann, {tvar, _, Name}, _Ctx) -> NewT = case ets_lookup(freshen_tvars, Name) of [] -> fresh_uvar(Ann); [{Name, T}] -> T end, ets_insert(freshen_tvars, {Name, NewT}), NewT; -freshen(Ann, {bytes_t, _, any}) -> +freshen(Ann, {bytes_t, _, '_'}, Ctx) -> X = fresh_uvar(Ann), - add_constraint({is_bytes, X}), + add_constraint({is_bytes, Ctx, X}), X; -freshen(Ann, T) when is_tuple(T) -> - list_to_tuple(freshen(Ann, tuple_to_list(T))); -freshen(Ann, [A | B]) -> - [freshen(Ann, A) | freshen(Ann, B)]; -freshen(_, X) -> +freshen(Ann, {bytes_t, _, fixed}, Ctx) -> + X = fresh_uvar(Ann), + add_constraint({is_fixed_bytes, Ctx, X}), + X; +freshen(Ann, {fun_t, FAnn, NamedArgs, Args, Result}, Ctx) when is_list(Args) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), + [ freshen(Ann, Arg, [{arg, Ix} | Ctx]) || {Arg, Ix} <- lists:zip(Args, lists:seq(1, length(Args))) ], + freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, {fun_t, FAnn, NamedArgs, Arg, Result}, Ctx) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), freshen(Ann, Arg, Ctx), freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, T, Ctx) when is_tuple(T) -> + list_to_tuple(freshen(Ann, tuple_to_list(T), Ctx)); +freshen(Ann, [A | B], Ctx) -> + [freshen(Ann, A, Ctx) | freshen(Ann, B, Ctx)]; +freshen(_, X, _Ctx) -> X. -freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) -> - FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), +freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}, Ctx) -> + FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig), Ctx), apply_typesig_constraint(Ann, Constr, FunT), FunT. apply_typesig_constraint(_Ann, none, _FunT) -> ok; apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) -> add_constraint([#is_contract_constraint{ contract_t = Type, - context = {address_to_contract, Ann}}]); + context = {address_to_contract, Ann}}]); apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> add_constraint({add_bytes, Ann, split, A, B, C}); apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) -> add_constraint([#is_contract_constraint{ contract_t = Con, - context = {bytecode_hash, Ann} }]). + context = {bytecode_hash, Ann} }]). %% Dereferences all uvars and replaces the uninstantiated ones with a @@ -3341,7 +3409,7 @@ instantiate1(X) -> integer_to_tvar(X) when X < 26 -> [$a + X]; integer_to_tvar(X) -> - [integer_to_tvar(X div 26)] ++ [$a + (X rem 26)]. + integer_to_tvar(X div 26 - 1) ++ [$a + (X rem 26)]. %% Warnings @@ -3523,7 +3591,6 @@ create_type_errors() -> destroy_and_report_type_errors(Env) -> Errors0 = lists:reverse(ets_tab2list(type_errors)), - %% io:format("Type errors now: ~p\n", [Errors0]), ets_delete(type_errors), Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ], aeso_errors:throw(Errors). %% No-op if Errors == [] @@ -3729,6 +3796,9 @@ mk_error({type_decl, _, {id, Pos, Name}, _}) -> Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition", [Name]), mk_t_err(pos(Pos), Msg); +mk_error({too_many_tvars, Name, {type_sig, Pos, _, _, _, _}}) -> + Msg = io_lib:format("Too many type variables (max 256) in definition of `~s`", [Name]), + mk_t_err(pos(Pos), Msg); mk_error({stateful_not_allowed, Id, Fun}) -> Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.", [pp(Id), pp(Fun)]), @@ -3829,8 +3899,11 @@ mk_error({bad_top_level_decl, Decl}) -> Msg = io_lib:format("The definition of '~s' must appear inside a ~s.", [pp_expr(Id), What]), mk_t_err(pos(Decl), Msg); -mk_error({unknown_byte_length, Type}) -> - Msg = io_lib:format("Cannot resolve length of byte array.", []), +mk_error({unknown_byte_type, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve type of byte array in\n ~s", [pp_context(Ctx)]), + mk_t_err(pos(Type), Msg); +mk_error({unknown_byte_length, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve length of byte array in\n ~s", [pp_context(Ctx)]), mk_t_err(pos(Type), Msg); mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) -> Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n" @@ -3844,6 +3917,12 @@ mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) -> [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), pp_type(" - ", B), pp_loc(B)]), mk_t_err(pos(Ann), Msg); +mk_error({unsolved_bytes_constraint, Ann, split_any, A, B, C}) -> + Msg = io_lib:format("Failed to resolve byte arrays in call to Bytes.split_any with argument of type\n" + "~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)", + [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), + pp_type(" - ", B), pp_loc(B)]), + mk_t_err(pos(Ann), Msg); mk_error({failed_to_get_compiler_version, Err}) -> Msg = io_lib:format("Failed to get compiler version. Error: ~p", [Err]), mk_t_err(pos(0, 0), Msg); @@ -3941,7 +4020,7 @@ mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) -> [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" + Msg = io_lib:format("Invalid return type of `AENSv2.resolve`:\n" "~s`\n" "It must be a `string` or a pubkey type (`address`, `oracle`, etc)", [pp_type(" `", T)]), @@ -4118,8 +4197,8 @@ pp_when({if_branches, Then, ThenType0, Else, ElseType0}) -> Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ], {pos(element(1, hd(Branches))), io_lib:format("when comparing the types of the if-branches\n" - "~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)]) - || {B, BType} <- Branches ] ])}; + "~s", [string:join([ io_lib:format("~s (at ~s)", [pp_typed(" - ", B, BType), pp_loc(B)]) + || {B, BType} <- Branches ], "\n")])}; pp_when({case_pat, Pat, PatType0, ExprType0}) -> {PatType, ExprType} = instantiate({PatType0, ExprType0}), {pos(Pat), @@ -4166,6 +4245,10 @@ pp_when({var_args, Ann, Fun}) -> {pos(Ann) , io_lib:format("when resolving arguments of variadic function `~s`", [pp_expr(Fun)]) }; +pp_when({implement_interface_fun, Ann, Entrypoint, Interface}) -> + { pos(Ann) + , io_lib:format("when implementing the entrypoint `~s` from the interface `~s`", [Entrypoint, Interface]) + }; pp_when(unknown) -> {pos(0,0), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. @@ -4210,6 +4293,18 @@ pp_type(Type) -> pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated])), 80, 80). + +pp_context([{fun_name, Id}]) -> ["a call to ", pp(Id)]; +pp_context([result | Ctx]) -> ["the result of ", pp_context(Ctx)]; +pp_context([{arg, N} | Ctx]) -> + Cnt = fun(1) -> "first"; + (2) -> "second"; + (3) -> "third"; + (I) -> io_lib:format("~pth", [I]) + end, + ["the ", Cnt(N), " argument of ", pp_context(Ctx)]; +pp_context(none) -> "unknown context". + src_file(T) -> aeso_syntax:get_ann(file, T, no_file). include_type(T) -> aeso_syntax:get_ann(include_type, T, none). line_number(T) -> aeso_syntax:get_ann(line, T, 0). @@ -4261,7 +4356,7 @@ pp({tuple_t, _, []}) -> "unit"; pp({tuple_t, _, Cpts}) -> ["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"]; -pp({bytes_t, _, any}) -> "bytes(_)"; +pp({bytes_t, _, any}) -> "bytes()"; pp({bytes_t, _, Len}) -> ["bytes(", integer_to_list(Len), ")"]; pp({app_t, _, T, []}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 288ab21..c341054 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -33,12 +33,13 @@ -type op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '!' | + 'band' | 'bor' | 'bxor' | 'bnot' | '<<' | '>>' | map_get | map_get_d | map_set | map_from_list | map_to_list | map_delete | map_member | map_size | string_length | string_concat | bits_set | bits_clear | bits_test | bits_sum | bits_intersection | bits_union | bits_difference | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 | - crypto_sha3 | crypto_sha256 | crypto_blake2b | + crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_poseidon | crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 | mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid | mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul | @@ -55,38 +56,44 @@ | {contract_pubkey, binary()} | {oracle_pubkey, binary()} | {oracle_query_id, binary()} + | {signature, binary()} | {bool, false | true} | {contract_code, string()} %% for CREATE, by name | {typerep, ftype()}. --type fexpr() :: {lit, flit()} - | nil - | {var, var_name()} - | {def, fun_name(), [fexpr()]} - | {remote, [ftype()], ftype(), fexpr(), fun_name(), [fexpr()]} - | {builtin, builtin(), [fexpr()]} - | {con, arities(), tag(), [fexpr()]} - | {tuple, [fexpr()]} - | {proj, fexpr(), integer()} - | {set_proj, fexpr(), integer(), fexpr()} %% tuple, field, new_value - | {op, op(), [fexpr()]} - | {'let', var_name(), fexpr(), fexpr()} - | {funcall, fexpr(), [fexpr()]} %% Call to unknown function - | {closure, fun_name(), fexpr()} - | {switch, fsplit()} - | {set_state, state_reg(), fexpr()} - | {get_state, state_reg()} +-type fann() :: [ {file, aeso_syntax:ann_file()} | + {line, aeso_syntax:ann_line()} | + {col, aeso_syntax:ann_col()} + ]. + +-type fexpr() :: {lit, fann(), flit()} + | {nil, fann()} + | {var, fann(), var_name()} + | {def, fann(), fun_name(), [fexpr()]} + | {remote, fann(), [ftype()], ftype(), fexpr(), fun_name(), [fexpr()]} + | {builtin, fann(), builtin(), [fexpr()]} + | {con, fann(), arities(), tag(), [fexpr()]} + | {tuple, fann(), [fexpr()]} + | {proj, fann(), fexpr(), integer()} + | {set_proj, fann(), fexpr(), integer(), fexpr()} %% tuple, field, new_value + | {op, fann(), op(), [fexpr()]} + | {'let', fann(), var_name(), fexpr(), fexpr()} + | {funcall, fann(), fexpr(), [fexpr()]} %% Call to unknown function + | {closure, fann(), fun_name(), fexpr()} + | {switch, fann(), fsplit()} + | {set_state, fann(), state_reg(), fexpr()} + | {get_state, fann(), state_reg()} %% The following (unapplied top-level functions/builtins and %% lambdas) are generated by the fcode compiler, but translated %% to closures by the lambda lifter. - | {def_u, fun_name(), arity()} - | {remote_u, [ftype()], ftype(), fexpr(), fun_name()} - | {builtin_u, builtin(), arity()} - | {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args. - | {lam, [var_name()], fexpr()}. + | {def_u, fann(), fun_name(), arity()} + | {remote_u, fann(), [ftype()], ftype(), fexpr(), fun_name()} + | {builtin_u, fann(), builtin(), arity()} + | {builtin_u, fann(), builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args. + | {lam, fann(), [var_name()], fexpr()}. -type fsplit() :: {split, ftype(), var_name(), [fcase()]} - | {nosplit, fexpr()}. + | {nosplit, [rename()], fexpr()}. %% Renames are needed to add DBG_DEF for switch pattern vars -type fcase() :: {'case', fsplit_pat(), fsplit()}. @@ -116,18 +123,21 @@ | bits | {variant, [[ftype()]]} | {function, [ftype()], ftype()} - | any | {tvar, var_name()}. + | any + | {tvar, var_name()}. --type fun_def() :: #{ attrs := [attribute()], +-type fun_def() :: #{ attrs := [attribute() | fann()], args := [{var_name(), ftype()}], return := ftype(), body := fexpr() }. +-type functions() :: #{ fun_name() => fun_def() }. + -type fcode() :: #{ contract_name := string(), state_type := ftype(), state_layout := state_layout(), event_type := ftype() | none, - functions := #{ fun_name() => fun_def() }, + functions := functions(), payable := boolean() }. -type type_def() :: fun(([ftype()]) -> ftype()). @@ -138,11 +148,14 @@ -record(con_tag, { tag :: tag(), arities :: arities() }). -type con_tag() :: #con_tag{}. --type type_env() :: #{ sophia_name() => type_def() }. --type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. --type con_env() :: #{ sophia_name() => con_tag() }. --type child_con_env() :: #{sophia_name() => fcode()}. --type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. +-type expr_env() :: #{ var_name() => fexpr() }. +-type type_env() :: #{ sophia_name() => type_def() }. +-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. +-type con_env() :: #{ sophia_name() => con_tag() }. +-type child_con_env() :: #{ sophia_name() => fcode() }. +-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. + +-type rename() :: [{var_name(), var_name()}]. -type context() :: {contract_def, string()} | {namespace, string()} @@ -189,6 +202,7 @@ ast_to_fcode(Code, Options) -> clear_fresh_names(Options), {Env3, FCode2}. +-spec optimize(fcode(), [option()]) -> fcode(). optimize(FCode1, Options) -> Verbose = lists:member(pp_fcode, Options), [io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose], @@ -216,6 +230,12 @@ init_env(Options) -> ["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] }, ["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] }, ["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] }, + ["AENSv2", "AccountPt"] => #con_tag{ tag = 0, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "DataPt"] => #con_tag{ tag = 4, arities = [1, 1, 1, 1, 1] }, + ["AENSv2", "Name"] => #con_tag{ tag = 0, arities = [3] }, ["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] }, ["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] }, ["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities }, @@ -248,45 +268,51 @@ init_env(Options) -> -spec builtins() -> builtins(). builtins() -> - MkName = fun(NS, Fun) -> - list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_"))) + MkName = fun + (["AENSv2"], Fun) -> + list_to_atom(string:to_lower("AENS_" ++ Fun)); + (NS, Fun) -> + list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_"))) end, 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}]}, + {"timestamp", none}, {"block_height", none}, {"difficulty", none}, {"gas_limit", none}, + {"network_id", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]}, {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, {["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"fee", none}, {"gas_left", 0}]}, {["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2}, {"respond", 4}, {"extend", 3}, {"get_answer", 2}, {"check", 1}, {"check_query", 2}]}, - {["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4}, + {["AENSv2"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4}, {"revoke", 3}, {"update", 6}, {"lookup", 1}]}, {["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2}, {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2}, - {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, + {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"poseidon", 2}]}, {["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2}, {"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2}, {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, - {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, + {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, {"to_bytes", 1}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]}, {["Char"], [{"to_int", 1}, {"from_int", 1}]}, {["Auth"], [{"tx_hash", none}, {"tx", none}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, - {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, - {["Int"], [{"to_str", 1}]}, - {["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} + {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}, {"to_fixed_size", 1}, + {"to_any_size", 1}, {"size", 1}, {"split_any", 2}]}, + {["Int"], [{"to_str", 1}, {"to_bytes", 2}, {"mulmod", 2}]}, + {["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1}, + {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} || {NS, Funs} <- Scopes, {Fun, Arity} <- Funs ]). +-spec state_layout(env()) -> state_layout(). state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}). -define(type(T), fun([]) -> T end). @@ -316,19 +342,24 @@ init_type_env() -> ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}), ["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}), ["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}), + ["AENSv2", "pointee"] => ?type({variant, [[address], [address], [address], [address], [{bytes, any}]]}), + ["AENSv2", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address], [{bytes, any}]]}}]]}), ["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}), ["Chain", "base_tx"] => ?type(BaseTx), - ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), - ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) + ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), + ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) }. +-spec is_no_code(env()) -> boolean(). is_no_code(Env) -> get_option(no_code, Env). +-spec get_option(atom(), env()) -> option(). get_option(Opt, Env) -> get_option(Opt, Env, false). +-spec get_option(atom(), env(), option()) -> option(). get_option(Opt, Env, Default) -> proplists:get_value(Opt, maps:get(options, Env, []), Default). @@ -375,6 +406,27 @@ to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) -> Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls), to_fcode(Env1, Code). +-spec ann_loc(aeso_syntax:ann() | fann()) -> {File, Line, Column} when + File :: string() | none, + Line :: non_neg_integer() | none, + Column :: non_neg_integer() | none. +ann_loc(Ann) -> + File = proplists:get_value(file, Ann, none), + Line = proplists:get_value(line, Ann, none), + Col = proplists:get_value(col, Ann, none), + {File, Line, Col}. + +-spec to_fann(aeso_syntax:ann()) -> fann(). +to_fann(Ann) -> + {File, Line, Col} = ann_loc(Ann), + [ {Tag, X} || + {Tag, X} <- [{file, File}, {line, Line}, {col, Col}], + X =/= none, X =/= no_file + ]. + +-spec get_fann(fexpr()) -> fann(). +get_fann(FExpr) -> element(2, FExpr). + -spec decls_to_fcode(env(), [aeso_syntax:decl()]) -> env(). decls_to_fcode(Env, Decls) -> %% First compute mapping from Sophia names to fun_names and add it to the @@ -387,8 +439,11 @@ decls_to_fcode(Env, Decls) -> 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, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) -> - Attrs = get_attributes(Ann), +decl_to_fcode(Env = #{ functions := Funs, options := Options }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) -> + Attrs = case proplists:get_value(debug_info, Options, false) of + true -> get_attributes_debug(Ann); + false -> get_attributes(Ann) + end, FName = lookup_fun(Env, qname(Env, Name)), FArgs = args_to_fcode(Env, Args), FRet = type_to_fcode(Env, Ret), @@ -443,6 +498,7 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> Env3 = compute_state_layout(Env2, Name, FDef), bind_type(Env3, Q, FDef). +-spec compute_state_layout(env(), string(), type_def()) -> env(). compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) -> NoLayout = get_option(no_flatten_state, Env), Layout = @@ -455,6 +511,7 @@ compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) -> Env#{ state_layout => Layout }; compute_state_layout(Env, _, _) -> Env. +-spec compute_state_layout(state_reg(), ftype() | [ftype()]) -> {state_reg(), state_layout() | [state_layout()]}. compute_state_layout(R, {tuple, [T]}) -> compute_state_layout(R, T); compute_state_layout(R, {tuple, Ts}) -> @@ -509,19 +566,23 @@ args_to_fcode(Env, Args) -> -define(make_let(X, Expr, Body), make_let(Expr, fun(X) -> Body end)). +-spec make_let(fexpr(), fun((fexpr()) -> fexpr())) -> fexpr(). make_let(Expr, Body) -> case Expr of - {var, _} -> Body(Expr); - {lit, {int, _}} -> Body(Expr); - {lit, {bool, _}} -> Body(Expr); + {var, _, _} -> Body(Expr); + {lit, _, {int, _}} -> Body(Expr); + {lit, _, {bool, _}} -> Body(Expr); _ -> X = fresh_name(), - {'let', X, Expr, Body({var, X})} + FAnn = get_fann(Expr), + {'let', FAnn, X, Expr, Body({var, FAnn, X})} end. -let_bind(X, {var, Y}, Body) -> rename([{X, Y}], Body); -let_bind(X, Expr, Body) -> {'let', X, Expr, Body}. +-spec let_bind(var_name(), fexpr(), fexpr()) -> fexpr(). +let_bind(X, {var, _, Y}, Body) -> rename([{X, Y}], Body); +let_bind(X, Expr, Body) -> {'let', get_fann(Expr), X, Expr, Body}. +-spec let_bind([{var_name(), fexpr()}], fexpr()) -> fexpr(). let_bind(Binds, Body) -> lists:foldr(fun({X, E}, Rest) -> let_bind(X, E, Rest) end, Body, Binds). @@ -535,50 +596,54 @@ expr_to_fcode(Env, Expr) -> -spec expr_to_fcode(env(), aeso_syntax:type() | no_type, aeso_syntax:expr()) -> fexpr(). %% Literals -expr_to_fcode(_Env, _Type, {int, _, N}) -> {lit, {int, N}}; -expr_to_fcode(_Env, _Type, {char, _, N}) -> {lit, {int, N}}; -expr_to_fcode(_Env, _Type, {bool, _, B}) -> {lit, {bool, B}}; -expr_to_fcode(_Env, _Type, {string, _, S}) -> {lit, {string, S}}; -expr_to_fcode(_Env, _Type, {account_pubkey, _, K}) -> {lit, {account_pubkey, K}}; -expr_to_fcode(_Env, _Type, {contract_pubkey, _, K}) -> {lit, {contract_pubkey, K}}; -expr_to_fcode(_Env, _Type, {oracle_pubkey, _, K}) -> {lit, {oracle_pubkey, K}}; -expr_to_fcode(_Env, _Type, {oracle_query_id, _, K}) -> {lit, {oracle_query_id, K}}; -expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}}; +expr_to_fcode(_Env, _Type, {int, Ann, N}) -> {lit, to_fann(Ann), {int, N}}; +expr_to_fcode(_Env, _Type, {char, Ann, N}) -> {lit, to_fann(Ann), {int, N}}; +expr_to_fcode(_Env, _Type, {bool, Ann, B}) -> {lit, to_fann(Ann), {bool, B}}; +expr_to_fcode(_Env, _Type, {string, Ann, S}) -> {lit, to_fann(Ann), {string, S}}; +expr_to_fcode(_Env, _Type, {account_pubkey, Ann, K}) -> {lit, to_fann(Ann), {account_pubkey, K}}; +expr_to_fcode(_Env, _Type, {signature, Ann, K}) -> {lit, to_fann(Ann), {signature, K}}; +expr_to_fcode(_Env, _Type, {contract_pubkey, Ann, K}) -> {lit, to_fann(Ann), {contract_pubkey, K}}; +expr_to_fcode(_Env, _Type, {oracle_pubkey, Ann, K}) -> {lit, to_fann(Ann), {oracle_pubkey, K}}; +expr_to_fcode(_Env, _Type, {oracle_query_id, Ann, K}) -> {lit, to_fann(Ann), {oracle_query_id, K}}; +expr_to_fcode(_Env, _Type, {bytes, Ann, B}) -> {lit, to_fann(Ann), {bytes, B}}; %% Variables -expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [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; - B =:= oracle_get_answer; - B =:= oracle_respond; - B =:= oracle_register; - B =:= oracle_check; - B =:= oracle_check_query -> +expr_to_fcode(Env, _Type, {id, Ann, X}) -> resolve_var(Env, Ann, [X]); +expr_to_fcode(Env, Type, {qid, Ann, X}) -> + case resolve_var(Env, Ann, X) of + {builtin_u, FAnn, B, Ar} when B =:= oracle_query; + B =:= oracle_get_question; + B =:= oracle_get_answer; + B =:= oracle_respond; + B =:= oracle_register; + B =:= oracle_check; + B =:= oracle_check_query -> OType = get_oracle_type(B, Type), {oracle, QType, RType} = type_to_fcode(Env, OType), - TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], - {builtin_u, B, Ar, TypeArgs}; - {builtin_u, B = aens_resolve, Ar} -> + TypeArgs = [{lit, FAnn, {typerep, QType}}, {lit, FAnn, {typerep, RType}}], + {builtin_u, FAnn, B, Ar, TypeArgs}; + {builtin_u, FAnn, B = aens_resolve, Ar} -> {fun_t, _, _, _, ResType} = Type, AensType = type_to_fcode(Env, ResType), - TypeArgs = [{lit, {typerep, AensType}}], - {builtin_u, B, Ar, TypeArgs}; - {builtin_u, B = bytes_split, Ar} -> + TypeArgs = [{lit, FAnn, {typerep, AensType}}], + {builtin_u, FAnn, B, Ar, TypeArgs}; + {builtin_u, FAnn, B = bytes_split, Ar} -> {fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type, - {builtin_u, B, Ar, [{lit, {int, N}}]}; + {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; + {builtin_u, FAnn, B = bytes_to_fixed_size, Ar} -> + {fun_t, _, _, _, {app_t, _, {id, _, "option"}, [{bytes_t, _, N}]}} = Type, + {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; Other -> Other end; %% Constructors expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> expr_to_fcode(Env, Type, {app, [], {typed, [], Con, Type}, []}); -expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C == con; C == qcon -> +expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, Ann, _} = Con, _}, Args}) when C == con; C == qcon -> #con_tag{ tag = I, arities = Arities } = lookup_con(Env, Con), Arity = lists:nth(I + 1, Arities), case length(Args) == Arity of - true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; + true -> {con, to_fann(Ann), Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity}) end; @@ -587,18 +652,18 @@ expr_to_fcode(Env, _Type, {tuple, _, Es}) -> make_tuple([expr_to_fcode(Env, E) || E <- Es]); %% Records -expr_to_fcode(Env, Type, {proj, _Ann, Rec = {typed, _, _, RecType}, {id, _, X}}) -> +expr_to_fcode(Env, Type, {proj, Ann, Rec = {typed, _, _, RecType}, {id, _, X}}) -> case RecType of {con, _, _} when X == "address" -> - {op, contract_to_address, [expr_to_fcode(Env, Rec)]}; + {op, to_fann(Ann), contract_to_address, [expr_to_fcode(Env, Rec)]}; {con, _, _} -> {fun_t, _, _, Args, Ret} = Type, FArgs = [type_to_fcode(Env, Arg) || Arg <- Args], - {remote_u, FArgs, type_to_fcode(Env, Ret), expr_to_fcode(Env, Rec), + {remote_u, to_fann(Ann), FArgs, type_to_fcode(Env, Ret), expr_to_fcode(Env, Rec), {entrypoint, list_to_binary(X)}}; {record_t, [_]} -> expr_to_fcode(Env, Rec); %% Singleton record {record_t, _} -> - {proj, expr_to_fcode(Env, Rec), field_index(Rec, X)} + {proj, to_fann(Ann), expr_to_fcode(Env, Rec), field_index(RecType, X)} end; expr_to_fcode(Env, {record_t, [FieldT]}, {record, _Ann, [_] = Fields}) -> @@ -612,55 +677,56 @@ expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Fields}) -> end, make_tuple(lists:map(FVal, FieldTypes)); -expr_to_fcode(Env, {record_t, [FieldT]}, {record, _Ann, Rec, Fields}) -> +expr_to_fcode(Env, {record_t, [FieldT]}, {record, Ann, Rec, Fields}) -> case field_value(FieldT, Fields) of false -> expr_to_fcode(Env, Rec); {set, E} -> expr_to_fcode(Env, E); - {upd, Z, E} -> {'let', Z, expr_to_fcode(Env, Rec), expr_to_fcode(bind_var(Env, Z), E)} + {upd, Z, E} -> {'let', to_fann(Ann), Z, expr_to_fcode(Env, Rec), expr_to_fcode(bind_var(Env, Z), E)} end; -expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Rec, Fields}) -> +expr_to_fcode(Env, {record_t, FieldTypes}, {record, Ann, Rec, Fields}) -> X = fresh_name(), - Proj = fun(I) -> {proj, {var, X}, I - 1} end, + FAnn = to_fann(Ann), + Proj = fun(I) -> {proj, FAnn, {var, FAnn, X}, I - 1} end, Comp = fun({I, false}) -> Proj(I); ({_, {set, E}}) -> expr_to_fcode(Env, E); - ({I, {upd, Z, E}}) -> {'let', Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)} + ({I, {upd, Z, E}}) -> {'let', FAnn, Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)} end, Set = fun({_, false}, R) -> R; - ({I, {set, E}}, R) -> {set_proj, R, I - 1, expr_to_fcode(Env, E)}; - ({I, {upd, Z, E}}, R) -> {set_proj, R, I - 1, - {'let', Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)}} + ({I, {set, E}}, R) -> {set_proj, FAnn, R, I - 1, expr_to_fcode(Env, E)}; + ({I, {upd, Z, E}}, R) -> {set_proj, FAnn, R, I - 1, + {'let', FAnn, Z, Proj(I), expr_to_fcode(bind_var(Env, Z), E)}} end, Expand = length(Fields) == length(FieldTypes), Updates = [ {I, field_value(FT, Fields)} || {I, FT} <- indexed(FieldTypes) ], Body = case Expand of - true -> {tuple, lists:map(Comp, Updates)}; - false -> lists:foldr(Set, {var, X}, Updates) + true -> {tuple, FAnn, lists:map(Comp, Updates)}; + false -> lists:foldr(Set, {var, FAnn, X}, Updates) end, - {'let', X, expr_to_fcode(Env, Rec), Body}; + {'let', FAnn, X, expr_to_fcode(Env, Rec), Body}; %% Lists -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, {list, Ann, Es}) -> + lists:foldr(fun(E, L) -> {op, to_fann(aeso_syntax:get_ann(E)), '::', [expr_to_fcode(Env, E), L]} end, + {nil, to_fann(Ann)}, Es); -expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) -> - {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, {app, As, {'..', _}, [A, B]}) -> + {def_u, FAnn, FromTo, _} = resolve_fun(Env, As, ["ListInternal", "from_to"]), + {def, FAnn, 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, []}) -> + {op, to_fann(As), '::', [expr_to_fcode(Env, Yield), {nil, to_fann(As)}]}; expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) -> Arg = fresh_name(), Env1 = bind_var(Env, Arg), - Bind = {lam, [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType}, - [{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]}, - {'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})}, - {def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]), - {def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; + Bind = {lam, to_fann(As), [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType}, + [{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]}, + {'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})}, + {def_u, FAnn, FlatMap, _} = resolve_fun(Env, As, ["ListInternal", "flat_map"]), + {def, FAnn, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) -> make_if(expr_to_fcode(Env, Cond), expr_to_fcode(Env, Type, {list_comp, As, Yield, Rest}), - nil + {nil, to_fann(As)} ); expr_to_fcode(Env, Type, {list_comp, As, Yield, [LV = {letval, _, _, _}|Rest]}) -> expr_to_fcode(Env, Type, {block, As, [LV, {list_comp, As, Yield, Rest}]}); @@ -674,15 +740,15 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) -> expr_to_fcode(Env, Else)); %% Switch -expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) -> +expr_to_fcode(Env, _, S = {switch, Ann, Expr = {typed, _, E, Type}, Alts}) -> Switch = fun(X) -> - {switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)} + {switch, to_fann(Ann), alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)} end, case E of {id, _, X} -> Switch(X); _ -> X = fresh_name(), - {'let', X, expr_to_fcode(Env, Expr), + {'let', to_fann(Ann), X, expr_to_fcode(Env, Expr), Switch(X)} end; @@ -697,54 +763,57 @@ expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) -> case Op of '|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]}); - _ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]} + _ -> {op, to_fann(Ann), Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]} end; -expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> +expr_to_fcode(Env, _Type, {app, Ann, {Op, _}, [A]}) when is_atom(Op) -> + FAnn = to_fann(Ann), case Op of - '-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]}; - '!' -> {op, '!', [expr_to_fcode(Env, A)]} + '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; + 'bnot' -> {op, FAnn, 'bnot', [expr_to_fcode(Env, A)]}; + '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} end; %% Function calls -expr_to_fcode(Env, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) -> +expr_to_fcode(Env, _, {app, _, Fun = {typed, Ann, 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 - {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs); - {builtin_u, chain_clone, _Ar} -> + {builtin_u, FAnn, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), FAnn, B, FArgs ++ TypeArgs); + {builtin_u, FAnn, chain_clone, _Ar} -> case ArgsT of var_args -> fcode_error({var_args_not_set, FunE}); _ -> %% Here we little cheat on the typechecker, but this inconsistency %% is to be solved in `aeso_fcode_to_fate:type_to_scode/1` FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), - builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) + builtin_to_fcode(state_layout(Env), FAnn, chain_clone, [{lit, FAnn, FInitArgsT}|FArgs]) end; - {builtin_u, chain_create, _Ar} -> + {builtin_u, FAnn, chain_create, _Ar} -> case {ArgsT, Type} of {var_args, _} -> fcode_error({var_args_not_set, FunE}); {_, {con, _, Contract}} -> FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), - builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); + builtin_to_fcode(state_layout(Env), FAnn, chain_create, [{lit, FAnn, {contract_code, Contract}}, {lit, FAnn, FInitArgsT}|FArgs]); {_, _} -> fcode_error({not_a_contract_type, Type}) end; - {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); - {def_u, F, _Ar} -> {def, F, FArgs}; - {remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs}; + {builtin_u, FAnn, B, _Ar} -> builtin_to_fcode(state_layout(Env), FAnn, B, FArgs); + {def_u, FAnn, F, _Ar} -> {def, FAnn, F, FArgs}; + {remote_u, FAnn, RArgsT, RRetT, Ct, RFun} -> {remote, FAnn, RArgsT, RRetT, Ct, RFun, FArgs}; FFun -> %% FFun is a closure, with first component the function name and %% second component the environment - Call = fun(X) -> {funcall, {proj, {var, X}, 0}, [{proj, {var, X}, 1} | FArgs]} end, + FAnn = to_fann(Ann), + Call = fun(X) -> {funcall, FAnn, {proj, FAnn, {var, FAnn, X}, 0}, [{proj, FAnn, {var, FAnn, X}, 1} | FArgs]} end, case FFun of - {var, X} -> Call(X); - _ -> X = fresh_name(), - {'let', X, FFun, Call(X)} + {var, _, X} -> Call(X); + _ -> X = fresh_name(), + {'let', FAnn, X, FFun, Call(X)} end end; %% Maps -expr_to_fcode(_Env, _Type, {map, _, []}) -> - {builtin, map_empty, []}; +expr_to_fcode(_Env, _Type, {map, Ann, []}) -> + {builtin, to_fann(Ann), map_empty, []}; expr_to_fcode(Env, Type, {map, Ann, KVs}) -> %% Cheaper to do incremental map_update than building the list and doing %% map_from_list (I think). @@ -754,59 +823,72 @@ expr_to_fcode(Env, _Type, {map, _, Map, KVs}) -> ?make_let(Map1, expr_to_fcode(Env, Map), lists:foldr(fun(Fld, M) -> case Fld of - {field, _, [{map_get, _, K}], V} -> - {op, map_set, [M, expr_to_fcode(Env, K), expr_to_fcode(Env, V)]}; - {field_upd, _, [MapGet], {typed, _, {lam, _, [{arg, _, {id, _, Z}, _}], V}, _}} when element(1, MapGet) == map_get -> + {field, Ann, [{map_get, _, K}], V} -> + {op, to_fann(Ann), map_set, [M, expr_to_fcode(Env, K), expr_to_fcode(Env, V)]}; + {field_upd, Ann, [MapGet], {typed, _, {lam, _, [{arg, _, {id, _, Z}, _}], V}, _}} when element(1, MapGet) == map_get -> [map_get, _, K | Default] = tuple_to_list(MapGet), ?make_let(Key, expr_to_fcode(Env, K), begin %% Z might shadow Map1 or Key Z1 = fresh_name(), + FAnn = to_fann(Ann), GetExpr = case Default of - [] -> {op, map_get, [Map1, Key]}; - [D] -> {op, map_get_d, [Map1, Key, expr_to_fcode(Env, D)]} + [] -> {op, FAnn, map_get, [Map1, Key]}; + [D] -> {op, FAnn, map_get_d, [Map1, Key, expr_to_fcode(Env, D)]} end, - {'let', Z1, GetExpr, - {op, map_set, [M, Key, rename([{Z, Z1}], expr_to_fcode(bind_var(Env, Z), V))]}} + {'let', FAnn, Z1, GetExpr, + {op, FAnn, map_set, [M, Key, rename([{Z, Z1}], expr_to_fcode(bind_var(Env, Z), V))]}} end) end end, Map1, KVs)); -expr_to_fcode(Env, _Type, {map_get, _, Map, Key}) -> - {op, map_get, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key)]}; -expr_to_fcode(Env, _Type, {map_get, _, Map, Key, Def}) -> - {op, map_get_d, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key), expr_to_fcode(Env, Def)]}; +expr_to_fcode(Env, _Type, {map_get, Ann, Map, Key}) -> + {op, to_fann(Ann), map_get, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key)]}; +expr_to_fcode(Env, _Type, {map_get, Ann, Map, Key, Def}) -> + {op, to_fann(Ann), map_get_d, [expr_to_fcode(Env, Map), expr_to_fcode(Env, Key), expr_to_fcode(Env, Def)]}; -expr_to_fcode(Env, _Type, {lam, _, Args, Body}) -> +expr_to_fcode(Env, _Type, {lam, Ann, Args, Body}) -> GetArg = fun({arg, _, {id, _, X}, _}) -> X end, Xs = lists:map(GetArg, Args), - {lam, Xs, expr_to_fcode(bind_vars(Env, Xs), Body)}; + {lam, to_fann(Ann), Xs, expr_to_fcode(bind_vars(Env, Xs), Body)}; expr_to_fcode(_Env, Type, Expr) -> error({todo, {Expr, ':', Type}}). -make_if({var, X}, Then, Else) -> - {switch, {split, boolean, X, - [{'case', {bool, false}, {nosplit, Else}}, - {'case', {bool, true}, {nosplit, Then}}]}}; +-spec make_if(fexpr(), fexpr(), fexpr()) -> fexpr(). +make_if({var, FAnn, X}, Then, Else) -> + {switch, FAnn, {split, boolean, X, + [{'case', {bool, false}, {nosplit, [], Else}}, + {'case', {bool, true}, {nosplit, [], Then}}]}}; make_if(Cond, Then, Else) -> X = fresh_name(), - {'let', X, Cond, make_if({var, X}, Then, Else)}. + FAnn = get_fann(Cond), + {'let', FAnn, X, Cond, make_if({var, FAnn, X}, Then, Else)}. -make_if_no_else({var, X}, Then) -> - {switch, {split, boolean, X, - [{'case', {bool, true}, {nosplit, Then}}]}}; +-spec make_if_no_else(fexpr(), fexpr()) -> fexpr(). +make_if_no_else({var, FAnn, X}, Then) -> + {switch, FAnn, {split, boolean, X, + [{'case', {bool, true}, {nosplit, [], Then}}]}}; make_if_no_else(Cond, Then) -> X = fresh_name(), - {'let', X, Cond, make_if_no_else({var, X}, Then)}. + FAnn = get_fann(Cond), + {'let', FAnn, X, Cond, make_if_no_else({var, FAnn, X}, Then)}. -spec make_tuple([fexpr()]) -> fexpr(). make_tuple([E]) -> E; -make_tuple(Es) -> {tuple, Es}. +make_tuple(Es) -> {tuple, [], Es}. + +-spec make_tuple_fpat([fpat()]) -> fpat(). +make_tuple_fpat([P]) -> P; +make_tuple_fpat(Ps) -> {tuple, Ps}. -spec strip_singleton_tuples(ftype()) -> ftype(). -strip_singleton_tuples({tuple, [T]}) -> strip_singleton_tuples(T); +strip_singleton_tuples({tuple, _, [T]}) -> strip_singleton_tuples(T); strip_singleton_tuples(T) -> T. +-spec get_oracle_type(OracleFun, FunT) -> OracleType when + OracleFun :: atom(), + FunT :: aeso_syntax:type(), + OracleType :: aeso_syntax:type(). get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType; get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType; get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType; @@ -828,11 +910,13 @@ alts_to_fcode(Env, Type, X, Alts, Switch) -> | {bool, false | true} | {int, integer()} | {string, binary()} - | nil | {'::', fpat(), fpat()} + | nil + | {'::', fpat(), fpat()} | {tuple, [fpat()]} | {con, arities(), tag(), [fpat()]} | {assign, fpat(), fpat()}. +-spec remove_guards(env(), [aeso_syntax:alt()], aeso_syntax:expr()) -> [falt()]. remove_guards(_Env, [], _Switch) -> []; remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) -> @@ -879,7 +963,7 @@ split_tree(Env, Vars, Alts = [{'case', Pats, Body} | _]) -> Ys = [ Y || {var, Y} <- Pats ], Ren = [ {Y, X} || {Y, X} <- lists:zip(Ys, Xs), X /= Y, Y /= "_" ], %% TODO: Unreachable clauses error - {nosplit, rename(Ren, Body)}; + {nosplit, Ren, rename(Ren, Body)}; I when is_integer(I) -> {Vars0, [{X, Type} | Vars1]} = lists:split(I - 1, Vars), Type1 = strip_singleton_tuples(Type), @@ -891,10 +975,13 @@ split_tree(Env, Vars, Alts = [{'case', Pats, Body} | _]) -> {split, Type1, X, Cases} end. --spec merge_alts(integer(), var_name(), [{fsplit_pat(), falt()}]) -> [{fsplit_pat(), [falt()]}]. +-spec merge_alts(integer(), var_name(), Alts) -> [{fsplit_pat(), [falt()]}] when + Alts :: [{fsplit_pat(), falt()}]. merge_alts(I, X, Alts) -> merge_alts(I, X, Alts, []). +-spec merge_alts(integer(), var_name(), Alts, Alts) -> [{fsplit_pat(), [falt()]}] when + Alts :: [{fsplit_pat(), falt()}]. merge_alts(I, X, Alts, Alts1) -> lists:foldr(fun(A, As) -> merge_alt(I, X, A, As) end, Alts1, Alts). @@ -908,7 +995,7 @@ merge_alt(I, X, {P, A}, [{Q, As} | Rest]) -> ({bool, B}, {bool, B}) -> match; ({int, N}, {int, N}) -> match; ({string, S}, {string, S}) -> match; - (nil, nil) -> match; + (nil, nil) -> match; ({'::', _, _}, {'::', _, _}) -> match; ({con, _, C, _}, {con, _, C, _}) -> match; ({con, _, _, _}, {con, _, _, _}) -> mismatch; @@ -925,6 +1012,7 @@ merge_alt(I, X, {P, A}, [{Q, As} | Rest]) -> insert -> [{P, [A]}, {Q, As} | Rest] end. +-spec expand(integer(), var_name(), fsplit_pat(), fsplit_pat(), falt()) -> term(). expand(I, X, P, Q, Case = {'case', Ps, E}) -> {Ps0, [{var, Y} | Ps1]} = lists:split(I - 1, Ps), {Ps0r, Ren1} = rename_fpats([{Y, X} || Y /= X], Ps0), @@ -957,11 +1045,11 @@ split_alt(I, {'case', Pats, Body}) -> {SPat, {'case', Pats0 ++ InnerPats ++ Pats1, Body}}. -spec split_pat(fpat()) -> {fsplit_pat(), [fpat()]}. -split_pat(P = {var, _}) -> {{var, fresh_name()}, [P]}; -split_pat({bool, B}) -> {{bool, B}, []}; -split_pat({int, N}) -> {{int, N}, []}; -split_pat({string, N}) -> {{string, N}, []}; -split_pat(nil) -> {nil, []}; +split_pat(P = {var, _}) -> {{var, fresh_name()}, [P]}; +split_pat({bool, B}) -> {{bool, B}, []}; +split_pat({int, N}) -> {{int, N}, []}; +split_pat({string, N}) -> {{string, N}, []}; +split_pat(nil) -> {nil, []}; split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]}; split_pat({con, As, I, Pats}) -> Xs = [fresh_name() || _ <- Pats], @@ -976,7 +1064,7 @@ split_pat({tuple, Pats}) -> split_vars({bool, _}, boolean) -> []; split_vars({int, _}, integer) -> []; split_vars({string, _}, string) -> []; -split_vars(nil, {list, _}) -> []; +split_vars(nil, {list, _}) -> []; split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}]; split_vars({assign, X, P}, T) -> [{X, T}, {P, T}]; split_vars({con, _, I, Xs}, {variant, Cons}) -> @@ -1014,7 +1102,7 @@ pat_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Pats}) when C #con_tag{tag = I, arities = As} = lookup_con(Env, Con), {con, As, I, [pat_to_fcode(Env, Pat) || Pat <- Pats]}; pat_to_fcode(Env, _Type, {tuple, _, Pats}) -> - make_tuple([ pat_to_fcode(Env, Pat) || Pat <- Pats ]); + make_tuple_fpat([ pat_to_fcode(Env, Pat) || Pat <- Pats ]); pat_to_fcode(_Env, _Type, {bool, _, B}) -> {bool, B}; pat_to_fcode(_Env, _Type, {int, _, N}) -> {int, N}; pat_to_fcode(_Env, _Type, {char, _, N}) -> {int, N}; @@ -1032,7 +1120,7 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) -> {set, Pat} -> Pat %% {upd, _, _} is impossible in patterns end end, - make_tuple([pat_to_fcode(Env, FieldPat(Field)) + make_tuple_fpat([pat_to_fcode(Env, FieldPat(Field)) || Field <- Fields]); pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) -> {assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)}; @@ -1042,6 +1130,12 @@ pat_to_fcode(_Env, Type, Pat) -> %% -- Decision trees for boolean operators -- +-type decision_tree() :: false + | true + | {atom, fexpr()} + | {'if', fexpr(), decision_tree(), decision_tree()}. + +-spec decision_op(aeso_syntax:op(), decision_tree(), decision_tree()) -> decision_tree(). decision_op('&&', {atom, A}, B) -> {'if', A, B, false}; decision_op('&&', false, _) -> false; decision_op('&&', true, B) -> B; @@ -1051,26 +1145,28 @@ decision_op('||', true, _) -> true; decision_op(Op, {'if', A, Then, Else}, B) -> {'if', A, decision_op(Op, Then, B), decision_op(Op, Else, B)}. +-spec expr_to_decision_tree(env(), aeso_syntax:expr()) -> decision_tree(). expr_to_decision_tree(Env, {app, _Ann, {Op, _}, [A, B]}) when Op == '&&'; Op == '||' -> decision_op(Op, expr_to_decision_tree(Env, A), expr_to_decision_tree(Env, B)); expr_to_decision_tree(Env, {typed, _, Expr, _}) -> expr_to_decision_tree(Env, Expr); expr_to_decision_tree(Env, Expr) -> {atom, expr_to_fcode(Env, Expr)}. -decision_tree_to_fcode(false) -> {lit, {bool, false}}; -decision_tree_to_fcode(true) -> {lit, {bool, true}}; +-spec decision_tree_to_fcode(decision_tree()) -> fexpr(). +decision_tree_to_fcode(false) -> {lit, [], {bool, false}}; +decision_tree_to_fcode(true) -> {lit, [], {bool, true}}; decision_tree_to_fcode({atom, B}) -> B; decision_tree_to_fcode({'if', A, Then, Else}) -> X = fresh_name(), - {'let', X, A, - {switch, {split, boolean, X, [{'case', {bool, false}, {nosplit, decision_tree_to_fcode(Else)}}, - {'case', {bool, true}, {nosplit, decision_tree_to_fcode(Then)}}]}}}. + {'let', get_fann(A), X, A, + {switch, get_fann(A), {split, boolean, X, [{'case', {bool, false}, {nosplit, [], decision_tree_to_fcode(Else)}}, + {'case', {bool, true}, {nosplit, [], decision_tree_to_fcode(Then)}}]}}}. %% -- Statements -- -spec stmts_to_fcode(env(), [aeso_syntax:stmt()]) -> fexpr(). -stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, Expr} | Stmts]) -> - {'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)}; +stmts_to_fcode(Env, [{letval, Ann, {typed, _, {id, _, X}, _}, Expr} | Stmts]) -> + {'let', to_fann(Ann), X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)}; stmts_to_fcode(Env, [{letval, Ann, Pat, Expr} | Stmts]) -> expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [{guarded, Ann, [], {block, Ann, Stmts}}]}]}); stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Expr}]} | Stmts]) -> @@ -1078,25 +1174,27 @@ stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Ex {typed, Ann1, Id, T} -> {arg, Ann1, Id, T}; _ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared end || Arg <- Args ], - {'let', X, expr_to_fcode(Env, {lam, Ann, LamArgs, Expr}), + {'let', to_fann(Ann), X, expr_to_fcode(Env, {lam, Ann, LamArgs, Expr}), stmts_to_fcode(bind_var(Env, X), Stmts)}; stmts_to_fcode(Env, [Expr]) -> expr_to_fcode(Env, Expr); stmts_to_fcode(Env, [Expr | Stmts]) -> - {'let', "_", expr_to_fcode(Env, Expr), stmts_to_fcode(Env, Stmts)}. + {'let', to_fann(aeso_syntax:get_ann(Expr)), "_", expr_to_fcode(Env, Expr), stmts_to_fcode(Env, Stmts)}. %% -- Builtins -- +-spec op_builtins() -> [BuiltinFun] when + BuiltinFun :: atom(). op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, - stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, + stringinternal_to_bytes, stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, - bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, - bits_difference, int_to_str, address_to_str, crypto_verify_sig, - address_to_contract, - crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, - crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, + bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_difference, + int_to_str, int_to_bytes, int_mulmod, + address_to_str, address_to_bytes, address_to_contract, + crypto_verify_sig, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid, @@ -1106,47 +1204,52 @@ op_builtins() -> mcl_bls12_381_int_to_fr, mcl_bls12_381_int_to_fp, mcl_bls12_381_fr_to_int, mcl_bls12_381_fp_to_int ]. -set_state({reg, R}, Val) -> - {set_state, R, Val}; -set_state({tuple, Ls}, Val) -> +-spec set_state(state_layout(), fann(), fexpr()) -> fexpr(). +set_state({reg, R}, FAnn, Val) -> + {set_state, FAnn, R, Val}; +set_state({tuple, Ls}, FAnn, Val) -> ?make_let(X, Val, lists:foldr(fun({I, L}, Code) -> - {'let', "_", set_state(L, {proj, X, I - 1}), Code} - end, {tuple, []}, indexed(Ls))). + {'let', FAnn, "_", set_state(L, FAnn, {proj, FAnn, X, I - 1}), Code} + end, {tuple, FAnn, []}, indexed(Ls))). -get_state({reg, R}) -> - {get_state, R}; -get_state({tuple, Ls}) -> - {tuple, [get_state(L) || L <- Ls]}. +-spec get_state(state_layout(), fann()) -> fexpr(). +get_state({reg, R}, FAnn) -> + {get_state, FAnn, R}; +get_state({tuple, Ls}, FAnn) -> + {tuple, FAnn, [get_state(L, FAnn) || L <- Ls]}. -builtin_to_fcode(Layout, set_state, [Val]) -> - set_state(Layout, Val); -builtin_to_fcode(Layout, get_state, []) -> - get_state(Layout); -builtin_to_fcode(_Layout, require, [Cond, Msg]) -> - make_if(Cond, {tuple, []}, {builtin, abort, [Msg]}); -builtin_to_fcode(_Layout, chain_event, [Event]) -> - {def, event, [Event]}; -builtin_to_fcode(_Layout, map_delete, [Key, Map]) -> - {op, map_delete, [Map, Key]}; -builtin_to_fcode(_Layout, map_member, [Key, Map]) -> - {op, map_member, [Map, Key]}; -builtin_to_fcode(_Layout, map_lookup, [Key0, Map0]) -> +-spec builtin_to_fcode(state_layout(), fann(), BuiltinFun, [fexpr()]) -> fexpr() when + BuiltinFun :: atom(). %% No need to mention all of them +builtin_to_fcode(Layout, FAnn, set_state, [Val]) -> + set_state(Layout, FAnn, Val); +builtin_to_fcode(Layout, FAnn, get_state, []) -> + get_state(Layout, FAnn); +builtin_to_fcode(_Layout, FAnn, require, [Cond, Msg]) -> + make_if(Cond, {tuple, FAnn, []}, {builtin, FAnn, abort, [Msg]}); +builtin_to_fcode(_Layout, FAnn, chain_event, [Event]) -> + {def, FAnn, event, [Event]}; +builtin_to_fcode(_Layout, FAnn, map_delete, [Key, Map]) -> + {op, FAnn, map_delete, [Map, Key]}; +builtin_to_fcode(_Layout, FAnn, map_member, [Key, Map]) -> + {op, FAnn, map_member, [Map, Key]}; +builtin_to_fcode(_Layout, FAnn, map_lookup, [Key0, Map0]) -> ?make_let(Key, Key0, ?make_let(Map, Map0, - make_if({op, map_member, [Map, Key]}, - {con, [0, 1], 1, [{op, map_get, [Map, Key]}]}, - {con, [0, 1], 0, []}))); -builtin_to_fcode(_Layout, map_lookup_default, [Key, Map, Def]) -> - {op, map_get_d, [Map, Key, Def]}; -builtin_to_fcode(_Layout, Builtin, Args) -> + make_if({op, FAnn, map_member, [Map, Key]}, + {con, FAnn, [0, 1], 1, [{op, FAnn, map_get, [Map, Key]}]}, + {con, FAnn, [0, 1], 0, []}))); +builtin_to_fcode(_Layout, FAnn, map_lookup_default, [Key, Map, Def]) -> + {op, FAnn, map_get_d, [Map, Key, Def]}; +builtin_to_fcode(_Layout, FAnn, Builtin, Args) -> case lists:member(Builtin, op_builtins()) of - true -> {op, Builtin, Args}; - false -> {builtin, Builtin, Args} + true -> {op, FAnn, Builtin, Args}; + false -> {builtin, FAnn, Builtin, Args} end. %% -- Init function -- +-spec add_init_function(env(), functions()) -> functions(). add_init_function(Env, Funs0) -> case is_no_code(Env) of true -> Funs0; @@ -1155,10 +1258,11 @@ add_init_function(Env, Funs0) -> InitName = {entrypoint, <<"init">>}, InitFun = #{ body := InitBody} = maps:get(InitName, Funs), Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []}, - body => builtin_to_fcode(state_layout(Env), set_state, [InitBody]) } }, + body => builtin_to_fcode(state_layout(Env), [], set_state, [InitBody]) } }, Funs1 end. +-spec add_default_init_function(env(), functions()) -> functions(). add_default_init_function(_Env, Funs) -> InitName = {entrypoint, <<"init">>}, case maps:get(InitName, Funs, none) of @@ -1166,38 +1270,40 @@ add_default_init_function(_Env, Funs) -> Funs#{ InitName => #{attrs => [], args => [], return => {tuple, []}, - body => {tuple, []}} }; + body => {tuple, [], []}} }; _ -> Funs end. %% -- Event function -- +-spec add_event_function(env(), ftype() | none, functions()) -> functions(). add_event_function(_Env, none, Funs) -> Funs; add_event_function(Env, EventFType, Funs) -> Funs#{ event => event_function(Env, EventFType) }. +-spec event_function(env(), ftype()) -> fun_def(). event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {variant, FCons}) -> Cons = [ {Name, I - 1, proplists:get_value(indices, Ann)} || {I, {constr_t, Ann, {con, _, Name}, _}} <- indexed(EventCons) ], Arities = [length(Ts) || Ts <- FCons], Case = fun({Name, Tag, Ixs}) -> {ok, HashValue} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Name)), - Hash = {lit, {bytes, HashValue}}, + Hash = {lit, [], {bytes, HashValue}}, Vars = [ "arg" ++ integer_to_list(I) || I <- lists:seq(1, length(Ixs)) ], IVars = lists:zip(Ixs, Vars), Payload = case [ V || {notindexed, V} <- IVars ] of - [] -> {lit, {string, <<>>}}; - [V] -> {var, V} + [] -> {lit, [], {string, <<>>}}; + [V] -> {var, [], V} end, - Indices = [ {var, V} || {indexed, V} <- IVars ], - Body = {builtin, chain_event, [Payload, Hash | Indices]}, - {'case', {con, Arities, Tag, Vars}, {nosplit, Body}} + Indices = [ {var, [], V} || {indexed, V} <- IVars ], + Body = {builtin, [], chain_event, [Payload, Hash | Indices]}, + {'case', {con, Arities, Tag, Vars}, {nosplit, [], Body}} end, #{ attrs => [private], args => [{"e", EventType}], return => {tuple, []}, - body => {switch, {split, EventType, "e", lists:map(Case, Cons)}} }. + body => {switch, [], {split, EventType, "e", lists:map(Case, Cons)}} }. %% -- Lambda lifting --------------------------------------------------------- %% The expr_to_fcode compiler lambda expressions to {lam, Xs, Body}, but in @@ -1206,24 +1312,58 @@ event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {vari -spec lambda_lift(fcode()) -> fcode(). lambda_lift(FCode = #{ functions := Funs, state_layout := StateLayout }) -> - init_lambda_funs(), - Funs1 = maps:map(fun(_, Body) -> lambda_lift_fun(StateLayout, Body) end, Funs), - NewFuns = get_lambda_funs(), - FCode#{ functions := maps:merge(Funs1, NewFuns) }. + NewFuns = + [ {FunName, FunDef} + || {ParentName, ParentDef} <- maps:to_list(Funs), + {NewParentDef, Lambdas} <- [lambda_lift_fun(StateLayout, ParentName, ParentDef)], + {FunName, FunDef} <- [{ParentName, NewParentDef} | maps:to_list(Lambdas)] + ], + FCode#{ functions := maps:from_list(NewFuns) }. -define(lambda_key, '%lambdalifted'). -init_lambda_funs() -> put(?lambda_key, #{}). -get_lambda_funs() -> erase(?lambda_key). -add_lambda_fun(Def) -> - Name = fresh_fun(), +-spec init_lambda_funs() -> term(). +init_lambda_funs() -> put(?lambda_key, #{}). + +-spec get_lambda_funs() -> term(). +get_lambda_funs() -> + Lambdas = erase(?lambda_key), + %% Remove name feed entries and leave only actual functions + maps:filter(fun({fresh, _}, _) -> false; + (_, _) -> true + end, Lambdas). + +-spec add_lambda_fun(fun_name(), fann(), fun_def()) -> fun_name(). +add_lambda_fun(Parent, FAnn, Def) -> Funs = get(?lambda_key), - put(?lambda_key, Funs#{ Name => Def }), + LambdaId = maps:get({fresh, Parent}, Funs, 0), + Name = lambda_name(FAnn, LambdaId, Parent), + put(?lambda_key, Funs#{ Name => Def, {fresh, Parent} => LambdaId + 1}), Name. -lambda_lift_fun(Layout, Def = #{ body := Body }) -> - Def#{ body := lambda_lift_expr(Layout, Body) }. +-spec lambda_name(fann(), non_neg_integer(), fun_name()) -> fun_name(). +lambda_name(FAnn, Id, PName) -> + PSName = case PName of + {entrypoint, N} -> [binary_to_list(N)]; + {local_fun, Ns} -> Ns + end, + {_File, Line, Col} = ann_loc(FAnn), + Name = PSName ++ + [ "%lambda" + , if is_integer(Line) -> integer_to_list(Line); true -> "" end + , if is_integer(Col) -> integer_to_list(Col); true -> "" end + , integer_to_list(Id)], + {local_fun, Name}. +-spec lambda_lift_fun(state_layout(), fun_name(), fun_def()) -> {fun_def(), #{var_name() => term()}}. +lambda_lift_fun(Layout, Name, Def = #{ body := Body }) -> + %% Not thread safe! We initialize state per functions not to depend on the order in which + %% functions are processed. + init_lambda_funs(), + NewDef = Def#{ body := lambda_lift_expr(Layout, Name, Body) }, + {NewDef, get_lambda_funs()}. + +-spec lifted_fun([var_name()], [var_name()], fexpr()) -> fun_def(). lifted_fun([Z], Xs, Body) -> #{ attrs => [private], args => [{Z, any} | [{X, any} || X <- Xs]], @@ -1231,66 +1371,73 @@ lifted_fun([Z], Xs, Body) -> body => Body }; lifted_fun(FVs, Xs, Body) -> Z = "%env", - Proj = fun({I, Y}, E) -> {'let', Y, {proj, {var, Z}, I - 1}, E} end, + FAnn = get_fann(Body), + Proj = fun({I, Y}, E) -> {'let', FAnn, Y, {proj, FAnn, {var, FAnn, Z}, I - 1}, E} end, #{ attrs => [private], args => [{Z, any} | [{X, any} || X <- Xs]], return => any, body => lists:foldr(Proj, Body, indexed(FVs)) }. -make_closure(FVs, Xs, Body) -> - Fun = add_lambda_fun(lifted_fun(FVs, Xs, Body)), - Tup = fun([Y]) -> Y; (Ys) -> {tuple, Ys} end, - {closure, Fun, Tup([{var, Y} || Y <- FVs])}. +-spec make_closure(fun_name(), fann(), [var_name()], [var_name()], fexpr()) -> Closure when + Closure :: fexpr(). +make_closure(ParentName, FAnn, FVs, Xs, Body) -> + Name = add_lambda_fun(ParentName, FAnn, lifted_fun(FVs, Xs, Body)), + Tup = fun([Y]) -> Y; (Ys) -> {tuple, FAnn, Ys} end, + {closure, FAnn, Name, Tup([{var, FAnn, Y} || Y <- FVs])}. -lambda_lift_expr(Layout, {lam, Xs, Body}) -> - FVs = free_vars({lam, Xs, Body}), - make_closure(FVs, Xs, lambda_lift_expr(Layout, Body)); -lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> - [Tag, F, Ar | _] = tuple_to_list(UExpr), +-spec lambda_lift_expr(state_layout(), fun_name(), fexpr()) -> Closure when + Closure :: fexpr(). +lambda_lift_expr(Layout, Name, L = {lam, FAnn, Xs, Body}) -> + FVs = free_vars(L), + make_closure(Name, FAnn, FVs, Xs, lambda_lift_expr(Layout, Name, Body)); +lambda_lift_expr(Layout, Name, UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u -> + [Tag, FAnn, F, Ar | _] = tuple_to_list(UExpr), ExtraArgs = case UExpr of - {builtin_u, _, _, TypeArgs} -> TypeArgs; - _ -> [] + {builtin_u, _, _, _, TypeArgs} -> TypeArgs; + _ -> [] end, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], - Args = [{var, X} || X <- Xs] ++ ExtraArgs, + Args = [{var, get_fann(UExpr), X} || X <- Xs] ++ ExtraArgs, Body = case Tag of - builtin_u -> builtin_to_fcode(Layout, F, Args); - def_u -> {def, F, Args} + builtin_u -> builtin_to_fcode(Layout, get_fann(UExpr), F, Args); + def_u -> {def, get_fann(UExpr), F, Args} end, - make_closure([], Xs, Body); -lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) -> + make_closure(Name, FAnn, [], Xs, Body); +lambda_lift_expr(Layout, Name, {remote_u, FAnn, ArgsT, RetT, Ct, F}) -> FVs = free_vars(Ct), - Ct1 = lambda_lift_expr(Layout, Ct), + Ct1 = lambda_lift_expr(Layout, Name, Ct), NamedArgCount = 3, Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ], - Args = [{var, X} || X <- Xs], - make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args}); -lambda_lift_expr(Layout, Expr) -> + Args = [{var, [], X} || X <- Xs], + make_closure(Name, FAnn, FVs, Xs, {remote, FAnn, ArgsT, RetT, Ct1, F, Args}); +lambda_lift_expr(Layout, Name, Expr) -> case Expr of - {lit, _} -> Expr; - nil -> Expr; - {var, _} -> Expr; - {closure, _, _} -> Expr; - {def, D, As} -> {def, D, lambda_lift_exprs(Layout, As)}; - {builtin, B, As} -> {builtin, B, lambda_lift_exprs(Layout, As)}; - {remote, ArgsT, RetT, Ct, F, As} -> {remote, ArgsT, RetT, lambda_lift_expr(Layout, Ct), F, lambda_lift_exprs(Layout, As)}; - {con, Ar, C, As} -> {con, Ar, C, lambda_lift_exprs(Layout, As)}; - {tuple, As} -> {tuple, lambda_lift_exprs(Layout, As)}; - {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)}; - {'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)}; - {get_state, _} -> Expr; - {switch, S} -> {switch, lambda_lift_expr(Layout, S)}; - {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Alts)}; - {nosplit, A} -> {nosplit, lambda_lift_expr(Layout, A)}; - {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, S)} + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {var, _, _} -> Expr; + {closure, _, _, _} -> Expr; + {def, FAnn, D, As} -> {def, FAnn, D, lambda_lift_exprs(Layout, Name, As)}; + {builtin, FAnn, B, As} -> {builtin, FAnn, B, lambda_lift_exprs(Layout, Name, As)}; + {remote, FAnn, ArgsT, RetT, Ct, F, As} -> {remote, FAnn, ArgsT, RetT, lambda_lift_expr(Layout, Name, Ct), F, lambda_lift_exprs(Layout, Name, As)}; + {con, FAnn, Ar, C, As} -> {con, FAnn, Ar, C, lambda_lift_exprs(Layout, Name, As)}; + {tuple, FAnn, As} -> {tuple, FAnn, lambda_lift_exprs(Layout, Name, As)}; + {proj, FAnn, A, I} -> {proj, FAnn, lambda_lift_expr(Layout, Name, A), I}; + {set_proj, FAnn, A, I, B} -> {set_proj, FAnn, lambda_lift_expr(Layout, Name, A), I, lambda_lift_expr(Layout, Name, B)}; + {op, FAnn, Op, As} -> {op, FAnn, Op, lambda_lift_exprs(Layout, Name, As)}; + {'let', FAnn, X, A, B} -> {'let', FAnn, X, lambda_lift_expr(Layout, Name, A), lambda_lift_expr(Layout, Name, B)}; + {funcall, FAnn, A, Bs} -> {funcall, FAnn, lambda_lift_expr(Layout, Name, A), lambda_lift_exprs(Layout, Name, Bs)}; + {set_state, FAnn, R, A} -> {set_state, FAnn, R, lambda_lift_expr(Layout, Name, A)}; + {get_state, _, _} -> Expr; + {switch, FAnn, S} -> {switch, FAnn, lambda_lift_expr(Layout, Name, S)}; + {split, Type, X, Alts} -> {split, Type, X, lambda_lift_exprs(Layout, Name, Alts)}; + {nosplit, Rens, A} -> {nosplit, Rens, lambda_lift_expr(Layout, Name, A)}; + {'case', P, S} -> {'case', P, lambda_lift_expr(Layout, Name, S)} end. -lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As]. +-spec lambda_lift_exprs(state_layout(), fun_name(), [fexpr()]) -> [Closure] when + Closure :: fexpr(). +lambda_lift_exprs(Layout, Name, As) -> [lambda_lift_expr(Layout, Name, A) || A <- As]. %% -- Optimisations ---------------------------------------------------------- @@ -1326,68 +1473,82 @@ optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) -> %% --- Inlining --- -spec inliner(fcode(), fun_name(), fexpr()) -> fexpr(). -inliner(Fcode, Fun, {def, Fun1, Args} = E) when Fun1 /= Fun -> +inliner(Fcode, Fun, {def, _, Fun1, Args} = E) when Fun1 /= Fun -> case should_inline(Fcode, Fun1) of false -> E; true -> inline(Fcode, Fun1, Args) end; inliner(_Fcode, _Fun, E) -> E. +-spec should_inline(fcode(), fun_name()) -> boolean(). should_inline(_Fcode, _Fun1) -> false == list_to_atom("true"). %% Dialyzer -inline(_Fcode, Fun, Args) -> {def, Fun, Args}. %% TODO +-spec inline(fcode(), fun_name(), Args) -> Def when + Args :: [fexpr()], + Def :: fexpr(). +inline(_Fcode, Fun, Args) -> {def, [], Fun, Args}. %% TODO %% --- Bind subexpressions --- -define(make_lets(Xs, Es, Body), make_lets(Es, fun(Xs) -> Body end)). +-spec bind_subexpressions(fexpr()) -> fexpr(). bind_subexpressions(Expr) -> bottom_up(fun bind_subexpressions/2, Expr). -bind_subexpressions(_, {tuple, Es}) -> - ?make_lets(Xs, Es, {tuple, Xs}); -bind_subexpressions(_, {set_proj, A, I, B}) -> - ?make_lets([X, Y], [A, B], {set_proj, X, I, Y}); +-spec bind_subexpressions(expr_env(), fexpr()) -> fexpr(). +bind_subexpressions(_, {tuple, FAnn, Es}) -> + ?make_lets(Xs, Es, {tuple, FAnn, Xs}); +bind_subexpressions(_, {set_proj, FAnn, A, I, B}) -> + ?make_lets([X, Y], [A, B], {set_proj, FAnn, X, I, Y}); bind_subexpressions(_, E) -> E. +-spec make_lets([fexpr()], fun(([fexpr()]) -> fexpr())) -> fexpr(). make_lets(Es, Body) -> make_lets(Es, [], Body). +-spec make_lets([fexpr()], [fexpr()], fun(([fexpr()]) -> fexpr())) -> fexpr(). make_lets([], Xs, Body) -> Body(lists:reverse(Xs)); -make_lets([{var, _} = E | Es], Xs, Body) -> +make_lets([{var, _, _} = E | Es], Xs, Body) -> make_lets(Es, [E | Xs], Body); -make_lets([{lit, _} = E | Es], Xs, Body) -> +make_lets([{lit, _, _} = E | Es], Xs, Body) -> make_lets(Es, [E | Xs], Body); make_lets([E | Es], Xs, Body) -> ?make_let(X, E, make_lets(Es, [X | Xs], Body)). %% --- Inline local functions --- +-spec inline_local_functions(fexpr()) -> fexpr(). inline_local_functions(Expr) -> bottom_up(fun inline_local_functions/2, Expr). -inline_local_functions(Env, {funcall, {proj, {var, Y}, 0}, [{proj, {var, Y}, 1} | Args]} = Expr) -> +-spec inline_local_functions(expr_env(), fexpr()) -> fexpr(). +inline_local_functions(Env, {funcall, _, {proj, _, {var, _, Y}, 0}, [{proj, _, {var, _, Y}, 1} | Args]} = Expr) -> %% TODO: Don't always inline local funs? case maps:get(Y, Env, free) of - {lam, Xs, Body} -> let_bind(lists:zip(Xs, Args), Body); - _ -> Expr + {lam, _, Xs, Body} -> let_bind(lists:zip(Xs, Args), Body); + _ -> Expr end; inline_local_functions(_, Expr) -> Expr. %% --- Let-floating --- +-spec let_floating(fexpr()) -> fexpr(). let_floating(Expr) -> bottom_up(fun let_float/2, Expr). -let_float(_, {'let', X, E, Body}) -> - pull_out_let({'let', X, {here, E}, Body}); -let_float(_, {proj, E, I}) -> - pull_out_let({proj, {here, E}, I}); -let_float(_, {set_proj, E, I, V}) -> - pull_out_let({set_proj, {here, E}, I, {here, V}}); -let_float(_, {op, Op, Es}) -> +-spec let_float(expr_env(), fexpr()) -> fexpr(). +let_float(_, {'let', FAnn, X, E, Body}) -> + pull_out_let({'let', FAnn, X, {here, E}, Body}); +let_float(_, {proj, FAnn, E, I}) -> + pull_out_let({proj, FAnn, {here, E}, I}); +let_float(_, {set_proj, FAnn, E, I, V}) -> + pull_out_let({set_proj, FAnn, {here, E}, I, {here, V}}); +let_float(_, {op, FAnn, Op, Es}) -> {Lets, Es1} = pull_out_let([{here, E} || E <- Es]), - let_bind(Lets, {op, Op, Es1}); + let_bind(Lets, {op, FAnn, Op, Es1}); let_float(_, E) -> E. +-spec pull_out_let(fexpr() | [fexpr()]) -> fexpr() | {Lets, [fexpr()]} when + Lets :: [{var_name(), fexpr()}]. pull_out_let(Expr) when is_tuple(Expr) -> {Lets, Es} = pull_out_let(tuple_to_list(Expr)), Inner = list_to_tuple(Es), @@ -1407,9 +1568,13 @@ pull_out_let(Es) when is_list(Es) -> end. %% Also renames the variables to fresh names +-spec let_view(fexpr()) -> {Lets, fexpr()} when + Lets :: [{var_name(), fexpr()}]. let_view(E) -> let_view(E, [], []). -let_view({'let', X, E, Rest}, Ren, Lets) -> +-spec let_view(fexpr(), rename(), Lets) -> {Lets, fexpr()} when + Lets :: [{var_name(), fexpr()}]. +let_view({'let', _, X, E, Rest}, Ren, Lets) -> Z = fresh_name(), let_view(Rest, [{X, Z} | Ren], [{Z, rename(Ren, E)} | Lets]); let_view(E, Ren, Lets) -> @@ -1421,60 +1586,61 @@ let_view(E, Ren, Lets) -> simplifier(Expr) -> bottom_up(fun simplify/2, Expr). --spec simplify(#{var_name() => fexpr()}, fexpr()) -> fexpr(). +-spec simplify(expr_env(), fexpr()) -> fexpr(). %% (e₀, .., en).i -> %% let _ = e₀ in .. let x = ei in .. let _ = en in x -simplify(_Env, {proj, {tuple, Es}, I}) -> +simplify(_Env, {proj, FAnn, {tuple, _, Es}, I}) -> It = lists:nth(I + 1, Es), X = fresh_name(), Dup = safe_to_duplicate(It), - Val = if Dup -> It; true -> {var, X} end, + Val = if Dup -> It; true -> {var, FAnn, X} end, lists:foldr( fun({J, E}, Rest) when I == J -> case Dup of true -> Rest; - false -> {'let', X, E, Rest} + false -> {'let', FAnn, X, E, Rest} end; ({_, E}, Rest) -> case read_only(E) of true -> Rest; - false -> {'let', "_", E, Rest} + false -> {'let', FAnn, "_", E, Rest} end end, Val, indexed(Es)); %% let x = e in .. x.i .. -simplify(Env, {proj, {var, X}, I} = Expr) -> - case simpl_proj(Env, I, {var, X}) of +simplify(Env, {proj, _, Var = {var, _, _}, I} = Expr) -> + case simpl_proj(Env, I, Var) of false -> Expr; E -> E end; -simplify(Env, {switch, Split}) -> - case simpl_switch(Env, [], Split) of - nomatch -> {builtin, abort, [{lit, {string, <<"Incomplete patterns">>}}]}; - stuck -> {switch, Split}; +simplify(Env, {switch, FAnn, Split}) -> + case simpl_switch(Env, FAnn, [], Split) of + nomatch -> {builtin, FAnn, abort, [{lit, FAnn, {string, <<"Incomplete patterns">>}}]}; Expr -> Expr end; simplify(_, E) -> E. +-spec simpl_proj(expr_env(), integer(), fexpr()) -> fexpr() | false. simpl_proj(Env, I, Expr) -> IfSafe = fun(E) -> case safe_to_duplicate(E) of true -> E; false -> false end end, case Expr of - false -> false; - {var, X} -> simpl_proj(Env, I, maps:get(X, Env, false)); - {tuple, Es} -> IfSafe(lists:nth(I + 1, Es)); - {set_proj, _, I, Val} -> IfSafe(Val); - {set_proj, E, _, _} -> simpl_proj(Env, I, E); - {proj, E, J} -> simpl_proj(Env, I, simpl_proj(Env, J, E)); - _ -> false + false -> false; + {var, _, X} -> simpl_proj(Env, I, maps:get(X, Env, false)); + {tuple, _, Es} -> IfSafe(lists:nth(I + 1, Es)); + {set_proj, _, _, I, Val} -> IfSafe(Val); + {set_proj, _, E, _, _} -> simpl_proj(Env, I, E); + {proj, _, E, J} -> simpl_proj(Env, I, simpl_proj(Env, J, E)); + _ -> false end. +-spec get_catchalls([fcase()]) -> [fcase()]. get_catchalls(Alts) -> [ C || C = {'case', {var, _}, _} <- Alts ]. @@ -1486,6 +1652,7 @@ get_catchalls(Alts) -> %% _ => switch(x) %% .. %% _ => e +-spec add_catchalls([fcase()], [fcase()]) -> [fcase()]. add_catchalls(Alts, []) -> Alts; add_catchalls(Alts, Catchalls) -> case lists:splitwith(fun({'case', {var, _}, _}) -> false; (_) -> true end, @@ -1495,118 +1662,120 @@ add_catchalls(Alts, Catchalls) -> %% NOTE: relies on catchalls always being at the end end. -nest_catchalls([C = {'case', {var, _}, {nosplit, _}} | _]) -> C; +-spec nest_catchalls([fcase()]) -> fcase(). +nest_catchalls([C = {'case', {var, _}, {nosplit, _, _}} | _]) -> C; nest_catchalls([{'case', P = {var, _}, {split, Type, X, Alts}} | Catchalls]) -> {'case', P, {split, Type, X, add_catchalls(Alts, Catchalls)}}. -simpl_switch(_Env, _, {nosplit, E}) -> E; -simpl_switch(Env, Catchalls, {split, Type, X, Alts}) -> +-spec simpl_switch(expr_env(), fann(), [fcase()], fsplit()) -> fexpr() | nomatch. +simpl_switch(_Env, _FAnn, _, {nosplit, _, E}) -> E; +simpl_switch(Env, FAnn, Catchalls, {split, Type, X, Alts}) -> Alts1 = add_catchalls(Alts, Catchalls), - Stuck = {switch, {split, Type, X, Alts1}}, - case constructor_form(Env, {var, X}) of + Stuck = {switch, FAnn, {split, Type, X, Alts1}}, + case constructor_form(Env, {var, [], X}) of false -> Stuck; - E -> - case simpl_case(Env, E, Alts1) of - stuck -> Stuck; - Res -> Res - end + E -> simpl_case(Env, E, Alts1) end. +-spec simpl_case(expr_env(), fexpr(), [fcase()]) -> fexpr() | nomatch. simpl_case(_, _, []) -> nomatch; simpl_case(Env, E, [{'case', Pat, Body} | Alts]) -> case match_pat(Pat, E) of false -> simpl_case(Env, E, Alts); Binds -> Env1 = maps:merge(Env, maps:from_list(Binds)), - case simpl_switch(Env1, get_catchalls(Alts), Body) of + case simpl_switch(Env1, get_fann(E), get_catchalls(Alts), Body) of nomatch -> simpl_case(Env, E, Alts); - stuck -> stuck; Body1 -> let_bind(Binds, Body1) end end. -spec match_pat(fsplit_pat(), fexpr()) -> false | [{var_name(), fexpr()}]. -match_pat({tuple, Xs}, {tuple, Es}) -> lists:zip(Xs, Es); -match_pat({con, _, C, Xs}, {con, _, C, Es}) -> lists:zip(Xs, Es); -match_pat(L, {lit, L}) -> []; -match_pat(nil, nil) -> []; -match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}]; -match_pat({var, X}, E) -> [{X, E}]; -match_pat({assign, X, P}, E) -> [{X, E}, {P, E}]; -match_pat(_, _) -> false. +match_pat({tuple, Xs}, {tuple, _, Es}) -> lists:zip(Xs, Es); +match_pat({con, _, C, Xs}, {con, _, _, C, Es}) -> lists:zip(Xs, Es); +match_pat(L, {lit, _, L}) -> []; +match_pat(nil, {nil, _}) -> []; +match_pat({'::', X, Y}, {op, _, '::', [A, B]}) -> [{X, A}, {Y, B}]; +match_pat({var, X}, E) -> [{X, E}]; +match_pat({assign, X, P}, E) -> [{X, E}, {P, E}]; +match_pat(_, _) -> false. +-spec constructor_form(expr_env(), fexpr()) -> fexpr() | false. constructor_form(Env, Expr) -> case Expr of - {var, X} -> + {var, _, X} -> case maps:get(X, Env, free) of free -> false; E -> constructor_form(Env, E) %% TODO: shadowing? end; - {set_proj, E, I, V} -> + {set_proj, _, E, I, V} -> case constructor_form(Env, E) of - {tuple, Es} -> {tuple, setnth(I + 1, V, Es)}; - _ -> false + {tuple, FAnn, Es} -> {tuple, FAnn, setnth(I + 1, V, Es)}; + _ -> false end; - {proj, E, I} -> + {proj, _, E, I} -> case constructor_form(Env, E) of - {tuple, Es} -> constructor_form(Env, lists:nth(I + 1, Es)); - _ -> false + {tuple, _, Es} -> constructor_form(Env, lists:nth(I + 1, Es)); + _ -> false end; - {con, _, _, _} -> Expr; - {tuple, _} -> Expr; - {lit, _} -> Expr; - nil -> Expr; - {op, '::', _} -> Expr; - _ -> false + {con, _, _, _, _} -> Expr; + {tuple, _, _} -> Expr; + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {op, _, '::', _} -> Expr; + _ -> false end. %% --- Drop unused lets --- +-spec drop_unused_lets(fexpr()) -> fexpr(). drop_unused_lets(Expr) -> bottom_up(fun drop_unused_lets/2, Expr). -drop_unused_lets(_, {'let', X, E, Body} = Expr) -> +-spec drop_unused_lets(expr_env(), fexpr()) -> fexpr(). +drop_unused_lets(_, {'let', FAnn, X, E, Body} = Expr) -> case {read_only(E), not lists:member(X, free_vars(Body))} of {true, true} -> Body; - {false, true} -> {'let', "_", E, Body}; + {false, true} -> {'let', FAnn, "_", E, Body}; _ -> Expr end; drop_unused_lets(_, Expr) -> Expr. %% -- Static analysis -------------------------------------------------------- -safe_to_duplicate({lit, _}) -> true; -safe_to_duplicate({var, _}) -> true; -safe_to_duplicate(nil) -> true; -safe_to_duplicate({tuple, []}) -> true; -safe_to_duplicate(_) -> false. +-spec safe_to_duplicate(fexpr()) -> boolean(). +safe_to_duplicate({lit, _, _}) -> true; +safe_to_duplicate({var, _, _}) -> true; +safe_to_duplicate({nil, _}) -> true; +safe_to_duplicate({tuple, _, []}) -> true; +safe_to_duplicate(_) -> false. -spec read_only(fexpr() | fsplit() | fcase() | [fexpr()] | [fcase()]) -> boolean(). -read_only({lit, _}) -> true; -read_only({var, _}) -> true; -read_only(nil) -> true; -read_only({con, _, _, Es}) -> read_only(Es); -read_only({tuple, Es}) -> read_only(Es); -read_only({proj, E, _}) -> read_only(E); -read_only({set_proj, A, _, B}) -> read_only([A, B]); -read_only({op, _, Es}) -> read_only(Es); -read_only({get_state, _}) -> true; -read_only({set_state, _, _}) -> false; -read_only({def_u, _, _}) -> true; -read_only({remote_u, _, _, _, _}) -> true; -read_only({builtin_u, _, _}) -> true; -read_only({builtin_u, _, _, _}) -> true; -read_only({lam, _, _}) -> true; -read_only({def, _, _}) -> false; %% TODO: purity analysis -read_only({remote, _, _, _, _, _}) -> false; -read_only({builtin, _, _}) -> false; %% TODO: some builtins are -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({'let', _, A, B}) -> read_only([A, B]); -read_only({funcall, _, _}) -> false; -read_only({closure, _, _}) -> internal_error(no_closures_here); -read_only(Es) when is_list(Es) -> lists:all(fun read_only/1, Es). +read_only({lit, _, _}) -> true; +read_only({var, _, _}) -> true; +read_only({nil, _}) -> true; +read_only({con, _, _, _, Es}) -> read_only(Es); +read_only({tuple, _, Es}) -> read_only(Es); +read_only({proj, _, E, _}) -> read_only(E); +read_only({set_proj, _, A, _, B}) -> read_only([A, B]); +read_only({op, _, _, Es}) -> read_only(Es); +read_only({get_state, _, _}) -> true; +read_only({set_state, _, _, _}) -> false; +read_only({def_u, _, _, _}) -> true; +read_only({remote_u, _, _, _, _, _}) -> true; +read_only({builtin_u, _, _, _}) -> true; +read_only({builtin_u, _, _, _, _}) -> true; +read_only({lam, _, _, _}) -> true; +read_only({def, _, _, _}) -> false; %% TODO: purity analysis +read_only({remote, _, _, _, _, _, _}) -> false; +read_only({builtin, _, _, _}) -> false; %% TODO: some builtins are +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({'let', _, _, A, B}) -> read_only([A, B]); +read_only({funcall, _, _, _}) -> false; +read_only({closure, _, _, _}) -> internal_error(no_closures_here); +read_only(Es) when is_list(Es) -> lists:all(fun read_only/1, Es). %% --- Deadcode elimination --- @@ -1616,12 +1785,15 @@ eliminate_dead_code(Code = #{ functions := Funs }) -> Code#{ functions := maps:filter(fun(Name, _) -> maps:is_key(Name, UsedFuns) end, Funs) }. --spec used_functions(#{ fun_name() => fun_def() }) -> #{ fun_name() => true }. +-spec used_functions(functions()) -> Used when + Used :: #{ fun_name() => true }. used_functions(Funs) -> Exported = [ Fun || {Fun, #{ attrs := Attrs }} <- maps:to_list(Funs), not lists:member(private, Attrs) ], used_functions(#{}, Exported, Funs). +-spec used_functions(Used, [fun_name()], functions()) -> Used when + Used :: #{ fun_name() => true }. used_functions(Used, [], _) -> Used; used_functions(Used, [Name | Rest], Defs) -> case maps:is_key(Name, Used) of @@ -1682,6 +1854,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> FunEnv1 = maps:from_list(lists:flatmap(Entry, Decls)), Env#{ fun_env := maps:merge(FunEnv, FunEnv1) }. +-spec make_fun_name(env(), aeso_syntax:ann(), aeso_syntax:name()) -> fun_name(). make_fun_name(#{ context := Context }, Ann, Name) -> Entrypoint = proplists:get_value(entrypoint, Ann, false), case Context of @@ -1721,23 +1894,26 @@ lookup_con(#{ con_env := ConEnv }, Con) -> Tag -> Tag end. +-spec bind_vars(env(), [var_name()]) -> env(). bind_vars(Env, Xs) -> lists:foldl(fun(X, E) -> bind_var(E, X) end, Env, Xs). +-spec bind_var(env(), var_name()) -> env(). bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }. -resolve_var(#{ vars := Vars } = Env, [X]) -> +-spec resolve_var(env(), aeso_syntax:ann(), [aeso_syntax:name()]) -> fexpr(). +resolve_var(#{ vars := Vars } = Env, Ann, [X]) -> case lists:member(X, Vars) of - true -> {var, X}; + true -> {var, to_fann(Ann), X}; false -> case resolve_const(Env, [X]) of - false -> resolve_fun(Env, [X]); + false -> resolve_fun(Env, Ann, [X]); Const -> Const end end; -resolve_var(Env, Q) -> +resolve_var(Env, Ann, Q) -> case resolve_const(Env, Q) of - false -> resolve_fun(Env, Q); + false -> resolve_fun(Env, Ann, Q); Const -> Const end. @@ -1747,25 +1923,30 @@ resolve_const(#{ consts := Consts }, Q) -> Val -> Val end. -resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) -> +-spec resolve_fun(env(), aeso_syntax:ann(), [aeso_syntax:name()]) -> fexpr(). +resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Ann, Q) -> case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of {not_found, not_found} -> internal_error({unbound_variable, Q}); - {_, {B, none}} -> builtin_to_fcode(state_layout(Env), B, []); - {_, {B, Ar}} -> {builtin_u, B, Ar}; - {{Fun, Ar}, _} -> {def_u, Fun, Ar} + {_, {B, none}} -> builtin_to_fcode(state_layout(Env), to_fann(Ann), B, []); + {_, {B, Ar}} -> {builtin_u, to_fann(Ann), B, Ar}; + {{Fun, Ar}, _} -> {def_u, to_fann(Ann), Fun, Ar} end. +-spec init_fresh_names([option()]) -> term(). init_fresh_names(Options) -> proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(), put('%fresh', 0). +-spec clear_fresh_names([option()]) -> term(). clear_fresh_names(Options) -> proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(), erase('%fresh'). +-spec init_saved_fresh_names() -> term(). init_saved_fresh_names() -> put(saved_fresh_names, #{}). +-spec clear_saved_fresh_names() -> term(). clear_saved_fresh_names() -> erase(saved_fresh_names). @@ -1781,9 +1962,6 @@ fresh_name_save(Name) -> -spec fresh_name() -> var_name(). fresh_name() -> fresh_name("%"). --spec fresh_fun() -> fun_name(). -fresh_fun() -> {local_fun, [fresh_name("^")]}. - -spec fresh_name(string()) -> var_name(). fresh_name(Prefix) -> N = get('%fresh'), @@ -1810,96 +1988,103 @@ fsplit_pat_vars({string, _}) -> []; fsplit_pat_vars(nil) -> []; fsplit_pat_vars({'::', P, Q}) -> [P, Q]; fsplit_pat_vars({tuple, Ps}) -> Ps; -fsplit_pat_vars({con, _, _, Ps}) -> Ps. +fsplit_pat_vars({con, _, _, Ps}) -> Ps; +fsplit_pat_vars({assign, X, P}) -> [X, P]. +-spec free_vars(fexpr() | [fexpr()]) -> [var_name()]. free_vars(Xs) when is_list(Xs) -> lists:umerge([ free_vars(X) || X <- Xs ]); free_vars(Expr) -> case Expr of - {var, X} -> [X]; - {lit, _} -> []; - nil -> []; - {def, _, As} -> free_vars(As); - {def_u, _, _} -> []; - {remote, _, _, Ct, _, As} -> free_vars([Ct | As]); - {remote_u, _, _, Ct, _} -> free_vars(Ct); - {builtin, _, As} -> free_vars(As); - {builtin_u, _, _} -> []; - {builtin_u, _, _, _} -> []; %% Typereps are always literals - {con, _, _, As} -> free_vars(As); - {tuple, As} -> free_vars(As); - {proj, A, _} -> free_vars(A); - {set_proj, A, _, B} -> free_vars([A, B]); - {op, _, As} -> free_vars(As); - {'let', X, A, B} -> free_vars([A, {lam, [X], B}]); - {funcall, A, Bs} -> free_vars([A | Bs]); - {set_state, _, A} -> free_vars(A); - {get_state, _} -> []; - {lam, Xs, B} -> free_vars(B) -- lists:sort(Xs); - {closure, _, A} -> free_vars(A); - {switch, A} -> free_vars(A); - {split, _, X, As} -> free_vars([{var, X} | As]); - {nosplit, A} -> free_vars(A); - {'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P)) + {var, _, X} -> [X]; + {lit, _, _} -> []; + {nil, _} -> []; + {def, _, _, As} -> free_vars(As); + {def_u, _, _, _} -> []; + {remote, _, _, _, Ct, _, As} -> free_vars([Ct | As]); + {remote_u, _, _, _, Ct, _} -> free_vars(Ct); + {builtin, _, _, As} -> free_vars(As); + {builtin_u, _, _, _} -> []; + {builtin_u, _, _, _, _} -> []; %% Typereps are always literals + {con, _, _, _, As} -> free_vars(As); + {tuple, _, As} -> free_vars(As); + {proj, _, A, _} -> free_vars(A); + {set_proj, _, A, _, B} -> free_vars([A, B]); + {op, _, _, As} -> free_vars(As); + {'let', FAnn, X, A, B} -> free_vars([A, {lam, FAnn, [X], B}]); + {funcall, _, A, Bs} -> free_vars([A | Bs]); + {set_state, _, _, A} -> free_vars(A); + {get_state, _, _} -> []; + {lam, _, Xs, B} -> free_vars(B) -- lists:sort(Xs); + {closure, _, _, A} -> free_vars(A); + {switch, _, A} -> free_vars(A); + {split, _, X, As} -> free_vars([{var, [], X} | As]); + {nosplit, _, A} -> free_vars(A); + {'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P)) end. +-spec used_defs(fexpr() | [fexpr()]) -> [fun_name()]. used_defs(Xs) when is_list(Xs) -> lists:umerge([ used_defs(X) || X <- Xs ]); used_defs(Expr) -> case Expr of - {var, _} -> []; - {lit, _} -> []; - nil -> []; - {def, F, As} -> lists:umerge([F], used_defs(As)); - {def_u, F, _} -> [F]; - {remote, _, _, Ct, _, As} -> used_defs([Ct | As]); - {remote_u, _, _, Ct, _} -> used_defs(Ct); - {builtin, _, As} -> used_defs(As); - {builtin_u, _, _} -> []; - {builtin_u, _, _, _} -> []; - {con, _, _, As} -> used_defs(As); - {tuple, As} -> used_defs(As); - {proj, A, _} -> used_defs(A); - {set_proj, A, _, B} -> used_defs([A, B]); - {op, _, As} -> used_defs(As); - {'let', _, A, B} -> used_defs([A, B]); - {funcall, A, Bs} -> used_defs([A | Bs]); - {set_state, _, A} -> used_defs(A); - {get_state, _} -> []; - {lam, _, B} -> used_defs(B); - {closure, F, A} -> lists:umerge([F], used_defs(A)); - {switch, A} -> used_defs(A); - {split, _, _, As} -> used_defs(As); - {nosplit, A} -> used_defs(A); - {'case', _, A} -> used_defs(A) + {var, _, _} -> []; + {lit, _, _} -> []; + {nil, _} -> []; + {def, _, F, As} -> lists:umerge([F], used_defs(As)); + {def_u, _, F, _} -> [F]; + {remote, _, _, _, Ct, _, As} -> used_defs([Ct | As]); + {remote_u, _, _, _, Ct, _} -> used_defs(Ct); + {builtin, _, _, As} -> used_defs(As); + {builtin_u, _, _, _} -> []; + {builtin_u, _, _, _, _} -> []; + {con, _, _, _, As} -> used_defs(As); + {tuple, _, As} -> used_defs(As); + {proj, _, A, _} -> used_defs(A); + {set_proj, _, A, _, B} -> used_defs([A, B]); + {op, _, _, As} -> used_defs(As); + {'let', _, _, A, B} -> used_defs([A, B]); + {funcall, _, A, Bs} -> used_defs([A | Bs]); + {set_state, _, _, A} -> used_defs(A); + {get_state, _, _} -> []; + {lam, _, _, B} -> used_defs(B); + {closure, _, F, A} -> lists:umerge([F], used_defs(A)); + {switch, _, A} -> used_defs(A); + {split, _, _, As} -> used_defs(As); + {nosplit, _, A} -> used_defs(A); + {'case', _, A} -> used_defs(A) end. +-spec bottom_up(Fun, fexpr()) -> fexpr() when + Fun :: fun((expr_env(), fexpr()) -> fexpr()). bottom_up(F, Expr) -> bottom_up(F, #{}, Expr). +-spec bottom_up(Fun, expr_env(), fexpr()) -> fexpr() when + Fun :: fun((expr_env(), fexpr()) -> fexpr()). bottom_up(F, Env, Expr) -> F(Env, case Expr of - {lit, _} -> Expr; - nil -> Expr; - {var, _} -> Expr; - {def, D, Es} -> {def, D, [bottom_up(F, Env, E) || E <- Es]}; - {def_u, _, _} -> Expr; - {builtin, B, Es} -> {builtin, B, [bottom_up(F, Env, E) || E <- Es]}; - {builtin_u, _, _} -> Expr; + {lit, _, _} -> Expr; + {nil, _} -> Expr; + {var, _, _} -> Expr; + {def, FAnn, D, Es} -> {def, FAnn, D, [bottom_up(F, Env, E) || E <- Es]}; + {def_u, _, _, _} -> Expr; + {builtin, FAnn, B, Es} -> {builtin, FAnn, B, [bottom_up(F, Env, E) || E <- Es]}; {builtin_u, _, _, _} -> Expr; - {remote, ArgsT, RetT, Ct, Fun, Es} -> {remote, ArgsT, RetT, bottom_up(F, Env, Ct), Fun, [bottom_up(F, Env, E) || E <- Es]}; - {remote_u, ArgsT, RetT, Ct, Fun} -> {remote_u, ArgsT, RetT, bottom_up(F, Env, Ct), Fun}; - {con, Ar, I, Es} -> {con, Ar, I, [bottom_up(F, Env, E) || E <- Es]}; - {tuple, Es} -> {tuple, [bottom_up(F, Env, E) || E <- Es]}; - {proj, E, I} -> {proj, bottom_up(F, Env, E), I}; - {set_proj, R, I, E} -> {set_proj, bottom_up(F, Env, R), I, bottom_up(F, Env, E)}; - {op, Op, Es} -> {op, Op, [bottom_up(F, Env, E) || E <- Es]}; - {funcall, Fun, Es} -> {funcall, bottom_up(F, Env, Fun), [bottom_up(F, Env, E) || E <- Es]}; - {set_state, R, E} -> {set_state, R, bottom_up(F, Env, E)}; - {get_state, _} -> Expr; - {closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)}; - {switch, Split} -> {switch, bottom_up(F, Env, Split)}; - {lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)}; - {'let', X, E, Body} -> + {builtin_u, _, _, _, _} -> Expr; + {remote, FAnn, ArgsT, RetT, Ct, Fun, Es} -> {remote, FAnn, ArgsT, RetT, bottom_up(F, Env, Ct), Fun, [bottom_up(F, Env, E) || E <- Es]}; + {remote_u, FAnn, ArgsT, RetT, Ct, Fun} -> {remote_u, FAnn, ArgsT, RetT, bottom_up(F, Env, Ct), Fun}; + {con, FAnn, Ar, I, Es} -> {con, FAnn, Ar, I, [bottom_up(F, Env, E) || E <- Es]}; + {tuple, FAnn, Es} -> {tuple, FAnn, [bottom_up(F, Env, E) || E <- Es]}; + {proj, FAnn, E, I} -> {proj, FAnn, bottom_up(F, Env, E), I}; + {set_proj, FAnn, R, I, E} -> {set_proj, FAnn, bottom_up(F, Env, R), I, bottom_up(F, Env, E)}; + {op, FAnn, Op, Es} -> {op, FAnn, Op, [bottom_up(F, Env, E) || E <- Es]}; + {funcall, FAnn, Fun, Es} -> {funcall, FAnn, bottom_up(F, Env, Fun), [bottom_up(F, Env, E) || E <- Es]}; + {set_state, FAnn, R, E} -> {set_state, FAnn, R, bottom_up(F, Env, E)}; + {get_state, _, _} -> Expr; + {closure, FAnn, F, CEnv} -> {closure, FAnn, F, bottom_up(F, Env, CEnv)}; + {switch, FAnn, Split} -> {switch, FAnn, bottom_up(F, Env, Split)}; + {lam, FAnn, Xs, B} -> {lam, FAnn, Xs, bottom_up(F, Env, B)}; + {'let', FAnn, X, E, Body} -> E1 = bottom_up(F, Env, E), %% Always freshen user variables to avoid shadowing issues. ShouldFreshen = fun(Y = "%" ++ _) -> maps:is_key(Y, Env); @@ -1908,16 +2093,17 @@ bottom_up(F, Env, Expr) -> true -> Z = fresh_name_save(X), Env1 = Env#{ Z => E1 }, - {'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; + {'let', FAnn, Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; false -> Env1 = Env#{ X => E1 }, - {'let', X, E1, bottom_up(F, Env1, Body)} + {'let', FAnn, X, E1, bottom_up(F, Env1, Body)} end; {split, Type, X, Cases} -> {split, Type, X, [bottom_up(F, Env, Case) || Case <- Cases]}; - {nosplit, E} -> {nosplit, bottom_up(F, Env, E)}; + {nosplit, Rens, E} -> {nosplit, Rens, bottom_up(F, Env, E)}; {'case', Pat, Split} -> {'case', Pat, bottom_up(F, Env, Split)} end). +-spec get_named_args([aeso_syntax:named_arg_t()], [aeso_syntax:arg_expr()]) -> [aeso_syntax:expr()]. get_named_args(NamedArgsT, Args) -> IsNamed = fun({named_arg, _, _, _}) -> true; (_) -> false end, @@ -1925,6 +2111,7 @@ get_named_args(NamedArgsT, Args) -> NamedArgs = [get_named_arg(NamedArg, Named) || NamedArg <- NamedArgsT], NamedArgs ++ NotNamed. +-spec get_named_arg(aeso_syntax:named_arg_t(), [aeso_syntax:arg_expr()]) -> aeso_syntax:expr(). get_named_arg({named_arg_t, _, {id, _, Name}, _, Default}, Args) -> case [ Val || {named_arg, _, {id, _, X}, Val} <- Args, X == Name ] of [Val] -> Val; @@ -1933,38 +2120,41 @@ get_named_arg({named_arg_t, _, {id, _, Name}, _, Default}, Args) -> %% -- Renaming -- --spec rename([{var_name(), var_name()}], fexpr()) -> fexpr(). +-spec rename(rename(), fexpr()) -> fexpr(). rename(Ren, Expr) -> case Expr of - {lit, _} -> Expr; - nil -> nil; - {var, X} -> {var, rename_var(Ren, X)}; - {def, D, Es} -> {def, D, [rename(Ren, E) || E <- Es]}; - {def_u, _, _} -> Expr; - {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; - {builtin_u, _, _} -> Expr; - {builtin_u, _, _, _} -> Expr; - {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; - {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; - {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; - {tuple, Es} -> {tuple, [rename(Ren, E) || E <- Es]}; - {proj, E, I} -> {proj, rename(Ren, E), I}; - {set_proj, R, I, E} -> {set_proj, rename(Ren, R), I, rename(Ren, E)}; - {op, Op, Es} -> {op, Op, [rename(Ren, E) || E <- Es]}; - {funcall, Fun, Es} -> {funcall, rename(Ren, Fun), [rename(Ren, E) || E <- Es]}; - {set_state, R, E} -> {set_state, R, rename(Ren, E)}; - {get_state, _} -> Expr; - {closure, F, Env} -> {closure, F, rename(Ren, Env)}; - {switch, Split} -> {switch, rename_split(Ren, Split)}; - {lam, Xs, B} -> + {lit, _, _} -> Expr; + {nil, FAnn} -> {nil, FAnn}; + {var, FAnn, X} -> {var, FAnn, rename_var(Ren, X)}; + {def, FAnn, D, Es} -> {def, FAnn, D, [rename(Ren, E) || E <- Es]}; + {def_u, _, _, _} -> Expr; + {builtin, FAnn, B, Es} -> {builtin, FAnn, B, [rename(Ren, E) || E <- Es]}; + {builtin_u, _, _, _} -> Expr; + {builtin_u, _, _, _, _} -> Expr; + {remote, FAnn, ArgsT, RetT, Ct, F, Es} -> {remote, FAnn, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; + {remote_u, FAnn, ArgsT, RetT, Ct, F} -> {remote_u, FAnn, ArgsT, RetT, rename(Ren, Ct), F}; + {con, FAnn, Ar, I, Es} -> {con, FAnn, Ar, I, [rename(Ren, E) || E <- Es]}; + {tuple, FAnn, Es} -> {tuple, FAnn, [rename(Ren, E) || E <- Es]}; + {proj, FAnn, E, I} -> {proj, FAnn, rename(Ren, E), I}; + {set_proj, FAnn, R, I, E} -> {set_proj, FAnn, rename(Ren, R), I, rename(Ren, E)}; + {op, FAnn, Op, Es} -> {op, FAnn, Op, [rename(Ren, E) || E <- Es]}; + {funcall, FAnn, Fun, Es} -> {funcall, FAnn, rename(Ren, Fun), [rename(Ren, E) || E <- Es]}; + {set_state, FAnn, R, E} -> {set_state, FAnn, R, rename(Ren, E)}; + {get_state, _, _} -> Expr; + {closure, FAnn, F, Env} -> {closure, FAnn, F, rename(Ren, Env)}; + {switch, FAnn, Split} -> {switch, FAnn, rename_split(Ren, Split)}; + {lam, FAnn, Xs, B} -> {Zs, Ren1} = rename_bindings(Ren, Xs), - {lam, Zs, rename(Ren1, B)}; - {'let', X, E, Body} -> + {lam, FAnn, Zs, rename(Ren1, B)}; + {'let', FAnn, X, E, Body} -> {Z, Ren1} = rename_binding(Ren, X), - {'let', Z, rename(Ren, E), rename(Ren1, Body)} + {'let', FAnn, Z, rename(Ren, E), rename(Ren1, Body)} end. +-spec rename_var(rename(), var_name()) -> var_name(). rename_var(Ren, X) -> proplists:get_value(X, Ren, X). + +-spec rename_binding(rename(), var_name()) -> {var_name(), rename()}. rename_binding(Ren, X) -> Ren1 = lists:keydelete(X, 1, Ren), case lists:keymember(X, 2, Ren) of @@ -1974,18 +2164,21 @@ rename_binding(Ren, X) -> {Z, [{X, Z} | Ren1]} end. +-spec rename_bindings(rename(), [var_name()]) -> {[var_name()], rename()}. rename_bindings(Ren, []) -> {[], Ren}; rename_bindings(Ren, [X | Xs]) -> {Z, Ren1} = rename_binding(Ren, X), {Zs, Ren2} = rename_bindings(Ren1, Xs), {[Z | Zs], Ren2}. +-spec rename_fpats(rename(), [fpat()]) -> {[fpat()], rename()}. rename_fpats(Ren, []) -> {[], Ren}; rename_fpats(Ren, [P | Ps]) -> {Q, Ren1} = rename_fpat(Ren, P), {Qs, Ren2} = rename_fpats(Ren1, Ps), {[Q | Qs], Ren2}. +-spec rename_fpat(rename(), fpat()) -> {fpat(), rename()}. rename_fpat(Ren, P = {bool, _}) -> {P, Ren}; rename_fpat(Ren, P = {int, _}) -> {P, Ren}; rename_fpat(Ren, P = {string, _}) -> {P, Ren}; @@ -2004,6 +2197,7 @@ rename_fpat(Ren, {tuple, Ps}) -> {Ps1, Ren1} = rename_fpats(Ren, Ps), {{tuple, Ps1}, Ren1}. +-spec rename_spat(rename(), fsplit_pat()) -> {fsplit_pat(), rename()}. rename_spat(Ren, P = {bool, _}) -> {P, Ren}; rename_spat(Ren, P = {int, _}) -> {P, Ren}; rename_spat(Ren, P = {string, _}) -> {P, Ren}; @@ -2026,23 +2220,32 @@ rename_spat(Ren, {assign, X, P}) -> {P1, Ren2} = rename_binding(Ren1, P), {{assign, X1, P1}, Ren2}. +-spec rename_split(rename(), fsplit()) -> fsplit(). rename_split(Ren, {split, Type, X, Cases}) -> {split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]}; -rename_split(Ren, {nosplit, E}) -> {nosplit, rename(Ren, E)}. +rename_split(Ren, {nosplit, Rens, E}) -> + {nosplit, update_rename(Rens, Ren), rename(Ren, E)}. +-spec rename_case(rename(), fcase()) -> fcase(). rename_case(Ren, {'case', Pat, Split}) -> {Pat1, Ren1} = rename_spat(Ren, Pat), {'case', Pat1, rename_split(Ren1, Split)}. +-spec update_rename(rename(), rename()) -> rename(). +update_rename(OldRen, NewRen) -> + [{Name, proplists:get_value(Rename, NewRen, Rename)} || {Name, Rename} <- OldRen]. + %% -- Records -- -field_index({typed, _, _, RecTy}, X) -> - field_index(RecTy, X); +-spec field_index(aeso_syntax:typedef(), aeso_syntax:name()) -> integer(). field_index({record_t, Fields}, X) -> IsX = fun({field_t, _, {id, _, Y}, _}) -> X == Y end, [I] = [ I || {I, Field} <- indexed(Fields), IsX(Field) ], I - 1. %% Tuples are 0-indexed +-spec field_value(aeso_syntax:field_t(), [aeso_syntax:field(aeso_syntax:pat())]) -> Res when + Res :: {upd, aeso_syntax:name(), Expr} | {set, Expr} | false, + Expr :: aeso_syntax:expr(). field_value({field_t, _, {id, _, X}, _}, Fields) -> View = fun({field, _, [{proj, _, {id, _, Y}}], E}) -> {Y, {set, E}}; ({field_upd, _, [{proj, _, {id, _, Y}}], @@ -2054,43 +2257,58 @@ field_value({field_t, _, {id, _, X}, _}, Fields) -> %% -- Attributes -- +-spec get_attributes(aeso_syntax:ann()) -> [stateful | payable | private]. get_attributes(Ann) -> [stateful || proplists:get_value(stateful, Ann, false)] ++ [payable || proplists:get_value(payable, Ann, false)] ++ [private || not proplists:get_value(entrypoint, Ann, false)]. +-spec get_attributes_debug(aeso_syntax:ann()) -> [stateful | payable | private | fann()]. +get_attributes_debug(Ann) -> + get_attributes(Ann) ++ to_fann(Ann). + %% -- Basic utilities -- +-spec indexed([term()]) -> [{integer(), term()}]. indexed(Xs) -> lists:zip(lists:seq(1, length(Xs)), Xs). +-spec setnth(integer(), Val, Vals) -> Vals when + Val :: term(), + Vals :: [Val]. setnth(I, X, Xs) -> {Ys, [_ | Zs]} = lists:split(I - 1, Xs), Ys ++ [X] ++ Zs. -dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}). +-spec fcode_error(string()) -> no_return(). fcode_error(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)). +-spec internal_error(string()) -> no_return(). internal_error(Error) -> Msg = lists:flatten(io_lib:format("~p\n", [Error])), aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)). %% -- Pretty printing -------------------------------------------------------- +-spec format_fcode(fcode()) -> string(). format_fcode(#{ functions := Funs }) -> prettypr:format(format_funs(Funs)). +-spec format_funs(functions()) -> prettypr:document(). format_funs(Funs) -> pp_above( [ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]). +-spec format_fexpr(fexpr()) -> string(). format_fexpr(E) -> prettypr:format(pp_fexpr(E)). +-spec pp_fun(fun_name(), fun_def()) -> prettypr:document(). pp_fun(Name, #{ args := Args, return := Return, body := Body }) -> PPArg = fun({X, T}) -> pp_beside([pp_text(X), pp_text(" : "), pp_ftype(T)]) end, pp_above(pp_beside([pp_text("function "), pp_fun_name(Name), @@ -2098,85 +2316,101 @@ pp_fun(Name, #{ args := Args, return := Return, body := Body }) -> pp_text(" : "), pp_ftype(Return), pp_text(" =")]), prettypr:nest(2, pp_fexpr(Body))). +-spec pp_fun_name(fun_name()) -> prettypr:document(). pp_fun_name(event) -> pp_text(event); pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E)); pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")). +-spec pp_text(binary() | string() | atom() | integer()) -> prettypr:document(). pp_text(<<>>) -> prettypr:text("\"\""); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)); pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)). +-spec pp_int(integer()) -> prettypr:document(). pp_int(I) -> prettypr:text(integer_to_list(I)). +-spec pp_beside([prettypr:document()]) -> prettypr:document(). pp_beside([]) -> prettypr:empty(); pp_beside([X]) -> X; pp_beside([X | Xs]) -> pp_beside(X, pp_beside(Xs)). +-spec pp_beside(prettypr:document(), prettypr:document()) -> prettypr:document(). pp_beside(A, B) -> prettypr:beside(A, B). +-spec pp_above([prettypr:document()]) -> prettypr:document(). pp_above([]) -> prettypr:empty(); pp_above([X]) -> X; pp_above([X | Xs]) -> pp_above(X, pp_above(Xs)). +-spec pp_above(prettypr:document(), prettypr:document()) -> prettypr:document(). pp_above(A, B) -> prettypr:above(A, B). +-spec pp_parens(prettypr:document()) -> prettypr:document(). pp_parens(Doc) -> pp_beside([pp_text("("), Doc, pp_text(")")]). + +-spec pp_braces(prettypr:document()) -> prettypr:document(). pp_braces(Doc) -> pp_beside([pp_text("{"), Doc, pp_text("}")]). +-spec pp_punctuate(prettypr:document(), [prettypr:document()]) -> [prettypr:document()]. pp_punctuate(_Sep, []) -> []; pp_punctuate(_Sep, [X]) -> [X]; pp_punctuate(Sep, [X | Xs]) -> [pp_beside(X, Sep) | pp_punctuate(Sep, Xs)]. +-spec pp_par([prettypr:document()]) -> prettypr:document(). pp_par([]) -> prettypr:empty(); pp_par(Xs) -> prettypr:par(Xs). -pp_fexpr({lit, {typerep, T}}) -> + +-spec pp_fexpr(fexpr()) -> prettypr:document(). +pp_fexpr({lit, _, {typerep, T}}) -> pp_ftype(T); -pp_fexpr({lit, {Tag, Lit}}) -> +pp_fexpr({lit, _, {contract_code, Contract}}) -> + pp_beside(pp_text("contract "), pp_text(Contract)); +pp_fexpr({lit, _, {Tag, Lit}}) -> aeso_pretty:expr({Tag, [], Lit}); -pp_fexpr(nil) -> +pp_fexpr({nil, _}) -> pp_text("[]"); -pp_fexpr({var, X}) -> pp_text(X); +pp_fexpr({var, _, X}) -> pp_text(X); pp_fexpr({def, Fun}) -> pp_fun_name(Fun); -pp_fexpr({def_u, Fun, Ar}) -> +pp_fexpr({def_u, _, Fun, Ar}) -> pp_beside([pp_fun_name(Fun), pp_text("/"), pp_int(Ar)]); -pp_fexpr({def, Fun, Args}) -> +pp_fexpr({def, _, Fun, Args}) -> pp_call(pp_fun_name(Fun), Args); -pp_fexpr({con, _, I, []}) -> +pp_fexpr({con, _, _, I, []}) -> pp_beside(pp_text("C"), pp_int(I)); -pp_fexpr({con, _, I, Es}) -> - pp_beside(pp_fexpr({con, [], I, []}), - pp_fexpr({tuple, Es})); -pp_fexpr({tuple, Es}) -> +pp_fexpr({con, FAnn, _, I, Es}) -> + pp_beside(pp_fexpr({con, FAnn, [], I, []}), + pp_fexpr({tuple, FAnn, Es})); +pp_fexpr({tuple, _, Es}) -> pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es]))); -pp_fexpr({proj, E, I}) -> +pp_fexpr({proj, _, E, I}) -> pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]); -pp_fexpr({lam, Xs, A}) -> - pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"), +pp_fexpr({lam, FAnn, Xs, A}) -> + pp_par([pp_fexpr({tuple, FAnn, [{var, FAnn, X} || X <- Xs]}), pp_text("=>"), prettypr:nest(2, pp_fexpr(A))]); -pp_fexpr({closure, Fun, ClEnv}) -> +pp_fexpr({closure, _, Fun, ClEnv}) -> FVs = case ClEnv of - {tuple, Xs} -> Xs; - {var, _} -> [ClEnv] + {tuple, _, Xs} -> Xs; + {var, _, _} -> [ClEnv] end, pp_call(pp_text("__CLOSURE__"), [{def, Fun} | FVs]); -pp_fexpr({set_proj, E, I, A}) -> +pp_fexpr({set_proj, _, E, I, A}) -> pp_beside(pp_fexpr(E), pp_braces(pp_beside([pp_int(I), pp_text(" = "), pp_fexpr(A)]))); -pp_fexpr({op, Op, [A, B] = Args}) -> +pp_fexpr({op, _, Op, [A, B] = Args}) -> case is_infix(Op) of false -> pp_call(pp_text(Op), Args); true -> pp_parens(pp_par([pp_fexpr(A), pp_text(Op), pp_fexpr(B)])) end; -pp_fexpr({op, Op, [A] = Args}) -> +pp_fexpr({op, _, Op, [A] = Args}) -> case is_infix(Op) of false -> pp_call(pp_text(Op), Args); true -> pp_parens(pp_par([pp_text(Op), pp_fexpr(A)])) end; -pp_fexpr({op, Op, As}) -> - pp_beside(pp_text(Op), pp_fexpr({tuple, As})); -pp_fexpr({'let', _, _, _} = Expr) -> - Lets = fun Lets({'let', Y, C, D}) -> +pp_fexpr({op, FAnn, Op, As}) -> + pp_beside(pp_text(Op), pp_fexpr({tuple, FAnn, As})); +pp_fexpr({'let', _, _, _, _} = Expr) -> + Lets = fun Lets({'let', _, Y, C, D}) -> {Ls, E} = Lets(D), {[{Y, C} | Ls], E}; Lets(E) -> {[], E} end, @@ -2187,29 +2421,29 @@ pp_fexpr({'let', _, _, _} = Expr) -> pp_above([ pp_par([pp_text(X), pp_text("="), prettypr:nest(2, pp_fexpr(A))]) || {X, A} <- Ls ]), pp_text(" in ") ]), pp_fexpr(Body) ])); -pp_fexpr({builtin_u, B, N}) -> +pp_fexpr({builtin_u, _, B, N}) -> pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); -pp_fexpr({builtin_u, B, N, TypeArgs}) -> - pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]); -pp_fexpr({builtin, B, As}) -> +pp_fexpr({builtin_u, FAnn, B, N, TypeArgs}) -> + pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, FAnn, TypeArgs}), pp_text("/"), pp_text(N)]); +pp_fexpr({builtin, _, B, As}) -> pp_call(pp_text(B), As); -pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> +pp_fexpr({remote_u, _, ArgsT, RetT, Ct, Fun}) -> pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})]); -pp_fexpr({remote, ArgsT, RetT, Ct, Fun, As}) -> +pp_fexpr({remote, _, ArgsT, RetT, Ct, Fun, As}) -> pp_call(pp_parens(pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})])), As); -pp_fexpr({funcall, Fun, As}) -> +pp_fexpr({funcall, _, Fun, As}) -> pp_call(pp_fexpr(Fun), As); -pp_fexpr({set_state, R, A}) -> - pp_call(pp_text("set_state"), [{lit, {int, R}}, A]); -pp_fexpr({get_state, R}) -> - pp_call(pp_text("get_state"), [{lit, {int, R}}]); -pp_fexpr({switch, Split}) -> pp_split(Split); -pp_fexpr({contract_code, Contract}) -> - pp_beside(pp_text("contract "), pp_text(Contract)). +pp_fexpr({set_state, FAnn, R, A}) -> + pp_call(pp_text("set_state"), [{lit, FAnn, {int, R}}, A]); +pp_fexpr({get_state, FAnn, R}) -> + pp_call(pp_text("get_state"), [{lit, FAnn, {int, R}}]); +pp_fexpr({switch, _, Split}) -> pp_split(Split). +-spec pp_call(prettypr:document(), [fexpr()]) -> prettypr:document(). pp_call(Fun, Args) -> - pp_beside(Fun, pp_fexpr({tuple, Args})). + pp_beside(Fun, pp_fexpr({tuple, [], Args})). +-spec pp_call_t(string(), [ftype()]) -> prettypr:document(). pp_call_t(Fun, Args) -> pp_beside(pp_text(Fun), pp_ftype({tuple, Args})). @@ -2217,7 +2451,7 @@ pp_call_t(Fun, Args) -> pp_ftype(T) when is_atom(T) -> pp_text(T); pp_ftype(any) -> pp_text("_"); pp_ftype({tvar, X}) -> pp_text(X); -pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, {int, N}}]); +pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, [], {int, N}}]); pp_ftype({oracle, Q, R}) -> pp_call_t("oracle", [Q, R]); pp_ftype({tuple, Ts}) -> pp_parens(pp_par(pp_punctuate(pp_text(" *"), [pp_ftype(T) || T <- Ts]))); @@ -2231,27 +2465,36 @@ pp_ftype({variant, Cons}) -> pp_par( pp_punctuate(pp_text(" |"), [ case Args of - [] -> pp_fexpr({con, [], I - 1, []}); - _ -> pp_beside(pp_fexpr({con, [], I - 1, []}), pp_ftype({tuple, Args})) - end || {I, Args} <- indexed(Cons)])). + [] -> pp_fexpr({con, [], [], I - 1, []}); + _ -> pp_beside(pp_fexpr({con, [], [], I - 1, []}), pp_ftype({tuple, Args})) + end || {I, Args} <- indexed(Cons)])); +pp_ftype([]) -> + %% NOTE: This could happen with `{typerep, []}` since `[]` is not a ftype(). + %% TODO: It would be better to make sure that `{typerep, []}` does not arrive here. + pp_text("[]"). -pp_split({nosplit, E}) -> pp_fexpr(E); +-spec pp_split(fsplit()) -> prettypr:document(). +pp_split({nosplit, _, E}) -> pp_fexpr(E); pp_split({split, Type, X, Alts}) -> pp_above([pp_beside([pp_text("switch("), pp_text(X), pp_text(" : "), pp_ftype(Type), pp_text(")")])] ++ [prettypr:nest(2, pp_case(Alt)) || Alt <- Alts]). +-spec pp_case(fcase()) -> prettypr:document(). pp_case({'case', Pat, Split}) -> prettypr:sep([pp_beside(pp_pat(Pat), pp_text(" =>")), prettypr:nest(2, pp_split(Split))]). -pp_pat({tuple, Xs}) -> pp_fexpr({tuple, [{var, X} || X <- Xs]}); -pp_pat({'::', X, Xs}) -> pp_fexpr({op, '::', [{var, X}, {var, Xs}]}); -pp_pat({con, As, I, Xs}) -> pp_fexpr({con, As, I, [{var, X} || X <- Xs]}); -pp_pat({var, X}) -> pp_fexpr({var, X}); +-spec pp_pat(fsplit_pat()) -> prettypr:document(). +pp_pat({tuple, Xs}) -> pp_fexpr({tuple, [], [{var, [], X} || X <- Xs]}); +pp_pat({'::', X, Xs}) -> pp_fexpr({op, [], '::', [{var, [], X}, {var, [], Xs}]}); +pp_pat({con, As, I, Xs}) -> pp_fexpr({con, [], As, I, [{var, [], X} || X <- Xs]}); +pp_pat({var, X}) -> pp_fexpr({var, [], X}); pp_pat(P = {Tag, _}) when Tag == bool; Tag == int; Tag == string - -> pp_fexpr({lit, P}); -pp_pat(Pat) -> pp_fexpr(Pat). + -> pp_fexpr({lit, [], P}); +pp_pat(nil) -> pp_fexpr({nil, []}); +pp_pat({assign, X, Y}) -> pp_beside([pp_text(X), pp_text(" = "), pp_text(Y)]). +-spec is_infix(op()) -> boolean(). is_infix(Op) -> C = hd(atom_to_list(Op)), C < $a orelse C > $z. diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 03fbfc4..8cf9af4 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -13,6 +13,8 @@ , file/2 , from_string/2 , check_call/4 + , decode_value/4 + , encode_value/4 , create_calldata/3 , create_calldata/4 , version/0 @@ -41,6 +43,7 @@ | {include, {file_system, [string()]} | {explicit_files, #{string() => binary()}}} | {src_file, string()} + | {src_dir, string()} | {aci, aeso_aci:aci_type()}. -type options() :: [option()]. @@ -86,7 +89,9 @@ file(Filename) -> file(File, Options0) -> Options = add_include_path(File, Options0), case read_contract(File) of - {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); + {ok, Bin} -> + SrcDir = aeso_utils:canonical_dir(filename:dirname(File)), + from_string(Bin, [{src_file, File}, {src_dir, SrcDir} | Options]); {error, Error} -> Msg = lists:flatten([File,": ",file:format_error(Error)]), {error, [aeso_errors:new(file_error, Msg)]} @@ -98,7 +103,7 @@ add_include_path(File, Options) -> false -> Dir = filename:dirname(File), {ok, Cwd} = file:get_cwd(), - [{include, {file_system, [Cwd, Dir]}} | Options] + [{include, {file_system, [Cwd, aeso_utils:canonical_dir(Dir)]}} | Options] end. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}. @@ -118,7 +123,7 @@ from_string1(ContractString, Options) -> , warnings := Warnings } = string_to_code(ContractString, Options), #{ child_con_env := ChildContracts } = FCodeEnv, SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}), - {FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), + FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), pp_assembler(FateCode, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = version(), @@ -131,13 +136,7 @@ from_string1(ContractString, Options) -> payable => maps:get(payable, FCode), warnings => Warnings }, - 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)}. + {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}. maybe_generate_aci(Result, FoldedTypedAst, Options) -> case proplists:get_value(aci, Options) of @@ -189,30 +188,58 @@ check_call(Source, FunName, Args, Options) -> check_call1(Source, FunName, Args, Options). check_call1(ContractString0, FunName, Args, Options) -> + case add_extra_call(ContractString0, {call, FunName, Args}, Options) of + {ok, CallName, Code} -> + {def, _, _, FcodeArgs} = get_call_body(CallName, Code), + {ok, FunName, [ aeso_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]}; + Err = {error, _} -> + Err + end. + +add_extra_call(Contract0, Call, Options) -> try %% First check the contract without the __call function #{fcode := OrgFcode , fcode_env := #{child_con_env := ChildContracts} - , ast := Ast} = string_to_code(ContractString0, Options), - {FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []), + , ast := Ast} = string_to_code(Contract0, Options), + 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, lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)), - ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args), - #{fcode := Fcode} = string_to_code(ContractString, Options), - CallArgs = arguments_of_body(CallName, FunName, Fcode), - - {ok, FunName, CallArgs} + Contract = insert_call_function(Ast, Contract0, CallName, Call), + {ok, CallName, string_to_code(Contract, Options)} catch throw:{error, Errors} -> {error, Errors} end. -arguments_of_body(CallName, _FunName, Fcode) -> +get_call_body(CallName, #{fcode := Fcode}) -> #{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)), - {def, _FName, Args} = Body, - %% FName is either {entrypoint, list_to_binary(FunName)} or 'init' - [ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ]. + Body. + +encode_value(Contract0, Type, Value, Options) -> + case add_extra_call(Contract0, {value, Type, Value}, Options) of + {ok, CallName, Code} -> + Body = get_call_body(CallName, Code), + {ok, aeb_fate_encoding:serialize(aeso_fcode_to_fate:term_to_fate(Body))}; + Err = {error, _} -> + Err + end. + +decode_value(Contract0, Type, FateValue, Options) -> + case add_extra_call(Contract0, {type, Type}, Options) of + {ok, CallName, Code} -> + #{ folded_typed_ast := TypedAst + , type_env := TypeEnv} = Code, + {ok, _, Type0} = get_decode_type(CallName, TypedAst), + Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types ]), + fate_data_to_sophia_value(Type0, Type1, FateValue); + Err = {error, _} -> + Err + end. first_none_match(_CallName, _Hashes, []) -> error(unable_to_find_unique_call_name); @@ -225,14 +252,31 @@ first_none_match(CallName, Hashes, [Char|Chars]) -> end. %% Add the __call function to a contract. --spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string(). -insert_call_function(Ast, Code, Call, FunName, Args) -> +-spec insert_call_function(aeso_syntax:ast(), string(), string(), + {call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string(). +insert_call_function(Ast, Code, Call, {call, FunName, Args}) -> Ind = last_contract_indent(Ast), lists:flatten( [ Code, "\n\n", lists:duplicate(Ind, " "), "stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n" + ]); +insert_call_function(Ast, Code, Call, {value, Type, Value}) -> + Ind = last_contract_indent(Ast), + lists:flatten( + [ Code, + "\n\n", + lists:duplicate(Ind, " "), + "entrypoint ", Call, "() : ", Type, " = ", Value, "\n" + ]); +insert_call_function(Ast, Code, Call, {type, Type}) -> + Ind = last_contract_indent(Ast), + lists:flatten( + [ Code, + "\n\n", + lists:duplicate(Ind, " "), + "entrypoint ", Call, "(val : ", Type, ") : ", Type, " = val\n" ]). -spec insert_init_function(string(), options()) -> string(). @@ -271,26 +315,32 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, _, Type0} = get_decode_type(FunName, TypedAst), - Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types]), - try - {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} - catch throw:cannot_translate_to_sophia -> - Type1 = prettypr:format(aeso_pretty:type(Type0)), - Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s", - [aeb_fate_encoding:deserialize(Data), Type1]), - {error, [aeso_errors:new(data_error, Msg)]}; - _:_ -> - Type1 = prettypr:format(aeso_pretty:type(Type0)), - Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]), - {error, [aeso_errors:new(data_error, Msg)]} - end + fate_data_to_sophia_value(Type0, Type, Data) catch throw:{error, Errors} -> {error, Errors} end. +fate_data_to_sophia_value(Type, UnfoldedType, FateData) -> + try + {ok, aeso_vm_decode:from_fate(UnfoldedType, aeb_fate_encoding:deserialize(FateData))} + catch throw:cannot_translate_to_sophia -> + Type1 = prettypr:format(aeso_pretty:type(Type)), + Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s", + [aeb_fate_encoding:deserialize(FateData), Type1]), + {error, [aeso_errors:new(data_error, Msg)]}; + _:_ -> + Type1 = prettypr:format(aeso_pretty:type(Type)), + Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]), + {error, [aeso_errors:new(data_error, Msg)]} + end. + -spec create_calldata(string(), string(), [string()]) -> {ok, binary()} | {error, [aeso_errors:error()]}. create_calldata(Code, Fun, Args) -> @@ -317,14 +367,17 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, Args, _} = get_decode_type(FunName, TypedAst), GetType = fun({typed, _, _, T}) -> T; (T) -> T end, ArgTypes = lists:map(GetType, Args), Type0 = {tuple_t, [], ArgTypes}, %% user defined data types such as variants needed to match against - Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), + Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, + [ unfold_record_types + , unfold_variant_types + , not_unfold_system_alias_types]), case aeb_fate_abi:decode_calldata(FunName, Calldata) of {ok, FateArgs} -> try diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 285b957..966fc2e 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -53,7 +53,8 @@ tailpos = true, child_contracts = #{}, saved_fresh_names = #{}, - options = [] }). + options = [], + debug_info = false }). %% -- Debugging -------------------------------------------------------------- @@ -82,24 +83,16 @@ code_error(Err) -> 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, SavedFreshNames, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(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()}. + case proplists:get_value(include_child_contract_symbols, Options, false) of + false -> FateCode; + true -> add_child_symbols(ChildContracts, FateCode) + end. make_function_id(X) -> aeb_fate_code:symbol_identifier(make_function_name(X)). @@ -124,31 +117,15 @@ functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Opt 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. + Attrs = [ A || A <- Attrs0, A == private orelse A == payable ], 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 ], + ArgsNames = [ X || {X, _} <- lists:reverse(Env#env.vars) ], + + %% DBG_LOC is added before the function body to make it possible to break + %% at the function signature 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}). + DbgSCode = dbg_contract(Env) ++ dbg_loc(Env, Attrs0) ++ dbg_scoped_vars(Env, ArgsNames, SCode), + {Attrs, {ArgTypes, ResType1}, DbgSCode}. -define(tvars, '$tvars'). @@ -195,20 +172,20 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). %% -- Environment functions -- 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, - saved_fresh_names = SavedFreshNames }. + #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], + contract = ContractName, + child_contracts = ChildContracts, + locals = FunNames, + current_function = Name, + options = Options, + tailpos = true, + saved_fresh_names = SavedFreshNames, + debug_info = proplists:get_value(debug_info, Options, false) }. next_var(#env{ vars = Vars }) -> 1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]). 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) -> @@ -235,7 +212,7 @@ serialize_contract_code(Env, C) -> 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), + FateCode = compile(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, ""), @@ -259,6 +236,7 @@ lit_to_fate(Env, L) -> {bytes, B} -> aeb_fate_data:make_bytes(B); {bool, B} -> aeb_fate_data:make_boolean(B); {account_pubkey, K} -> aeb_fate_data:make_address(K); + {signature, S} -> aeb_fate_data:make_bytes(S); {contract_pubkey, K} -> aeb_fate_data:make_contract(K); {oracle_pubkey, K} -> aeb_fate_data:make_oracle(K); {oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H); @@ -269,97 +247,106 @@ lit_to_fate(Env, L) -> term_to_fate(E) -> term_to_fate(#env{}, #{}, E). term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E). -term_to_fate(GlobEnv, _Env, {lit, L}) -> +term_to_fate(GlobEnv, _Env, {lit, _, L}) -> lit_to_fate(GlobEnv, L); %% negative literals are parsed as 0 - N -term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> +term_to_fate(_GlobEnv, _Env, {op, _, '-', [{lit, _, {int, 0}}, {lit, _, {int, N}}]}) -> aeb_fate_data:make_integer(-N); -term_to_fate(_GlobEnv, _Env, nil) -> +term_to_fate(_GlobEnv, _Env, {nil, _}) -> aeb_fate_data:make_list([]); -term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) -> +term_to_fate(GlobEnv, Env, {op, _, '::', [Hd, Tl]}) -> %% The Tl will translate into a list, because FATE lists are just lists [term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)]; -term_to_fate(GlobEnv, Env, {tuple, As}) -> +term_to_fate(GlobEnv, Env, {tuple, _, As}) -> aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As])); -term_to_fate(GlobEnv, Env, {con, Ar, I, As}) -> +term_to_fate(GlobEnv, Env, {con, _, Ar, I, As}) -> FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ], aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); -term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, bits_all, []}) -> aeb_fate_data:make_bits(-1); -term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, bits_none, []}) -> aeb_fate_data:make_bits(0); -term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) -> +term_to_fate(GlobEnv, _Env, {op, _, bits_set, [B, I]}) -> {bits, N} = term_to_fate(GlobEnv, B), J = term_to_fate(GlobEnv, I), {bits, N bor (1 bsl J)}; -term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) -> +term_to_fate(GlobEnv, _Env, {op, _, bits_clear, [B, I]}) -> {bits, N} = term_to_fate(GlobEnv, B), J = term_to_fate(GlobEnv, I), {bits, N band bnot (1 bsl J)}; -term_to_fate(GlobEnv, Env, {'let', X, E, Body}) -> +term_to_fate(GlobEnv, Env, {'let', _, X, E, Body}) -> Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) }, term_to_fate(GlobEnv, Env1, Body); -term_to_fate(_GlobEnv, Env, {var, X}) -> +term_to_fate(_GlobEnv, Env, {var, _, X}) -> case maps:get(X, Env, undefined) of undefined -> throw(not_a_fate_value); V -> V end; -term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) -> aeb_fate_data:make_map(#{}); -term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) -> +term_to_fate(GlobEnv, Env, {op, _, map_set, [M, K, V]}) -> Map = term_to_fate(GlobEnv, Env, M), Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)}; +term_to_fate(GlobEnv, Env, {builtin, _, bytes_to_any_size, [Bs]}) -> + term_to_fate(GlobEnv, Env, Bs); term_to_fate(_GlobEnv, _Env, _) -> throw(not_a_fate_value). to_scode(Env, T) -> try term_to_fate(Env, T) of - V -> [push(?i(V))] + V -> + FAnn = element(2, T), + [dbg_loc(Env, FAnn), push(?i(V))] catch throw:not_a_fate_value -> to_scode1(Env, T) end. -to_scode1(Env, {lit, L}) -> - [push(?i(lit_to_fate(Env, L)))]; +to_scode1(Env, {lit, Ann, L}) -> + [ dbg_loc(Env, Ann), push(?i(lit_to_fate(Env, L))) ]; -to_scode1(_Env, nil) -> - [aeb_fate_ops:nil(?a)]; +to_scode1(Env, {nil, Ann}) -> + [ dbg_loc(Env, Ann), aeb_fate_ops:nil(?a) ]; -to_scode1(Env, {var, X}) -> - [push(lookup_var(Env, X))]; +to_scode1(Env, {var, Ann, X}) -> + [ dbg_loc(Env, Ann), push(lookup_var(Env, X)) ]; -to_scode1(Env, {con, Ar, I, As}) -> +to_scode1(Env, {con, Ann, Ar, I, As}) -> N = length(As), - [[to_scode(notail(Env), A) || A <- As], - aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N))]; + [ dbg_loc(Env, Ann), + [to_scode(notail(Env), A) || A <- As], + aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N)) ]; -to_scode1(Env, {tuple, As}) -> +to_scode1(Env, {tuple, Ann, As}) -> N = length(As), - [[ to_scode(notail(Env), A) || A <- As ], - tuple(N)]; + [ dbg_loc(Env, Ann), + [ to_scode(notail(Env), A) || A <- As ], + tuple(N) ]; -to_scode1(Env, {proj, E, I}) -> - [to_scode(notail(Env), E), - aeb_fate_ops:element_op(?a, ?i(I), ?a)]; +to_scode1(Env, {proj, Ann, E, I}) -> + [ dbg_loc(Env, Ann), + to_scode(notail(Env), E), + aeb_fate_ops:element_op(?a, ?i(I), ?a) ]; -to_scode1(Env, {set_proj, R, I, E}) -> - [to_scode(notail(Env), E), - to_scode(notail(Env), R), - aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a)]; +to_scode1(Env, {set_proj, Ann, R, I, E}) -> + [ dbg_loc(Env, Ann), + to_scode(notail(Env), E), + to_scode(notail(Env), R), + aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a) ]; -to_scode1(Env, {op, Op, Args}) -> - call_to_scode(Env, op_to_scode(Op), Args); +to_scode1(Env, {op, Ann, Op, Args}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, op_to_scode(Op), Args) ]; -to_scode1(Env, {'let', X, {var, Y}, Body}) -> +to_scode1(Env, {'let', Ann, X, {var, _, Y}, Body}) -> Env1 = bind_var(X, lookup_var(Env, Y), Env), - to_scode(Env1, Body); -to_scode1(Env, {'let', X, Expr, Body}) -> + [ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], to_scode(Env1, Body)) ]; +to_scode1(Env, {'let', Ann, X, Expr, Body}) -> {I, Env1} = bind_local(X, Env), - [ to_scode(notail(Env), Expr), - aeb_fate_ops:store({var, I}, {stack, 0}), - to_scode(Env1, Body) ]; + SCode = [ to_scode(notail(Env), Expr), + aeb_fate_ops:store({var, I}, {stack, 0}), + to_scode(Env1, Body) ], + [ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], SCode) ]; -to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) -> +to_scode1(Env = #env{ current_function = Fun, tailpos = true, debug_info = false }, {def, Ann, Fun, Args}) -> %% Tail-call to current function, f(e0..en). Compile to %% [ let xi = ei ] %% [ STORE argi xi ] @@ -372,61 +359,62 @@ to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args} aeb_fate_ops:store({var, I}, ?a)], {[I | Is], Acc1, Env2} end, {[], [], Env}, Args), - [ Code, + [ dbg_loc(Env, Ann), + Code, [ aeb_fate_ops:store({arg, I}, {var, J}) || {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1), lists:reverse(Vars)) ], loop ]; -to_scode1(Env, {def, Fun, Args}) -> +to_scode1(Env, {def, Ann, Fun, Args}) -> FName = make_function_id(Fun), Lbl = aeb_fate_data:make_string(FName), - call_to_scode(Env, local_call(Env, ?i(Lbl)), Args); -to_scode1(Env, {funcall, Fun, Args}) -> - call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args); + [ dbg_loc(Env, Ann) | call_to_scode(Env, local_call(Env, ?i(Lbl)), Args) ]; +to_scode1(Env, {funcall, Ann, Fun, Args}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args) ]; -to_scode1(Env, {builtin, B, Args}) -> - builtin_to_scode(Env, B, Args); +to_scode1(Env, {builtin, Ann, B, Args}) -> + [ dbg_loc(Env, Ann) | builtin_to_scode(Env, B, Args) ]; -to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> +to_scode1(Env, {remote, Ann, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> Lbl = make_function_id(Fun), {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), - case Protected of - {lit, {bool, false}} -> + SCode = case Protected of + {lit, _, {bool, false}} -> case Gas of - {builtin, call_gas_left, _} -> + {builtin, _, call_gas_left, _} -> Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), call_to_scode(Env, Call, [Ct, Value | Args]); _ -> Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), call_to_scode(Env, Call, [Ct, Value, Gas | Args]) end; - {lit, {bool, true}} -> + {lit, _, {bool, true}} -> Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)), call_to_scode(Env, Call, [Ct, Value, Gas | Args]); _ -> Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a), call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args]) - end; + end, + [ dbg_loc(Env, Ann) | SCode ]; -to_scode1(_Env, {get_state, Reg}) -> - [push(?s(Reg))]; -to_scode1(Env, {set_state, Reg, Val}) -> - call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, - tuple(0)], [Val]); +to_scode1(Env, {get_state, Ann, Reg}) -> + [ dbg_loc(Env, Ann), push(?s(Reg)) ]; +to_scode1(Env, {set_state, Ann, Reg, Val}) -> + [ dbg_loc(Env, Ann) | call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, tuple(0)], [Val]) ]; -to_scode1(Env, {closure, Fun, FVs}) -> - to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]}); +to_scode1(Env, {closure, Ann, Fun, FVs}) -> + [ to_scode(Env, {tuple, Ann, [{lit, Ann, {string, make_function_id(Fun)}}, FVs]}) ]; -to_scode1(Env, {switch, Case}) -> - split_to_scode(Env, Case). +to_scode1(Env, {switch, Ann, Case}) -> + [ dbg_loc(Env, Ann) | split_to_scode(Env, Case) ]. -local_call( Env, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun); -local_call(_Env, Fun) -> aeb_fate_ops:call(Fun). +local_call( Env = #env{debug_info = false}, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun); +local_call(_Env, Fun) -> aeb_fate_ops:call(Fun). -split_to_scode(Env, {nosplit, Expr}) -> - [switch_body, to_scode(Env, Expr)]; +split_to_scode(Env, {nosplit, Renames, Expr}) -> + [switch_body, dbg_scoped_vars(Env, Renames, to_scode(Env, Expr))]; split_to_scode(Env, {split, {tuple, _}, X, Alts}) -> {Def, Alts1} = catchall_to_scode(Env, X, Alts), Arg = lookup_var(Env, X), @@ -555,6 +543,14 @@ builtin_to_scode(Env, bytes_concat, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args); builtin_to_scode(Env, bytes_split, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_split_any, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_split_any(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_fixed_size, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_to_fixed_size(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_any_size, [A]) -> + [to_scode(Env, A)]; %% no_op! +builtin_to_scode(Env, bytes_size, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_size(?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) -> @@ -576,6 +572,8 @@ builtin_to_scode(_Env, chain_difficulty, []) -> [aeb_fate_ops:difficulty(?a)]; builtin_to_scode(_Env, chain_gas_limit, []) -> [aeb_fate_ops:gaslimit(?a)]; +builtin_to_scode(_Env, chain_network_id, []) -> + [aeb_fate_ops:network_id(?a)]; builtin_to_scode(_Env, contract_balance, []) -> [aeb_fate_ops:balance(?a)]; builtin_to_scode(_Env, contract_address, []) -> @@ -650,7 +648,7 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> builtin_to_scode(Env, chain_clone, [InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) -> case GasCap of - {builtin, call_gas_left, _} -> + {builtin, _, call_gas_left, _} -> call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), [Contract, InitArgsT, Value, Prot | InitArgs] ); @@ -683,6 +681,12 @@ op_to_scode('>=') -> aeb_fate_ops:egt(?a, ?a, ?a); op_to_scode('==') -> aeb_fate_ops:eq(?a, ?a, ?a); op_to_scode('!=') -> aeb_fate_ops:neq(?a, ?a, ?a); op_to_scode('!') -> aeb_fate_ops:not_op(?a, ?a); +op_to_scode('bnot') -> aeb_fate_ops:bin_not(?a, ?a); +op_to_scode('band') -> aeb_fate_ops:bin_and(?a, ?a, ?a); +op_to_scode('bor') -> aeb_fate_ops:bin_or(?a, ?a, ?a); +op_to_scode('bxor') -> aeb_fate_ops:bin_xor(?a, ?a, ?a); +op_to_scode('<<') -> aeb_fate_ops:bin_sl(?a, ?a, ?a); +op_to_scode('>>') -> aeb_fate_ops:bin_sr(?a, ?a, ?a); op_to_scode(map_get) -> aeb_fate_ops:map_lookup(?a, ?a, ?a); op_to_scode(map_get_d) -> aeb_fate_ops:map_lookup(?a, ?a, ?a, ?a); op_to_scode(map_set) -> aeb_fate_ops:map_update(?a, ?a, ?a, ?a); @@ -693,6 +697,7 @@ op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a); op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a); op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a); op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_to_bytes) -> aeb_fate_ops:str_to_bytes(?a, ?a); op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a); op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a); op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a); @@ -707,7 +712,10 @@ op_to_scode(bits_intersection) -> aeb_fate_ops:bits_and(?a, ?a, ?a); op_to_scode(bits_union) -> aeb_fate_ops:bits_or(?a, ?a, ?a); op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); +op_to_scode(address_to_bytes) -> aeb_fate_ops:addr_to_bytes(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); +op_to_scode(int_to_bytes) -> aeb_fate_ops:int_to_bytes(?a, ?a, ?a); +op_to_scode(int_mulmod) -> aeb_fate_ops:mulmod(?a, ?a, ?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a); op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a); @@ -717,6 +725,7 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); +op_to_scode(crypto_poseidon) -> aeb_fate_ops:poseidon(?a, ?a, ?a); op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); @@ -752,6 +761,77 @@ push(A) -> {'STORE', ?a, A}. tuple(0) -> push(?i({tuple, {}})); tuple(N) -> aeb_fate_ops:tuple(?a, N). +%% -- Debug info functions -- + +dbg_contract(#env{debug_info = false}) -> + []; +dbg_contract(#env{contract = Contract}) -> + [{'DBG_CONTRACT', {immediate, Contract}}]. + +dbg_loc(#env{debug_info = false}, _) -> + []; +dbg_loc(_Env, Ann) -> + File = case proplists:get_value(file, Ann, no_file) of + no_file -> ""; + F -> F + end, + Line = proplists:get_value(line, Ann, undefined), + case Line of + undefined -> []; + _ -> [{'DBG_LOC', {immediate, File}, {immediate, Line}}] + end. + +dbg_scoped_vars(#env{debug_info = false}, _, SCode) -> + SCode; +dbg_scoped_vars(_Env, [], SCode) -> + SCode; +dbg_scoped_vars(Env, [{SavedVarName, Var} | Rest], SCode) -> + dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)); +dbg_scoped_vars(Env = #env{saved_fresh_names = SavedFreshNames}, [Var | Rest], SCode) -> + SavedVarName = maps:get(Var, SavedFreshNames, Var), + dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)). + +dbg_scoped_var(Env, SavedVarName, Var, SCode) -> + case SavedVarName == "_" orelse is_fresh_name(SavedVarName) of + true -> + SCode; + false -> + Register = lookup_var(Env, Var), + Def = [{'DBG_DEF', {immediate, SavedVarName}, Register}], + Undef = [{'DBG_UNDEF', {immediate, SavedVarName}, Register}], + Def ++ dbg_undef(Undef, SCode) + end. + +is_fresh_name([$% | _]) -> + true; +is_fresh_name(_) -> + false. + +dbg_undef(_Undef, missing) -> + missing; +dbg_undef(Undef, loop) -> + [Undef, loop]; +dbg_undef(Undef, switch_body) -> + [switch_body, Undef]; +dbg_undef(Undef, {switch, Arg, Type, Alts, Catch}) -> + NewAlts = [ dbg_undef(Undef, Alt) || Alt <- Alts ], + NewCatch = dbg_undef(Undef, Catch), + NewSwitch = {switch, Arg, Type, NewAlts, NewCatch}, + NewSwitch; +dbg_undef(Undef, SCode) when is_list(SCode) -> + lists:droplast(SCode) ++ [dbg_undef(Undef, lists:last(SCode))]; +dbg_undef(Undef, SCode) when is_tuple(SCode); is_atom(SCode) -> + [Mnemonic | _] = + case is_tuple(SCode) of + true -> tuple_to_list(SCode); + false -> [SCode] + end, + Op = aeb_fate_opcodes:m_to_op(Mnemonic), + case aeb_fate_opcodes:end_bb(Op) of + true -> [Undef, SCode]; + false -> [SCode, Undef] + end. + %% -- Phase II --------------------------------------------------------------- %% Optimize @@ -887,6 +967,10 @@ attributes(I) -> loop -> Impure(pc, []); switch_body -> Pure(none, []); 'RETURN' -> Impure(pc, []); + {'DBG_LOC', _, _} -> Impure(none, []); + {'DBG_DEF', _, _} -> Impure(none, []); + {'DBG_UNDEF', _, _} -> Impure(none, []); + {'DBG_CONTRACT', _} -> Impure(none, []); {'RETURNR', A} -> Impure(pc, A); {'CALL', A} -> Impure(?a, [A]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); @@ -914,6 +998,13 @@ attributes(I) -> {'DIV', A, B, C} -> Pure(A, [B, C]); {'MOD', A, B, C} -> Pure(A, [B, C]); {'POW', A, B, C} -> Pure(A, [B, C]); + {'MULMOD', A, B, C, D} -> Pure(A, [B, C, D]); + {'BAND', A, B, C} -> Pure(A, [B, C]); + {'BOR', A, B, C} -> Pure(A, [B, C]); + {'BXOR', A, B, C} -> Pure(A, [B, C]); + {'BNOT', A, B} -> Pure(A, [B]); + {'BSL', A, B, C} -> Pure(A, [B, C]); + {'BSR', A, B, C} -> Pure(A, [B, C]); {'LT', A, B, C} -> Pure(A, [B, C]); {'GT', A, B, C} -> Pure(A, [B, C]); {'EQ', A, B, C} -> Pure(A, [B, C]); @@ -944,9 +1035,11 @@ attributes(I) -> {'APPEND', A, B, C} -> Pure(A, [B, C]); {'STR_JOIN', A, B, C} -> Pure(A, [B, C]); {'INT_TO_STR', A, B} -> Pure(A, B); + {'INT_TO_BYTES', A, B, C} -> Pure(A, [B, C]); {'ADDR_TO_STR', A, B} -> Pure(A, B); {'STR_REVERSE', A, B} -> Pure(A, B); {'STR_LENGTH', A, B} -> Pure(A, B); + {'STR_TO_BYTES', A, B} -> Pure(A, B); {'INT_TO_ADDR', A, B} -> Pure(A, B); {'VARIANT', A, B, C, D} -> Pure(A, [?a, B, C, D]); {'VARIANT_TEST', A, B, C} -> Pure(A, [B, C]); @@ -966,18 +1059,23 @@ attributes(I) -> {'SHA3', A, B} -> Pure(A, [B]); {'SHA256', A, B} -> Pure(A, [B]); {'BLAKE2B', A, B} -> Pure(A, [B]); + {'POSEIDON', A, B, C} -> Pure(A, [B, C]); {'VERIFY_SIG', A, B, C, D} -> Pure(A, [B, C, D]); {'VERIFY_SIG_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]); + {'ADDRESS_TO_BYTES', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SPLIT_ANY', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SIZE', A, B} -> Pure(A, B); + {'BYTES_TO_FIXED_SIZE', A, B, C} -> Pure(A, [B, C]); {'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]); {'IS_ORACLE', A, B} -> Pure(A, [B]); @@ -998,6 +1096,7 @@ attributes(I) -> {'MICROBLOCK', A} -> Pure(A, []); {'DIFFICULTY', A} -> Pure(A, []); {'GASLIMIT', A} -> Pure(A, []); + {'NETWORK_ID', A} -> Pure(A, []); {'GAS', A} -> Pure(A, []); {'LOG0', A} -> Impure(none, [A]); {'LOG1', A, B} -> Impure(none, [A, B]); @@ -1082,11 +1181,16 @@ independent({i, _, I}, {i, _, J}) -> StackI = lists:member(?a, [WI | RI]), StackJ = lists:member(?a, [WJ | RJ]), - if WI == pc; WJ == pc -> false; %% no jumps - not (PureI or PureJ) -> false; %% at least one is pure - StackI and StackJ -> false; %% cannot both use the stack - WI == WJ -> false; %% cannot write to the same register - true -> + ReadStoreI = [] /= [ x || {store, _} <- RI ], + ReadStoreJ = [] /= [ x || {store, _} <- RJ ], + + if WI == pc; WJ == pc -> false; %% no jumps + not (PureI or PureJ) -> false; %% at least one is pure + StackI and StackJ -> false; %% cannot both use the stack + WI == WJ -> false; %% cannot write to the same register + ReadStoreI and not PureJ -> false; %% can't read store/state if other is impure + ReadStoreJ and not PureI -> false; %% can't read store/state if other is impure + true -> %% and cannot write to each other's inputs not lists:member(WI, RJ) andalso not lists:member(WJ, RI) @@ -1606,7 +1710,23 @@ bb(_Name, Code) -> Blocks = lists:flatmap(fun split_calls/1, Blocks1), Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]), BBs = [ set_labels(Labels, B) || B <- Blocks ], - maps:from_list(BBs). + maps:from_list(dbg_loc_filter(BBs)). + +%% Filter DBG_LOC instructions to keep one instruction per line +dbg_loc_filter(BBs) -> + dbg_loc_filter(BBs, [], [], sets:new()). + +dbg_loc_filter([], _, AllBlocks, _) -> + lists:reverse(AllBlocks); +dbg_loc_filter([{I, []} | Rest], AllOps, AllBlocks, DbgLocs) -> + dbg_loc_filter(Rest, [], [{I, lists:reverse(AllOps)} | AllBlocks], DbgLocs); +dbg_loc_filter([{I, [Op = {'DBG_LOC', _, _} | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) -> + case sets:is_element(Op, DbgLocs) of + true -> dbg_loc_filter([{I, Ops} | Rest], AllOps, AllBlocks, DbgLocs); + false -> dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, sets:add_element(Op, DbgLocs)) + end; +dbg_loc_filter([{I, [Op | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) -> + dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, DbgLocs). %% -- Break up scode into basic blocks -- diff --git a/src/aeso_parse_lib.erl b/src/aeso_parse_lib.erl index 26905e8..b3b16a1 100644 --- a/src/aeso_parse_lib.erl +++ b/src/aeso_parse_lib.erl @@ -16,7 +16,7 @@ many/1, many1/1, sep/2, sep1/2, infixl/2, infixr/2]). --export([current_file/0, set_current_file/1, +-export([current_file/0, set_current_file/1, current_dir/0, set_current_dir/1, current_include_type/0, set_current_include_type/1]). %% -- Types ------------------------------------------------------------------ @@ -481,6 +481,13 @@ current_file() -> set_current_file(File) -> put('$current_file', File). +%% Current source directory +current_dir() -> + get('$current_dir'). + +set_current_dir(File) -> + put('$current_dir', File). + add_current_file({L, C}) -> {current_file(), L, C}; add_current_file(Pos) -> Pos. diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index a296ff5..939a10b 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -20,6 +20,7 @@ -include("aeso_parse_lib.hrl"). -import(aeso_parse_lib, [current_file/0, set_current_file/1, + current_dir/0, set_current_dir/1, current_include_type/0, set_current_include_type/1]). -type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none(). @@ -59,6 +60,7 @@ run_parser(P, Inp, Opts) -> parse_and_scan(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), + set_current_dir(proplists:get_value(src_dir, Opts, no_file)), set_current_include_type(proplists:get_value(include_type, Opts, none)), case aeso_scan:scan(S) of {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); @@ -265,10 +267,11 @@ type300() -> type400() -> choice( [?RULE(typeAtom(), optional(type_args()), - case _2 of - none -> _1; - {ok, Args} -> {app_t, get_ann(_1), _1, Args} - end), + any_bytes( + case _2 of + none -> _1; + {ok, Args} -> {app_t, get_ann(_1), _1, Args} + end)), ?RULE(id("bytes"), parens(token(int)), {bytes_t, get_ann(_1), element(3, _2)}) ]). @@ -300,7 +303,7 @@ stmt() -> , {switch, keyword(switch), parens(expr()), maybe_block(branch())} , {'if', keyword('if'), parens(expr()), body()} , {elif, keyword(elif), parens(expr()), body()} - , {else, keyword(else), body()} + , {'else', keyword('else'), body()} ])). branch() -> @@ -324,7 +327,7 @@ expr100() -> Expr150 = ?LAZY_P(expr150()), choice( [ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location - , {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)} + , {'if', keyword('if'), parens(Expr100), Expr150, right(tok('else'), Expr100)} , ?RULE(Expr150, optional(right(tok(':'), type())), case _2 of none -> _1; @@ -334,14 +337,19 @@ expr100() -> expr150() -> infixl(expr200(), binop('|>')). expr200() -> infixr(expr300(), binop('||')). -expr300() -> infixr(expr400(), binop('&&')). +expr300() -> infixr(expr325(), binop('&&')). +expr325() -> infixl(expr350(), binop('bor')). +expr350() -> infixl(expr375(), binop('bxor')). +expr375() -> infixl(expr400(), binop('band')). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). -expr500() -> infixr(expr600(), binop(['::', '++'])). +expr500() -> infixr(expr550(), binop(['::', '++'])). +expr550() -> infixl(expr600(), binop(['<<', '>>'])). expr600() -> infixl(expr650(), binop(['+', '-'])). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr700() -> infixl(expr750(), binop(['*', '/', mod])). expr750() -> infixl(expr800(), binop(['^'])). -expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)). +expr800() -> ?RULE(many(token('!')), expr850(), prefixes(_1, _2)). +expr850() -> ?RULE(many(token('bnot')), expr900(), prefixes(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). exprAtom() -> @@ -518,7 +526,7 @@ id_or_addr() -> ?RULE(id(), parse_addr_literal(_1)). parse_addr_literal(Id = {id, Ann, Name}) -> - case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of + case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_", "sg_"]) of false -> Id; true -> try aeser_api_encoder:decode(list_to_binary(Name)) of @@ -557,6 +565,7 @@ bracket_list(P) -> brackets(comma_sep(P)). -spec pos_ann(ann_line(), ann_col()) -> ann(). pos_ann(Line, Col) -> [ {file, current_file()} + , {dir, current_dir()} , {include_type, current_include_type()} , {line, Line} , {col, Col} ]. @@ -604,7 +613,7 @@ group_ifs([], Acc) -> group_ifs([{'if', Ann, Cond, Then} | Stmts], Acc) -> {Elses, Rest} = else_branches(Stmts, []), group_ifs(Rest, [build_if(Ann, Cond, Then, Elses) | Acc]); -group_ifs([{else, Ann, _} | _], _) -> +group_ifs([{'else', Ann, _} | _], _) -> fail({Ann, "No matching 'if' for 'else'"}); group_ifs([{elif, Ann, _, _} | _], _) -> fail({Ann, "No matching 'if' for 'elif'"}); @@ -614,14 +623,14 @@ group_ifs([Stmt | Stmts], Acc) -> build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) -> {'if', Ann, Cond, Then, set_ann(format, elif, build_if(Ann1, Cond1, Then1, Elses))}; -build_if(Ann, Cond, Then, [{else, _Ann, Else}]) -> +build_if(Ann, Cond, Then, [{'else', _Ann, Else}]) -> {'if', Ann, Cond, Then, Else}; build_if(Ann, Cond, Then, []) -> {'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}. else_branches([Elif = {elif, _, _, _} | Stmts], Acc) -> else_branches(Stmts, [Elif | Acc]); -else_branches([Else = {else, _, _} | Stmts], Acc) -> +else_branches([Else = {'else', _, _} | Stmts], Acc) -> {lists:reverse([Else | Acc]), Stmts}; else_branches(Stmts, Acc) -> {lists:reverse(Acc), Stmts}. @@ -697,7 +706,7 @@ expand_includes([], Included, Acc, Opts) -> end; expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> case get_include_code(File, Ann, Opts) of - {ok, Code} -> + {ok, AbsDir, Code} -> Hashed = hash_include(File, Code), case sets:is_element(Hashed, Included) of false -> @@ -707,9 +716,10 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op _ -> indirect end, Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), - Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}), + Opts2 = lists:keystore(src_dir, 1, Opts1, {src_dir, AbsDir}), + Opts3 = lists:keystore(include_type, 1, Opts2, {include_type, IncludeType}), Included1 = sets:add_element(Hashed, Included), - case parse_and_scan(file(), Code, Opts2) of + case parse_and_scan(file(), Code, Opts3) of {ok, AST1} -> expand_includes(AST1 ++ AST, Included1, Acc, Opts); Err = {error, _} -> @@ -727,13 +737,12 @@ expand_includes([E | AST], Included, Acc, Opts) -> read_file(File, Opts) -> case proplists:get_value(include, Opts, {explicit_files, #{}}) of {file_system, Paths} -> - CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ], - lists:foldr(fun(F, {error, _}) -> file:read_file(F); - (_F, OK) -> OK end, {error, not_found}, CandidateNames); + lists:foldr(fun(Path, {error, _}) -> read_file_(Path, File); + (_Path, OK) -> OK end, {error, not_found}, Paths); {explicit_files, Files} -> case maps:get(binary_to_list(File), Files, not_found) of not_found -> {error, not_found}; - Src -> {ok, Src} + Src -> {ok, File, Src} end; escript -> try @@ -742,7 +751,7 @@ read_file(File, Opts) -> Archive = proplists:get_value(archive, Sections), FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])), case zip:extract(Archive, [{file_list, [FileName]}, memory]) of - {ok, [{_, Src}]} -> {ok, Src}; + {ok, [{_, Src}]} -> {ok, escript, Src}; _ -> {error, not_found} end catch _:_ -> @@ -750,6 +759,13 @@ read_file(File, Opts) -> end end. +read_file_(Path, File) -> + AbsFile = filename:join(Path, File), + case file:read_file(AbsFile) of + {ok, Bin} -> {ok, aeso_utils:canonical_dir(filename:dirname(AbsFile)), Bin}; + Err -> Err + end. + stdlib_options() -> StdLibDir = aeso_stdlib:stdlib_include_path(), case filelib:is_dir(StdLibDir) of @@ -758,23 +774,37 @@ stdlib_options() -> end. get_include_code(File, Ann, Opts) -> - case {read_file(File, Opts), read_file(File, stdlib_options())} of - {{ok, Bin}, {ok, _}} -> + %% Temporarily extend include paths with the directory of the current file + Opts1 = include_current_file_dir(Opts, Ann), + case {read_file(File, Opts1), read_file(File, stdlib_options())} of + {{ok, Dir, Bin}, {ok, _}} -> case filename:basename(File) == File of true -> { error , fail( ann_pos(Ann) , "Illegal redefinition of standard library " ++ binary_to_list(File))}; %% If a path is provided then the stdlib takes lower priority - false -> {ok, binary_to_list(Bin)} + false -> {ok, Dir, binary_to_list(Bin)} end; - {_, {ok, Bin}} -> - {ok, binary_to_list(Bin)}; - {{ok, Bin}, _} -> - {ok, binary_to_list(Bin)}; + {_, {ok, _, Bin}} -> + {ok, stdlib, binary_to_list(Bin)}; + {{ok, Dir, Bin}, _} -> + {ok, Dir, binary_to_list(Bin)}; {_, _} -> {error, {ann_pos(Ann), include_error, File}} end. +include_current_file_dir(Opts, Ann) -> + case {proplists:get_value(dir, Ann, undefined), + proplists:get_value(include, Opts, undefined)} of + {undefined, _} -> Opts; + {CurrDir, {file_system, Paths}} -> + case lists:member(CurrDir, Paths) of + false -> [{include, {file_system, [CurrDir | Paths]}} | Opts]; + true -> Opts + end; + {_, _} -> Opts + end. + -spec hash_include(string() | binary(), string()) -> include_hash(). hash_include(File, Code) when is_binary(File) -> hash_include(binary_to_list(File), Code); @@ -788,3 +818,7 @@ auto_imports(L) when is_list(L) -> auto_imports(T) when is_tuple(T) -> auto_imports(tuple_to_list(T)); auto_imports(_) -> []. + +any_bytes({id, Ann, "bytes"}) -> {bytes_t, Ann, any}; +any_bytes({app_t, _, {id, Ann, "bytes"}, []}) -> {bytes_t, Ann, any}; +any_bytes(Type) -> Type. diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 2c7c340..5b187df 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -276,7 +276,9 @@ type({tuple_t, _, Args}) -> tuple_type(Args); type({args_t, _, Args}) -> args_type(Args); -type({bytes_t, _, any}) -> text("bytes(_)"); +type({bytes_t, _, any}) -> text("bytes()"); +type({bytes_t, _, '_'}) -> text("bytes(_)"); +type({bytes_t, _, fixed}) -> text("bytes(_)"); type({bytes_t, _, Len}) -> text(lists:concat(["bytes(", Len, ")"])); type({if_t, _, Id, Then, Else}) -> @@ -386,7 +388,8 @@ expr_p(_, {Type, _, Bin}) when Type == account_pubkey; Type == contract_pubkey; Type == oracle_pubkey; - Type == oracle_query_id -> + Type == oracle_query_id; + Type == signature -> text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); expr_p(_, {string, _, <<>>}) -> text("\"\""); expr_p(_, {string, _, S}) -> @@ -417,7 +420,7 @@ stmt_p({'if', _, Cond, Then}) -> block_expr(200, beside(text("if"), paren(expr(Cond))), Then); stmt_p({elif, _, Cond, Then}) -> block_expr(200, beside(text("elif"), paren(expr(Cond))), Then); -stmt_p({else, Else}) -> +stmt_p({'else', Else}) -> HideGenerated = not show_generated(), case aeso_syntax:get_ann(origin, Else) of system when HideGenerated -> empty(); @@ -437,15 +440,20 @@ 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('&&') -> {300, 325, 300}; +bin_prec('bor') -> {325, 350, 325}; +bin_prec('bxor') -> {350, 375, 350}; +bin_prec('band') -> {375, 400, 375}; bin_prec('<') -> {400, 500, 500}; bin_prec('>') -> {400, 500, 500}; bin_prec('=<') -> {400, 500, 500}; bin_prec('>=') -> {400, 500, 500}; bin_prec('==') -> {400, 500, 500}; bin_prec('!=') -> {400, 500, 500}; -bin_prec('++') -> {500, 600, 500}; -bin_prec('::') -> {500, 600, 500}; +bin_prec('++') -> {500, 550, 500}; +bin_prec('::') -> {500, 550, 500}; +bin_prec('<<') -> {550, 600, 550}; +bin_prec('>>') -> {550, 600, 550}; bin_prec('+') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650}; bin_prec('*') -> {700, 700, 750}; @@ -455,7 +463,8 @@ bin_prec('^') -> {750, 750, 800}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. un_prec('-') -> {650, 650}; -un_prec('!') -> {800, 800}. +un_prec('!') -> {800, 800}; +un_prec('bnot') -> {850, 850}. equals(Ann, A, B) -> {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. @@ -526,5 +535,5 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) -> elif -> get_elifs(Else, [{elif, Ann, Cond, Then} | Elifs]); _ -> {lists:reverse(Elifs), If} end; -get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}. +get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {'else', Else}}. diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index d5aa532..363b952 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -46,7 +46,7 @@ lexer() -> Keywords = ["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" + "interface", "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot" ], KW = string:join(Keywords, "|"), diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 81588d0..12da1cc 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -11,7 +11,7 @@ -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]). --export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). +-export_type([ann_file/0, 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, fundecl/0]). @@ -25,8 +25,9 @@ -type ann_col() :: integer(). -type ann_origin() :: system | user. -type ann_format() :: '?:' | hex | infix | prefix | elif. +-type ann_file() :: string() | no_file. --type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} +-type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private | payable | main | interface | entrypoint]. -type name() :: string(). @@ -100,6 +101,7 @@ | {contract_pubkey, ann(), binary()} | {oracle_pubkey, ann(), binary()} | {oracle_query_id, ann(), binary()} + | {signature, ann(), binary()} | {string, ann(), binary()} | {char, ann(), integer()}. @@ -107,8 +109,8 @@ -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' - | '||' | '&&' | '..' | '|>'. --type un_op() :: '-' | '!'. + | '||' | '&&' | '..' | 'band' | 'bor' | 'bxor' | '>>' | '<<' | '|>'. +-type un_op() :: '-' | '!' | 'bnot'. -type expr() :: {lam, ann(), [arg()], expr()} diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index a419c33..43c2a4f 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -7,7 +7,7 @@ -module(aeso_syntax_utils). -vsn("7.1.2"). --export([used_ids/1, used_types/2, used/1]). +-export([used_ids/1, used_ids/2, used_types/2, used/1]). -record(alg, {zero, plus, scoped}). @@ -111,8 +111,16 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> %% Name dependencies +%% Used ids, top level used_ids(E) -> - [ X || {{term, [X]}, _} <- used(E) ]. + used_ids([], E). + +%% Used ids, top level or in (current) namespace +used_ids(Ns, E) -> + [ lists:last(Xs) || {{term, Xs}, _} <- used(E), in_ns(Xs, Ns) ]. + +in_ns([_], _) -> true; +in_ns(Xs, Ns) -> lists:droplast(Xs) == Ns. used_types([Top] = _CurrentNS, T) -> F = fun({{type, [X]}, _}) -> [X]; diff --git a/src/aeso_utils.erl b/src/aeso_utils.erl index e81e27e..77004a0 100644 --- a/src/aeso_utils.erl +++ b/src/aeso_utils.erl @@ -7,10 +7,22 @@ -module(aeso_utils). -vsn("7.1.2"). --export([scc/1]). +-export([scc/1, canonical_dir/1]). -export_type([graph/1]). +%% -- Simplistic canonical directory +%% Note: no attempts to be 100% complete + +canonical_dir(Dir) -> + {ok, Cwd} = file:get_cwd(), + AbsName = filename:absname(Dir), + RelAbsName = filename:join(tl(filename:split(AbsName))), + case filelib:safe_relative_path(RelAbsName, Cwd) of + unsafe -> AbsName; + Simplified -> filename:absname(Simplified, "") + end. + %% -- Topological sort -type graph(Node) :: #{Node => [Node]}. %% List of incoming edges (dependencies). diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index be1fe05..b985b3b 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -13,9 +13,13 @@ -spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr(). from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin}; +from_fate({id, _, "signature"}, ?FATE_BYTES(Bin)) -> {signature, [], Bin}; +from_fate({id, _, "hash"}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin}; +from_fate({id, _, "unit"}, ?FATE_UNIT) -> {tuple, [], []}; from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; +from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> make_any_bytes(Bin); from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N); from_fate({id, _, "int"}, N) when is_integer(N) -> @@ -79,6 +83,7 @@ from_fate_builtin(QType, Val) -> Hsh = {bytes_t, [], 32}, I32 = {bytes_t, [], 32}, I48 = {bytes_t, [], 48}, + Bts = {bytes_t, [], any}, Qid = fun(Name) -> {qid, [], Name} end, Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end, ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0], @@ -89,7 +94,7 @@ from_fate_builtin(QType, Val) -> {["AENS", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} -> App(["AENS","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL), - Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]); + Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]); {["AENS", "pointee"], {variant, [1, 1, 1, 1], 0, {Addr}}} -> App(["AENS","AccountPt"], [Chk(Adr, Addr)]); @@ -100,6 +105,21 @@ from_fate_builtin(QType, Val) -> {["AENS", "pointee"], {variant, [1, 1, 1, 1], 3, {Addr}}} -> App(["AENS","ChannelPt"], [Chk(Adr, Addr)]); + {["AENSv2", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} -> + App(["AENSv2","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL), + Chk(Map(Str, Qid(["AENSv2", "pointee"])), Ptrs)]); + + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 0, {Value}}} -> + App(["AENSv2","AccountPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 1, {Value}}} -> + App(["AENSv2","OraclePt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 2, {Value}}} -> + App(["AENSv2","ContractPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 3, {Value}}} -> + App(["AENSv2","ChannelPt"], [Chk(Adr, Value)]); + {["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 4, {Value}}} -> + App(["AENSv2","DataPt"], [Chk(Bts, Value)]); + {["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} -> App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]); @@ -171,3 +191,5 @@ make_bits(Set, Zero, I, N) when 0 == N rem 2 -> make_bits(Set, Zero, I, N) -> {app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}. +make_any_bytes(Bin) -> + {app, [], {qid, [], ["Bytes", "to_any_size"]}, [{bytes, [], Bin}]}. diff --git a/src/aesophia.app.src b/src/aesophia.app.src index d195d99..926f26a 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,13 +1,12 @@ {application, aesophia, [{description, "Compiler for Aeternity Sophia language"}, - {vsn, "7.1.0"}, + {vsn, "8.0.1"}, {registered, []}, {applications, [kernel, stdlib, jsx, syntax_tools, - getopt, aebytecode, eblake2 ]}, diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 88d7bae..a49efd6 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -85,6 +85,7 @@ compilable_contracts() -> {"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test {"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]}, + {"funargs", "any_bytes", ["Bytes.to_any_size(#0011AA)"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" "414243444546474849505152535455565758596061626364656667"]}, @@ -116,6 +117,7 @@ compilable_contracts() -> {"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]}, {"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]}, {"funargs", "chain_base_tx", ["Chain.GAAttachTx"]}, + {"funargs", "sig", ["sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj"]}, {"variant_types", "init", []}, {"basic_auth", "init", []}, {"address_literals", "init", []}, diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ed50821..c13b180 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -70,6 +70,7 @@ simple_compile_test_() -> fun() -> #{ warnings := Warnings } = compile("warnings", [warn_all]), #{ warnings := [] } = compile("warning_unused_include_no_include", [warn_all]), + #{ warnings := [] } = compile("warning_used_record_typedef", [warn_all]), check_warnings(warnings(), Warnings) end} ] ++ []. @@ -161,6 +162,7 @@ compilable_contracts() -> "state_handling", "events", "include", + "relative_include", "basic_auth", "basic_auth_tx", "bitcoin_auth", @@ -170,6 +172,7 @@ compilable_contracts() -> "namespace_bug", "bytes_to_x", "bytes_concat", + "bytes_misc", "aens", "aens_update", "tuple_match", @@ -223,6 +226,7 @@ compilable_contracts() -> "unapplied_named_arg_builtin", "resolve_field_constraint_by_arity", "toplevel_constants", + "ceres", "test" % Custom general-purpose test file. Keep it last on the list. ]. @@ -447,6 +451,10 @@ failing_contracts() -> [<>, + <>]) , ?TYPE_ERROR(not_toplevel_include, [< [<>]) + , ?TYPE_ERROR(bad_bytes_to_x, + [<>, + < option('a)`\n" + "to arguments\n" + " `b : bytes(4)`">>, + <>, + <>]) , ?TYPE_ERROR(bad_bytes_concat, [< "and result type\n" " - 'c (at line 16, column 39)">>, <>]) + "Cannot resolve type of byte array in\n" + " the first argument of a call to Bytes.to_str">>]) , ?TYPE_ERROR(bad_bytes_split, [< "Trying to implement or extend an undefined interface `Z`">> ]) , ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type, - [<>]) + [<> + ]) , ?TYPE_ERROR(polymorphism_contract_missing_implementation, [<> @@ -928,6 +952,9 @@ failing_contracts() -> < Cat) => dt_inv(Cat)`\nto arguments\n `f_c_to_a : (Cat) => Animal`">>, + <>, <>, @@ -976,6 +1003,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1019,6 +1049,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1043,6 +1076,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1104,19 +1140,19 @@ failing_contracts() -> ]) , ?TYPE_ERROR(polymorphic_aens_resolve, [<> ]) , ?TYPE_ERROR(bad_aens_resolve, [<> ]) , ?TYPE_ERROR(bad_aens_resolve_using, [<> ]) @@ -1266,6 +1302,10 @@ failing_contracts() -> <> ]) + , ?TYPE_ERROR(too_many_tvars, + [<> + ]) ]. validation_test_() -> @@ -1313,7 +1353,9 @@ validate(Contract1, Contract2) -> true -> [debug_mode]; false -> [] end ++ - [{include, {file_system, [aeso_test_utils:contract_path()]}}]); + [ {src_file, lists:concat([Contract2, ".aes"])} + , {include, {file_system, [aeso_test_utils:contract_path()]}} + ]); Error -> print_and_throw(Error) end. diff --git a/test/aeso_encode_decode_tests.erl b/test/aeso_encode_decode_tests.erl new file mode 100644 index 0000000..898842f --- /dev/null +++ b/test/aeso_encode_decode_tests.erl @@ -0,0 +1,40 @@ +-module(aeso_encode_decode_tests). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(EMPTY, "contract C =\n entrypoint f() = true"). + +encode_decode_test_() -> + [ {lists:flatten(io_lib:format("Testing encode-decode roundtrip for ~p : ~p", [Value, {EType, DType}])), + fun() -> + {ok, SerRes} = aeso_compiler:encode_value(?EMPTY, EType, Value, []), + {ok, Expr} = aeso_compiler:decode_value(?EMPTY, DType, SerRes, []), + Value2 = prettypr:format(aeso_pretty:expr(Expr)), + ?assertEqual(Value, Value2) + end} || {Value, EType, DType} <- test_data() ]. + +test_data() -> + lists:map(fun({V, T}) -> {V, T, T}; + ({V, T1, T2}) -> {V, T1, T2} end, data()). + +data() -> + [ {"42", "int"} + , {"- 42", "int"} + , {"true", "bool"} + , {"ak_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "address"} + , {"ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "C"} + , {"Some(42)", "option(int)"} + , {"None", "option(int)"} + , {"(true, 42)", "bool * int"} + , {"{[1] = true, [3] = false}", "map(int, bool)"} + , {"()", "unit"} + , {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "hash"} + , {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "bytes(32)"} + , {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "signature"} + , {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "bytes(64)", "signature"} + , {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "bytes(64)"} + , {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "signature", "bytes(64)"} + ]. + diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index e3b7598..2324fc4 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -53,7 +53,7 @@ simple_contracts_test_() -> %% associativity [ RightAssoc(Op) || Op <- ["||", "&&", "::", "++"] ], [ NonAssoc(Op) || Op <- ["==", "!=", "<", ">", "=<", ">="] ], - [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod"] ], + [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod", "band", "bor", "bxor", "<<", ">>"] ], %% precedence [ Stronger(Op2, Op1) || [T1 , T2 | _] <- tails(Tiers), Op1 <- T1, Op2 <- T2 ], diff --git a/test/aeso_scan_tests.erl b/test/aeso_scan_tests.erl index 685d3ac..92912e0 100644 --- a/test/aeso_scan_tests.erl +++ b/test/aeso_scan_tests.erl @@ -39,7 +39,8 @@ all_tokens() -> %% Symbols lists:map(Lit, [',', '.', ';', '|', ':', '(', ')', '[', ']', '{', '}']) ++ %% Operators - lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ + lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, + ':', '::', '->', '=>', '||', '&&', '!', 'band', 'bor', 'bxor', 'bnot' ,'<<', '>>']) ++ %% Keywords lists:map(Lit, [contract, type, 'let', switch]) ++ %% Comment token (not an actual token), just for tests diff --git a/test/contracts/aens.aes b/test/contracts/aens.aes index 698e4c5..ac59b4d 100644 --- a/test/contracts/aens.aes +++ b/test/contracts/aens.aes @@ -6,77 +6,77 @@ main contract AENSTest = // Name resolution stateful entrypoint resolve_word(name : string, key : string) : option(address) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_string(name : string, key : string) : option(string) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_contract(name : string, key : string) : option(C) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) = - AENS.resolve(name, key) + AENSv2.resolve(name, key) // Transactions stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address) chash : hash) : unit = // Commitment hash - AENS.preclaim(addr, chash) + AENSv2.preclaim(addr, chash) stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address) chash : hash, // Commitment hash sign : signature) : unit = // Signed by addr (if not Contract.address) - AENS.preclaim(addr, chash, signature = sign) + AENSv2.preclaim(addr, chash, signature = sign) stateful entrypoint claim(addr : address, name : string, salt : int, name_fee : int) : unit = - AENS.claim(addr, name, salt, name_fee) + AENSv2.claim(addr, name, salt, name_fee) stateful entrypoint signedClaim(addr : address, name : string, salt : int, name_fee : int, sign : signature) : unit = - AENS.claim(addr, name, salt, name_fee, signature = sign) + AENSv2.claim(addr, name, salt, name_fee, signature = sign) stateful entrypoint update(owner : address, name : string, ttl : option(Chain.ttl), client_ttl : option(int), - pointers : option(map(string, AENS.pointee))) : unit = - AENS.update(owner, name, ttl, client_ttl, pointers) + pointers : option(map(string, AENSv2.pointee))) : unit = + AENSv2.update(owner, name, ttl, client_ttl, pointers) stateful entrypoint signedUpdate(owner : address, name : string, ttl : option(Chain.ttl), client_ttl : option(int), - pointers : option(map(string, AENS.pointee)), + pointers : option(map(string, AENSv2.pointee)), sign : signature) : unit = - AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign) + AENSv2.update(owner, name, ttl, client_ttl, pointers, signature = sign) stateful entrypoint transfer(owner : address, new_owner : address, name : string) : unit = - AENS.transfer(owner, new_owner, name) + AENSv2.transfer(owner, new_owner, name) stateful entrypoint signedTransfer(owner : address, new_owner : address, name : string, sign : signature) : unit = - AENS.transfer(owner, new_owner, name, signature = sign) + AENSv2.transfer(owner, new_owner, name, signature = sign) stateful entrypoint revoke(owner : address, name : string) : unit = - AENS.revoke(owner, name) + AENSv2.revoke(owner, name) stateful entrypoint signedRevoke(owner : address, name : string, sign : signature) : unit = - AENS.revoke(owner, name, signature = sign) + AENSv2.revoke(owner, name, signature = sign) diff --git a/test/contracts/aens_update.aes b/test/contracts/aens_update.aes index 5d65229..9a0804d 100644 --- a/test/contracts/aens_update.aes +++ b/test/contracts/aens_update.aes @@ -1,17 +1,32 @@ -contract AENSUpdate = - stateful entrypoint update_name(owner : address, name : string) = - let p1 : AENS.pointee = AENS.AccountPt(Call.caller) - let p2 : AENS.pointee = AENS.OraclePt(Call.caller) - let p3 : AENS.pointee = AENS.ContractPt(Call.caller) - let p4 : AENS.pointee = AENS.ChannelPt(Call.caller) - AENS.update(owner, name, None, None, - Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, - ["contract_pubkey"] = p3, ["misc"] = p4 })) +include "Option.aes" +include "String.aes" +include "AENSCompat.aes" +contract interface OldAENSContract = + entrypoint set : (string, string, AENS.pointee) => unit + entrypoint lookup : (string, string) => AENS.pointee + +main contract AENSUpdate = + stateful entrypoint update_name(owner : address, name : string, b : bytes(2)) = + let p1 : AENSv2.pointee = AENSv2.AccountPt(Call.caller) + let p2 : AENSv2.pointee = AENSv2.OraclePt(Call.caller) + let p3 : AENSv2.pointee = AENSv2.ContractPt(Call.caller) + let p4 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller) + let p5 : AENSv2.pointee = AENSv2.DataPt(String.to_bytes("any something will do")) + let p6 : AENSv2.pointee = AENSv2.DataPt(Int.to_bytes(1345, 4)) + AENSv2.update(owner, name, None, None, + Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, + ["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5, ["data2"] = p6 })) + + stateful entrypoint old_interaction(c : OldAENSContract, owner : address, name : string) = + let p : AENS.pointee = c.lookup(name, "key1") + AENSv2.update(owner, name, None, None, Some({ ["key1"] = AENSCompat.pointee_to_V2(p) })) + switch(AENSv2.lookup(name)) + Some(AENSv2.Name(_, _, pt_map)) => + c.set(name, "key2", Option.force(AENSCompat.pointee_from_V2(pt_map["key1"]))) entrypoint get_ttl(name : string) = - switch(AENS.lookup(name)) - Some(AENS.Name(_, FixedTTL(ttl), _)) => ttl + switch(AENSv2.lookup(name)) + Some(AENSv2.Name(_, FixedTTL(ttl), _)) => ttl entrypoint expiry(o : oracle(int, int)) : int = Oracle.expiry(o) - diff --git a/test/contracts/bad_aens_resolve.aes b/test/contracts/bad_aens_resolve.aes index 8700dfb..44f15e5 100644 --- a/test/contracts/bad_aens_resolve.aes +++ b/test/contracts/bad_aens_resolve.aes @@ -3,7 +3,7 @@ contract BadAENSresolve = type t('a) = option(list('a)) function fail() : t(int) = - AENS.resolve("foo.aet", "whatever") + AENSv2.resolve("foo.aet", "whatever") entrypoint main_fun() = () diff --git a/test/contracts/bad_aens_resolve_using.aes b/test/contracts/bad_aens_resolve_using.aes index 8556f46..5b367e7 100644 --- a/test/contracts/bad_aens_resolve_using.aes +++ b/test/contracts/bad_aens_resolve_using.aes @@ -1,9 +1,9 @@ contract BadAENSresolve = - using AENS + using AENSv2 type t('a) = option(list('a)) function fail() : t(int) = resolve("foo.aet", "whatever") - entrypoint main_fun() = () \ No newline at end of file + entrypoint main_fun() = () diff --git a/test/contracts/bad_bytes_to_x.aes b/test/contracts/bad_bytes_to_x.aes new file mode 100644 index 0000000..4e3db65 --- /dev/null +++ b/test/contracts/bad_bytes_to_x.aes @@ -0,0 +1,5 @@ +// include "String.aes" +contract BytesToX = + entrypoint fail1(b : bytes()) = Bytes.to_fixed_size(b) + entrypoint fail2(b : bytes(4)) = Bytes.to_fixed_size(b) + entrypoint fail3(b : bytes()) = Bytes.to_any_size(b) diff --git a/test/contracts/bytes_misc.aes b/test/contracts/bytes_misc.aes new file mode 100644 index 0000000..13e7fb4 --- /dev/null +++ b/test/contracts/bytes_misc.aes @@ -0,0 +1,27 @@ +include "String.aes" +contract BytesMisc = + entrypoint sizeFixed(b : bytes(4)) : int = Bytes.size(b) + entrypoint sizeAny(b : bytes()) : int = Bytes.size(b) + entrypoint int_to_bytes(i : int) : bytes() = Int.to_bytes(i, 16) + + entrypoint test(b3 : bytes(3), b7 : bytes(7), bX : bytes, i : int, s : string) = + let bi = Int.to_bytes(i, 8) + let bs = String.to_bytes(s) + + let b10 = Bytes.concat(b3, b7) + + let (b4, b6 : bytes(6)) = Bytes.split(b10) + + let Some((b8, b2)) = Bytes.split_any(bX, 8) + + let bX7 = Bytes.concat(bX, b7) + + let Some((b5, bX2)) = Bytes.split_any(bX7, 5) + + let Some((b7b, b0)) = Bytes.split_any(bX, Bytes.size(b7)) + + let Some(b5b : bytes(5)) = Bytes.to_fixed_size(b5) + + let (b1 : bytes(1), _) = Bytes.split(b5b) + + [bi, bs, b0, Bytes.to_any_size(b1), b2, Bytes.to_any_size(b4), Bytes.to_any_size(b6), b7b, b8, bX2] diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes index 6ab4852..14e150d 100644 --- a/test/contracts/bytes_to_x.aes +++ b/test/contracts/bytes_to_x.aes @@ -6,3 +6,5 @@ contract BytesToX = String.concat(Bytes.to_str(b), Bytes.to_str(#ffff)) entrypoint to_str_big(b : bytes(65)) : string = Bytes.to_str(b) + entrypoint to_fixed(b : bytes()) : option(bytes(4)) = Bytes.to_fixed_size(b) + entrypoint to_any(b : bytes(4)) = Bytes.to_any_size(b) diff --git a/test/contracts/ceres.aes b/test/contracts/ceres.aes new file mode 100644 index 0000000..7e869cf --- /dev/null +++ b/test/contracts/ceres.aes @@ -0,0 +1,15 @@ +contract C = + entrypoint test() = + let a : int = 23 + let b : int = 52 + let c = a bor b + let d = c bxor b + let e = d band b + let f = bnot a + let g = f << 2 + let h = g >> 2 + let i = Int.mulmod(a, b, h) + let j = Crypto.poseidon(i, a) + let k : bytes(32) = Address.to_bytes(Call.origin) + let l = sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj + (a bor b band c bxor a << bnot b >> a, k, l) diff --git a/test/contracts/chain.aes b/test/contracts/chain.aes index 9063a5e..a43493d 100644 --- a/test/contracts/chain.aes +++ b/test/contracts/chain.aes @@ -2,7 +2,8 @@ contract ChainTest = - record state = { last_bf : address } + record state = { last_bf : address + , nw_id : string } function init() : state = {last_bf = Contract.address} @@ -11,3 +12,6 @@ contract ChainTest = function save_coinbase() = put(state{last_bf = Chain.coinbase}) + + function save_network_id() = + put(state{nw_id = Chain.network_id}) diff --git a/test/contracts/dir1/bar.aes b/test/contracts/dir1/bar.aes new file mode 100644 index 0000000..abc4aed --- /dev/null +++ b/test/contracts/dir1/bar.aes @@ -0,0 +1,4 @@ +include "../dir2/baz.aes" +namespace D = + function g() = E.h() + diff --git a/test/contracts/dir2/baz.aes b/test/contracts/dir2/baz.aes new file mode 100644 index 0000000..e14962f --- /dev/null +++ b/test/contracts/dir2/baz.aes @@ -0,0 +1,3 @@ +namespace E = + function h() = 42 + diff --git a/test/contracts/funargs.aes b/test/contracts/funargs.aes index cd54fa5..5d16112 100644 --- a/test/contracts/funargs.aes +++ b/test/contracts/funargs.aes @@ -20,6 +20,8 @@ contract FunctionArguments = entrypoint read(a : answer(int)) = a.result + entrypoint any_bytes(b : bytes()) = b + entrypoint sjutton(b : bytes(17)) = b @@ -57,3 +59,5 @@ contract FunctionArguments = entrypoint chain_ga_meta_tx(tx : Chain.ga_meta_tx) = true entrypoint chain_paying_for_tx(tx : Chain.paying_for_tx) = true entrypoint chain_base_tx(tx : Chain.base_tx) = true + + entrypoint sig(sg : signature) = true diff --git a/test/contracts/polymorphic_aens_resolve.aes b/test/contracts/polymorphic_aens_resolve.aes index 0bd9cc8..15517dd 100644 --- a/test/contracts/polymorphic_aens_resolve.aes +++ b/test/contracts/polymorphic_aens_resolve.aes @@ -1,7 +1,7 @@ contract PolymorphicAENSresolve = function fail() : option('a) = - AENS.resolve("foo.aet", "whatever") + AENSv2.resolve("foo.aet", "whatever") entrypoint main_fun() = () diff --git a/test/contracts/relative_include.aes b/test/contracts/relative_include.aes new file mode 100644 index 0000000..e64ecb1 --- /dev/null +++ b/test/contracts/relative_include.aes @@ -0,0 +1,3 @@ +include "./dir1/bar.aes" +contract C = + entrypoint f() = D.g() diff --git a/test/contracts/too_many_tvars.aes b/test/contracts/too_many_tvars.aes new file mode 100644 index 0000000..4fa78ce --- /dev/null +++ b/test/contracts/too_many_tvars.aes @@ -0,0 +1,60 @@ +contract C = + entrypoint too_many( + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _)) = 0 + + entrypoint not_too_many( + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _, _, _, _, _), + (_, _, _, _, _, _)) = 0 diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 169a29b..f0be4c9 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -2,10 +2,10 @@ // Named argument builtins are: // Oracle.register // Oracle.respond -// AENS.preclaim -// AENS.claim -// AENS.transfer -// AENS.revoke +// AENSv2.preclaim +// AENSv2.claim +// AENSv2.transfer +// AENSv2.revoke // Oracle.extend include "String.aes" contract UnappliedBuiltins = @@ -28,7 +28,7 @@ contract UnappliedBuiltins = function oracle_get_answer() = Oracle.get_answer : (o, _) => _ function oracle_check() = Oracle.check : o => _ function oracle_check_query() = Oracle.check_query : (o, _) => _ - function aens_resolve() = AENS.resolve : (_, _) => option(string) + function aens_resolve() = AENSv2.resolve : (_, _) => option(string) function map_lookup() = Map.lookup : (_, m) => _ function map_lookup_default() = Map.lookup_default : (_, m, _) => _ function map_member() = Map.member : (_, m) => _ @@ -36,7 +36,7 @@ contract UnappliedBuiltins = function map_delete() = Map.delete : (_, m) => _ function map_from_list() = Map.from_list : _ => m function map_to_list() = Map.to_list : m => _ - function crypto_verify_sig() = Crypto.verify_sig + function crypto_verify_sig() = Crypto.verify_sig : (bytes(), _, _) => _ function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1 function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1 function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1 diff --git a/test/contracts/warning_used_record_typedef.aes b/test/contracts/warning_used_record_typedef.aes new file mode 100644 index 0000000..537b19a --- /dev/null +++ b/test/contracts/warning_used_record_typedef.aes @@ -0,0 +1,5 @@ +contract Test = + type option_int = option(int) + record option_point = {x: int, y: option_int} + + entrypoint test_option_record(a: option_point) = a