User-defined operators: Documentation

This commit is contained in:
wechman 2022-08-10 10:42:02 +02:00 committed by Kamil Śliwak
parent 1a83fa7ebc
commit 2e8d50eca2
3 changed files with 107 additions and 12 deletions

View File

@ -1,6 +1,7 @@
### 0.8.19 (unreleased) ### 0.8.19 (unreleased)
Language Features: Language Features:
* Allow defining custom operators for user-defined value types via ``using {f as +} for T global`` syntax.
Compiler Features: Compiler Features:
@ -16,6 +17,10 @@ Bugfixes:
* SMTChecker: Fix internal error caused by unhandled ``z3`` expressions that come from the solver when bitwise operators are used. * SMTChecker: Fix internal error caused by unhandled ``z3`` expressions that come from the solver when bitwise operators are used.
AST Changes:
* AST: Add ``function`` field to ``UnaryOperation`` and ``BinaryOperation`` AST nodes. ``functionList`` in ``UsingForDirective`` AST nodes will now contain ``operator`` and ``definition`` members instead of ``function`` when the list entry defines an operator.
### 0.8.18 (2023-02-01) ### 0.8.18 (2023-02-01)
Language Features: Language Features:

View File

@ -1,4 +1,4 @@
.. index:: ! using for, library .. index:: ! using for, library, ! operator; user-defined
.. _using-for: .. _using-for:
@ -6,37 +6,87 @@
Using For Using For
********* *********
The directive ``using A for B;`` can be used to attach The directive ``using A for B`` can be used to attach
functions (``A``) as member functions to any type (``B``). functions (``A``) as operators to user-defined value types
These functions will receive the object they are called on 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). 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, It is valid either at file level or inside a contract,
at contract level. at contract level.
The first part, ``A``, can be one of: The first part, ``A``, can be one of:
- A list of file-level or library functions (e.g. ``using {f, g, h, L.t} for uint;``) - - A list of functions, optionally with an operator name assigned (e.g.
only those functions will be attached to the type as member functions. ``using {f, g as +, h, L.t} for uint``).
Note that private library functions can only be specified when ``using for`` is inside the library. If no operator is specified, the function can be either a library function or a free function and
- The name of a library (e.g. ``using L for uint;``) - is attached to the type as a member function.
all non-private functions of the library are attached to the type. Otherwise it must be a free function and it becomes the definition of that operator on the type.
- The name of a library (e.g. ``using L for uint``) -
all non-private functions of the library are attached to the type
as member functions
At file level, the second part, ``B``, has to be an explicit type (without data location specifier). 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 *;``), Inside contracts, you can also use ``*`` in place of the type (e.g. ``using L for *;``),
which has the effect that all functions of the library ``L`` which has the effect that all functions of the library ``L``
are attached to *all* types. are attached to *all* types.
If you specify a library, *all* functions in the library get attached, If you specify a library, *all* non-private functions in the library get attached,
even those where the type of the first parameter does not even those where the type of the first parameter does not
match the type of the object. The type is checked at the match the type of the object. The type is checked at the
point the function is called and function overload point the function is called and function overload
resolution is performed. resolution is performed.
If you use a list of functions (e.g. ``using {f, g, h, L.t} for uint;``), If you use a list of functions (e.g. ``using {f, g, h, L.t} for uint``),
then the type (``uint``) has to be implicitly convertible to the then the type (``uint``) has to be implicitly convertible to the
first parameter of each of these functions. This check is first parameter of each of these functions. This check is
performed even if none of these functions are called. performed even if none of these functions are called.
Note that private library functions can only be specified when ``using for`` is inside a library.
If you define an operator (e.g. ``using {f as +} for T``), then the type (``T``) must be a
:ref:`user-defined value type <user-defined-value-types>` and the definition must be a ``pure`` function.
Operator definitions must be global.
The following operators can be defined this way:
+------------+----------+---------------------------------------------+
| Category | Operator | Possible signatures |
+============+==========+=============================================+
| Bitwise | ``&`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``|`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``^`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``~`` | ``function (T) pure returns (T)`` |
+------------+----------+---------------------------------------------+
| Arithmetic | ``+`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``-`` | ``function (T, T) pure returns (T)`` |
| + +---------------------------------------------+
| | | ``function (T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``*`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``/`` | ``function (T, T) pure returns (T)`` |
| +----------+---------------------------------------------+
| | ``%`` | ``function (T, T) pure returns (T)`` |
+------------+----------+---------------------------------------------+
| Comparison | ``==`` | ``function (T, T) pure returns (bool)`` |
| +----------+---------------------------------------------+
| | ``!=`` | ``function (T, T) pure returns (bool)`` |
| +----------+---------------------------------------------+
| | ``<`` | ``function (T, T) pure returns (bool)`` |
| +----------+---------------------------------------------+
| | ``<=`` | ``function (T, T) pure returns (bool)`` |
| +----------+---------------------------------------------+
| | ``>`` | ``function (T, T) pure returns (bool)`` |
| +----------+---------------------------------------------+
| | ``>=`` | ``function (T, T) pure returns (bool)`` |
+------------+----------+---------------------------------------------+
Note that unary and binary ``-`` need separate definitions.
The compiler will choose the right definition based on how the operator is invoked.
The ``using A for B;`` directive is active only within the current The ``using A for B;`` directive is active only within the current
scope (either the contract or the current module/source unit), scope (either the contract or the current module/source unit),
@ -46,7 +96,7 @@ outside of the contract or module in which it is used.
When the directive is used at file level and applied to a 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, 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 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 the type is available (including other files), not only in the
scope of the using statement. scope of the using statement.
@ -150,3 +200,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 ``self`` variable. The only situation where no copy will be performed
is when storage reference variables are used or when internal library is when storage reference variables are used or when internal library
functions are called. 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.19;
type UFixed16x2 is uint16;
using {
add as +,
div as /
} for UFixed16x2 global;
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,17 @@ private:
* 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`. * 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`.
* 2. `using LibraryName for *` attaches to all types. * 2. `using LibraryName for *` attaches to all types.
* 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ..., `fn`, respectively to `T`. * 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 * 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 * 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. * 2, this check is only done when a function is called.
* *
* For version 4, T has to be user-defined value type and the function must be pure.
* All parameters and return value of all the functions have to be of type T.
* This version can be combined with version 3 - a single directive may attach functions to the
* type and define operators on it at the same time.
*
* Finally, `using {f1, f2, ..., fn} for T global` is also valid at file level, as long as T is * 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 * 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. * attached to all objects of that type regardless of scope.