owned this note
owned this note
Published
Linked with GitHub
###### tags:`Technical Notes`
# Literal suffixes - design decisions
## Open questions
1. Separator: `_` or whitespace?
- *Decision: use whitespace.*
2. Dedicated declaration syntax?
- *Tentatively: allowing any free function would be too broad. We need to limit that.*
- TODO: *Research possible syntax variants.*
3. Range of exponent
- *Decision: unsigned integer of any size, `uint` at most. Allowing signed together with having negative exponents represented with positive numbers would easily lead to user errors.*
4. Range of mantissa
- *Decision: both signed and unsigned integers of any size, `uint` at most. Here it's harmless and `int` may let the user avoid an explicit conversion.*
5. Distinguishing between integers and fractions
- *Decision: decide based on declaration. No parser hack for `123` vs `123.0`*
6. Number and types of return values
- *Decision: allow only one.
- *Tentatively: allow reference types if we already allowed them in operators, otherwise consider disallowing for now.*
7. non-pure suffixes (external calls? side-effects?)
- *Decision: allow only pure. Eventually we want constant evaluation and we'll be making it even more restricted.*
8. `storage` and `calldata` return values?
- *Decision: disallow. There's not much against it but there's also no good use case.*
9. Qualified suffix names (e.g. `123 m.suffix`, where `m` is a module)
- *Decision allow. No reason to disallow and in the future it might be useful with namespaces.*
10. Using deprecated denominations (`finney` and `szabo`) as suffix names.
- *Decision: allow*.
## Current decisions
- Supported literals: rational numbers, booleans, addresses, strings.
Allowed:
- Leading and trailing underscores in the name.
- Suffix function overloading.
- Importing suffix functions from other files.
Disallowed:
- Multiple suffixes on the same literal.
- Suffix function declarations in contracts, libraries, interfaces.
- External suffix functions.
## Details
### 1. Separator: `_` or whitespace?
- Variant A: `123_456.789e10 suffix`
- Variant B: `123_456.789e10_suffix`
Problem with A: same syntax as denominations. We will no longer be able to reject the deprecated `finney` and `szabo` unless we hard-code them in the parser.
Problem with B: ambiguous for hexadecimals. Is `0x123_abc` the number `0x123abc` or just `0x123` with a literal suffix called `abc`?
### 2. Dedicated declaration syntax?
- Variant A: `function _ suffix(T) pure returns (T)`
- Variant B: `function _suffix(T) pure returns (T)` (i.e. name must start with `_`)
- Variant C: `function suffix(T) pure returns (T)`
- Variant D: `literal suffix(T) pure returns (T)`
Initially we decided to go with (A) or at worst (B) but I don't see any technical obstacles to allowing any free function as a suffix so why not go with (C)?
If we still go for special syntax I also think that (D) might be nicer, though it unfortunately requires a new keyword.
### 3. Range of exponent
- Variant A: `uint8`.
- Variant B: `int8`.
- Variant C: `uint`.
- Variant D: `int`.
Context:
- Integer literals must be between `-2**255` and `2**256-1`. Otherwise: `Invalid rational number`.
- Fractional part of rational literals does not seem limited in length. Even numbers with 100k fractional digits are valid.
Considerations:
- Neither a single byte nor a full `uint` lets us represent all valid fractional literals but with `uint` we can support more of them. This could be used to implement decimal floating-point numbers.
- Since the integer part of a rational literal must fit in `uint`, positive exponents are not necessary. Any such integer can be represented with exponent=0.
- A signed exponent would be more natural for users since all valid values will be negative or zero. On the other hand it will likely require a cast to an unsigned number anyway. And half of the range will remain unused, forcing users to add `require()`s.
### 4. Range of mantissa
- Variant A: `uint`
- Variant B: `int`
With `int` we won't be able to represent the maximum signed integer.
### 5. Distinguishing between integers and fractions
For fractional numbers we need decomposition into the mantissa and exponent. For integers this is not necessary. How do we decide when to apply one or the other?
- Variant A: based on the literal
- `123 suffix` will call `function suffix(T) returns (T)`
- `123.0 suffix` will call `function suffix(uint mantissa, uint exponent) returns (T)`
- Variant B: based on the suffix
- `function suffix(T) returns (T)` will match `123 suffix`.
- `function suffix(uint mantissa, uint exponent) returns (T)` will match both `123 suffix` and `123.0 suffix`.
Variant B limits suffix overloading. Will have to produce an error when there's more than one overload that can handle a specific application of a suffix.
Variant A goes against the current literal semantics - `123` is considered to be the same exact number as `123.0` and distingushing them will require some changes in the parser.
### 6. Number and types of return values
Should suffixes returning zero values or more than one value be allowed? What types should be allowed for return values?
#### Zero return values
```solidity
function float(uint mantissa, uint exponent) pure {}
function test() {
1 float; // Useless
}
```
#### Multiple return values
```solidity
function float(uint mantissa, uint exponent) pure returns (uint, uint) {
return (mantissa, exponent);
}
function test() {
(uint a, uint b) = 1 float; // Works but still useless
1 float + 2 float; // Would require operators that can handle tuples
}
```
#### Struct return values
```solidity
struct Float {
uint mantissa;
uint exponent;
}
function float(uint mantissa, uint exponent) pure returns (Float memory) {
return Float(mantissa, exponent);
}
function test() {
Float memory f = 42 float; // Looks useful and compatible with operators
}
```
#### Function return values
```solidity
interface I {}
function f(uint x) external pure returns (uint);
function g(uint x) external pure returns (uint);
}
function fun(uint x) pure returns (function (uint) returns (uint)) {
return (x == 0 ? I.f : I.g);
}
function test() {
(20 fun)(1); // Can this be useful? Should we disallow it just because it's not?
}
```