User-defined operators: Documentation

This commit is contained in:
wechman 2022-08-10 10:42:02 +02:00 committed by Kamil Śliwak
parent c3d3285921
commit c583f56db7
3 changed files with 64 additions and 4 deletions

View File

@ -2,6 +2,7 @@
Language Features:
* Allow named parameters in mapping types.
* Allow defining custom operators for user-defined value types via ``using {f as +} for Typename;`` syntax.
Compiler Features:
@ -36,6 +37,10 @@ Bugfixes:
* TypeChecker: Fix bug where private library functions could be attached with ``using for`` outside of their declaration scope.
AST Changes:
* AST: Add ``function`` field to ``UnaryOperation`` and ``BinaryOperation`` AST nodes and ``functionList.operator`` field to ``UsingForDirective`` AST nodes.
### 0.8.17 (2022-09-08)
Important Bugfixes:

View File

@ -7,9 +7,11 @@ Using For
*********
The directive ``using A for B;`` can be used to attach
functions (``A``) as member functions to any type (``B``).
These functions will receive the object they are called on
functions (``A``) as operators to user-defined value types
or as member functions to any type (``B``).
The member functions receive the object they are called on
as their first parameter (like the ``self`` variable in Python).
The operator functions receive operands as parameters.
It is valid either at file level or inside a contract,
at contract level.
@ -20,7 +22,15 @@ The first part, ``A``, can be one of:
only those functions will be attached to the type as member functions.
Note that private library functions can only be specified when ``using for`` is inside the library.
- The name of a library (e.g. ``using L for uint;``) -
all non-private functions of the library are attached to the type.
all non-private functions of the library are attached to the type
as member functions
- a list of assignments of file-level or public/internal/private library functions to operators
(e.g. ``using {f as +, g as -} for T;``) - the functions will be attached to the type (``T``)
as operators. The following binary operators are allowed to be used on the list: ``|``,
``^``, ``&``, ``+``, ``-``, ``*``, ``/``, ``%``, ``==``, ``!=``, ``<``, ``>``, ``<=``,
``>=``, ``<<``, ``>>``, ``**``. Allowed unary operators are: ``~``, ``!``, ``-``.
If an operator can be both binary and unary, it is allowed to have each variant specified
on the list (e.g. ``using {sub as -, unsub as -} for T``).
At file level, the second part, ``B``, has to be an explicit type (without data location specifier).
Inside contracts, you can also use ``*`` in place of the type (e.g. ``using L for *;``),
@ -38,6 +48,13 @@ then the type (``uint``) has to be implicitly convertible to the
first parameter of each of these functions. This check is
performed even if none of these functions are called.
If you define an operator for a user-defined type (``using {f as +} for T``), then
the type (``T``), types of function parameters and the type of the function return value
have to be the same. The type (``T``) does not include data location.
But, data location of the function parameters and function return value must be
the same. There is an exception for comparison operators for which, the return value
type is always ``bool``.
The ``using A for B;`` directive is active only within the current
scope (either the contract or the current module/source unit),
including within all of its functions, and has no effect
@ -46,7 +63,7 @@ outside of the contract or module in which it is used.
When the directive is used at file level and applied to a
user-defined type which was defined at file level in the same file,
the word ``global`` can be added at the end. This will have the
effect that the functions are attached to the type everywhere
effect that the functions and operators are attached to the type everywhere
the type is available (including other files), not only in the
scope of the using statement.
@ -150,3 +167,37 @@ if you pass memory or value types, a copy will be performed, even in case of the
``self`` variable. The only situation where no copy will be performed
is when storage reference variables are used or when internal library
functions are called.
Another example shows how to define a custom operator for a user-defined type:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;
type UFixed16x2 is uint16;
using {
add as +,
div as /
} for UFixed16x2;
uint32 constant SCALE = 100;
function add(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) {
return UFixed16x2.wrap(UFixed16x2.unwrap(a) + UFixed16x2.unwrap(b));
}
function div(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) {
uint32 a32 = UFixed16x2.unwrap(a);
uint32 b32 = UFixed16x2.unwrap(b);
uint32 result32 = a32 * SCALE / b32;
require(result32 <= type(uint16).max, "Divide overflow");
return UFixed16x2.wrap(uint16(a32 * SCALE / b32));
}
contract Math {
function avg(UFixed16x2 a, UFixed16x2 b) public pure returns (UFixed16x2) {
return (a + b) / UFixed16x2.wrap(200);
}
}

View File

@ -647,11 +647,15 @@ private:
* 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`.
* 2. `using LibraryName for *` attaches to all types.
* 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ..., `fn`, respectively to `T`.
* 4. `using {f1 as op1, f2 as op2, ..., fn as opn} for T` implements operator `opn` for type `T` with function `fn`.
*
* For version 3, T has to be implicitly convertible to the first parameter type of
* all functions, and this is checked at the point of the using statement. For versions 1 and
* 2, this check is only done when a function is called.
*
* For version 4, T has to be user-defined value type. All parameters and
* return value of all the functions have to be of type T.
*
* Finally, `using {f1, f2, ..., fn} for T global` is also valid at file level, as long as T is
* a user-defined type defined in the same file at file level. In this case, the methods are
* attached to all objects of that type regardless of scope.