Skip to content

Commit 3f6beaa

Browse files
authored
Merge pull request #12121 from ethereum/extend-using-statement
Extend using statement
2 parents 63e65a6 + 672951c commit 3f6beaa

File tree

55 files changed

+993
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+993
-151
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Language Features:
44
* General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
5+
* General: ``using M for Type;`` is allowed at file level and ``M`` can now also be a brace-enclosed list of free functions or library functions.
56

67

78
Compiler Features:

docs/contracts/using-for.rst

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,89 @@
66
Using For
77
*********
88

9-
The directive ``using A for B;`` can be used to attach library
10-
functions (from the library ``A``) to any type (``B``)
11-
in the context of a contract.
9+
The directive ``using A for B;`` can be used to attach
10+
functions (``A``) as member functions to any type (``B``).
1211
These functions will receive the object they are called on
1312
as their first parameter (like the ``self`` variable in Python).
1413

15-
The effect of ``using A for *;`` is that the functions from
16-
the library ``A`` are attached to *any* type.
14+
It is valid either at file level or inside a contract,
15+
at contract level.
1716

18-
In both situations, *all* functions in the library are attached,
17+
The first part, ``A``, can be one of:
18+
19+
- a list of file-level or library functions (``using {f, g, h, L.t} for uint;``) -
20+
only those functions will be attached to the type.
21+
- the name of a library (``using L for uint;``) -
22+
all functions (both public and internal ones) of the library are attached to the type
23+
24+
At file level, the second part, ``B``, has to be an explicit type (without data location specifier).
25+
Inside contracts, you can also use ``using L for *;``,
26+
which has the effect that all functions of the library ``L``
27+
are attached to *all* types.
28+
29+
If you specify a library, *all* functions in the library are attached,
1930
even those where the type of the first parameter does not
2031
match the type of the object. The type is checked at the
2132
point the function is called and function overload
2233
resolution is performed.
2334

35+
If you use a list of functions (``using {f, g, h, L.t} for uint;``),
36+
then the type (``uint``) has to be implicitly convertible to the
37+
first parameter of each of these functions. This check is
38+
performed even if none of these functions are called.
39+
2440
The ``using A for B;`` directive is active only within the current
25-
contract, including within all of its functions, and has no effect
26-
outside of the contract in which it is used. The directive
27-
may only be used inside a contract, not inside any of its functions.
41+
scope (either the contract or the current module/source unit),
42+
including within all of its functions, and has no effect
43+
outside of the contract or module in which it is used.
2844

2945
Let us rewrite the set example from the
30-
:ref:`libraries` in this way:
46+
:ref:`libraries` section in this way, using file-level functions
47+
instead of library functions.
3148

3249
.. code-block:: solidity
3350
3451
// SPDX-License-Identifier: GPL-3.0
35-
pragma solidity >=0.6.0 <0.9.0;
36-
52+
pragma solidity ^0.8.13;
3753
38-
// This is the same code as before, just without comments
3954
struct Data { mapping(uint => bool) flags; }
55+
// Now we attach functions to the type.
56+
// The attached functions can be used throughout the rest of the module.
57+
// If you import the module, you have to
58+
// repeat the using directive there, for example as
59+
// import "flags.sol" as Flags;
60+
// using {Flags.insert, Flags.remove, Flags.contains}
61+
// for Flags.Data;
62+
using {insert, remove, contains} for Data;
63+
64+
function insert(Data storage self, uint value)
65+
returns (bool)
66+
{
67+
if (self.flags[value])
68+
return false; // already there
69+
self.flags[value] = true;
70+
return true;
71+
}
4072
41-
library Set {
42-
function insert(Data storage self, uint value)
43-
public
44-
returns (bool)
45-
{
46-
if (self.flags[value])
47-
return false; // already there
48-
self.flags[value] = true;
49-
return true;
50-
}
51-
52-
function remove(Data storage self, uint value)
53-
public
54-
returns (bool)
55-
{
56-
if (!self.flags[value])
57-
return false; // not there
58-
self.flags[value] = false;
59-
return true;
60-
}
73+
function remove(Data storage self, uint value)
74+
returns (bool)
75+
{
76+
if (!self.flags[value])
77+
return false; // not there
78+
self.flags[value] = false;
79+
return true;
80+
}
6181
62-
function contains(Data storage self, uint value)
63-
public
64-
view
65-
returns (bool)
66-
{
67-
return self.flags[value];
68-
}
82+
function contains(Data storage self, uint value)
83+
public
84+
view
85+
returns (bool)
86+
{
87+
return self.flags[value];
6988
}
7089
7190
7291
contract C {
73-
using Set for Data; // this is the crucial change
7492
Data knownValues;
7593
7694
function register(uint value) public {
@@ -82,12 +100,13 @@ Let us rewrite the set example from the
82100
}
83101
}
84102
85-
It is also possible to extend elementary types in that way:
103+
It is also possible to extend built-in types in that way.
104+
In this example, we will use a library.
86105

87106
.. code-block:: solidity
88107
89108
// SPDX-License-Identifier: GPL-3.0
90-
pragma solidity >=0.6.8 <0.9.0;
109+
pragma solidity ^0.8.13;
91110
92111
library Search {
93112
function indexOf(uint[] storage self, uint value)
@@ -100,9 +119,9 @@ It is also possible to extend elementary types in that way:
100119
return type(uint).max;
101120
}
102121
}
122+
using Search for uint[];
103123
104124
contract C {
105-
using Search for uint[];
106125
uint[] data;
107126
108127
function append(uint value) public {

docs/grammar/SolidityParser.g4

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ options { tokenVocab=SolidityLexer; }
1212
sourceUnit: (
1313
pragmaDirective
1414
| importDirective
15+
| usingDirective
1516
| contractDefinition
1617
| interfaceDefinition
1718
| libraryDefinition
@@ -311,10 +312,10 @@ errorDefinition:
311312
Semicolon;
312313

313314
/**
314-
* Using directive to bind library functions to types.
315-
* Can occur within contracts and libraries.
315+
* Using directive to bind library functions and free functions to types.
316+
* Can occur within contracts and libraries and at the file level.
316317
*/
317-
usingDirective: Using identifierPath For (Mul | typeName) Semicolon;
318+
usingDirective: Using (identifierPath | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Semicolon;
318319
/**
319320
* A type name can be an elementary type, a function type, a mapping type, a user-defined type
320321
* (e.g. a contract or struct) or an array type.

libsolidity/analysis/DeclarationTypeChecker.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <liblangutil/ErrorReporter.h>
2626

2727
#include <libsolutil/Algorithms.h>
28+
#include <libsolutil/Visitor.h>
2829

2930
#include <range/v3/view/transform.hpp>
3031

@@ -451,12 +452,39 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
451452

452453
bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor)
453454
{
454-
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
455-
_usingFor.libraryName().annotation().referencedDeclaration
456-
);
455+
if (_usingFor.usesBraces())
456+
{
457+
for (ASTPointer<IdentifierPath> const& function: _usingFor.functionsOrLibrary())
458+
if (auto functionDefinition = dynamic_cast<FunctionDefinition const*>(function->annotation().referencedDeclaration))
459+
{
460+
if (!functionDefinition->isFree() && !(
461+
dynamic_cast<ContractDefinition const*>(functionDefinition->scope()) &&
462+
dynamic_cast<ContractDefinition const*>(functionDefinition->scope())->isLibrary()
463+
))
464+
m_errorReporter.typeError(
465+
4167_error,
466+
function->location(),
467+
"Only file-level functions and library functions can be bound to a type in a \"using\" statement"
468+
);
469+
}
470+
else
471+
m_errorReporter.fatalTypeError(8187_error, function->location(), "Expected function name." );
472+
}
473+
else
474+
{
475+
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
476+
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
477+
);
478+
if (!library || !library->isLibrary())
479+
m_errorReporter.fatalTypeError(
480+
4357_error,
481+
_usingFor.functionsOrLibrary().front()->location(),
482+
"Library name expected. If you want to attach a function, use '{...}'."
483+
);
484+
}
457485

458-
if (!library || !library->isLibrary())
459-
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected.");
486+
// We do not visit _usingFor.functions() because it will lead to an error since
487+
// library names cannot be mentioned stand-alone.
460488

461489
if (_usingFor.typeName())
462490
_usingFor.typeName()->accept(*this);

libsolidity/analysis/SyntaxChecker.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,30 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
403403
m_currentContractKind = std::nullopt;
404404
}
405405

406+
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
407+
{
408+
if (!m_currentContractKind && !_usingFor.typeName())
409+
m_errorReporter.syntaxError(
410+
8118_error,
411+
_usingFor.location(),
412+
"The type has to be specified explicitly at file level (cannot use '*')."
413+
);
414+
else if (_usingFor.usesBraces() && !_usingFor.typeName())
415+
m_errorReporter.syntaxError(
416+
3349_error,
417+
_usingFor.location(),
418+
"The type has to be specified explicitly when attaching specific functions."
419+
);
420+
if (m_currentContractKind == ContractKind::Interface)
421+
m_errorReporter.syntaxError(
422+
9088_error,
423+
_usingFor.location(),
424+
"The \"using for\" directive is not allowed inside interfaces."
425+
);
426+
427+
return true;
428+
}
429+
406430
bool SyntaxChecker::visit(FunctionDefinition const& _function)
407431
{
408432
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");

libsolidity/analysis/SyntaxChecker.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class SyntaxChecker: private ASTConstVisitor
8888

8989
bool visit(ContractDefinition const& _contract) override;
9090
void endVisit(ContractDefinition const& _contract) override;
91+
92+
bool visit(UsingForDirective const& _usingFor) override;
93+
9194
bool visit(FunctionDefinition const& _function) override;
9295
bool visit(FunctionTypeName const& _node) override;
9396

libsolidity/analysis/TypeChecker.cpp

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <libsolutil/Algorithms.h>
3636
#include <libsolutil/StringUtils.h>
3737
#include <libsolutil/Views.h>
38+
#include <libsolutil/Visitor.h>
3839

3940
#include <boost/algorithm/string/join.hpp>
4041
#include <boost/algorithm/string/predicate.hpp>
@@ -3626,12 +3627,67 @@ void TypeChecker::endVisit(Literal const& _literal)
36263627

36273628
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
36283629
{
3629-
if (m_currentContract->isInterface())
3630-
m_errorReporter.typeError(
3631-
9088_error,
3632-
_usingFor.location(),
3633-
"The \"using for\" directive is not allowed inside interfaces."
3630+
if (!_usingFor.usesBraces())
3631+
{
3632+
solAssert(_usingFor.functionsOrLibrary().size() == 1);
3633+
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
3634+
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
3635+
);
3636+
solAssert(library && library->isLibrary());
3637+
// No type checking for libraries
3638+
return;
3639+
}
3640+
3641+
if (!_usingFor.typeName())
3642+
{
3643+
solAssert(m_errorReporter.hasErrors());
3644+
return;
3645+
}
3646+
3647+
solAssert(_usingFor.typeName()->annotation().type);
3648+
Type const* normalizedType = TypeProvider::withLocationIfReference(
3649+
DataLocation::Storage,
3650+
_usingFor.typeName()->annotation().type
3651+
);
3652+
solAssert(normalizedType);
3653+
3654+
for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
3655+
{
3656+
solAssert(path->annotation().referencedDeclaration);
3657+
FunctionDefinition const& functionDefinition =
3658+
dynamic_cast<FunctionDefinition const&>(*path->annotation().referencedDeclaration);
3659+
3660+
solAssert(functionDefinition.type());
3661+
3662+
if (functionDefinition.parameters().empty())
3663+
m_errorReporter.fatalTypeError(
3664+
4731_error,
3665+
path->location(),
3666+
"The function \"" + joinHumanReadable(path->path(), ".") + "\" " +
3667+
"does not have any parameters, and therefore cannot be bound to the type \"" +
3668+
(normalizedType ? normalizedType->toString(true) : "*") + "\"."
3669+
);
3670+
3671+
FunctionType const* functionType = dynamic_cast<FunctionType const&>(*functionDefinition.type()).asBoundFunction();
3672+
solAssert(functionType && functionType->selfType(), "");
3673+
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
3674+
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionType->selfType())
36343675
);
3676+
if (!result)
3677+
m_errorReporter.typeError(
3678+
3100_error,
3679+
path->location(),
3680+
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
3681+
"cannot be bound to the type \"" + _usingFor.typeName()->annotation().type->toString() +
3682+
"\" because the type cannot be implicitly converted to the first argument" +
3683+
" of the function (\"" + functionType->selfType()->toString() + "\")" +
3684+
(
3685+
result.message().empty() ?
3686+
"." :
3687+
": " + result.message()
3688+
)
3689+
);
3690+
}
36353691
}
36363692

36373693
void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)

0 commit comments

Comments
 (0)