diff --git a/Changelog.md b/Changelog.md index 2877bbf8c..eee92eca0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index 20299e1a4..18f0f2d7a 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -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); + } + } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 80fd68e0e..7181bef4d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -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.