Skip to content

Commit

Permalink
Extend using-for.
Browse files Browse the repository at this point in the history
  • Loading branch information
hrkrshnn authored and chriseth committed Mar 14, 2022
1 parent db30f4d commit 672951c
Show file tree
Hide file tree
Showing 55 changed files with 993 additions and 151 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Language Features:
* General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
* 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.


Compiler Features:
Expand Down
107 changes: 63 additions & 44 deletions docs/contracts/using-for.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,89 @@
Using For
*********

The directive ``using A for B;`` can be used to attach library
functions (from the library ``A``) to any type (``B``)
in the context of a contract.
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
as their first parameter (like the ``self`` variable in Python).

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

In both situations, *all* functions in the library are attached,
The first part, ``A``, can be one of:

- a list of file-level or library functions (``using {f, g, h, L.t} for uint;``) -
only those functions will be attached to the type.
- the name of a library (``using L for uint;``) -
all functions (both public and internal ones) of the library are attached to the type

At file level, the second part, ``B``, has to be an explicit type (without data location specifier).
Inside contracts, you can also use ``using L for *;``,
which has the effect that all functions of the library ``L``
are attached to *all* types.

If you specify a library, *all* functions in the library are attached,
even those where the type of the first parameter does not
match the type of the object. The type is checked at the
point the function is called and function overload
resolution is performed.

If you use a list of functions (``using {f, g, h, L.t} for uint;``),
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.

The ``using A for B;`` directive is active only within the current
contract, including within all of its functions, and has no effect
outside of the contract in which it is used. The directive
may only be used inside a contract, not inside any of its functions.
scope (either the contract or the current module/source unit),
including within all of its functions, and has no effect
outside of the contract or module in which it is used.

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

.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
pragma solidity ^0.8.13;
// This is the same code as before, just without comments
struct Data { mapping(uint => bool) flags; }
// Now we attach functions to the type.
// The attached functions can be used throughout the rest of the module.
// If you import the module, you have to
// repeat the using directive there, for example as
// import "flags.sol" as Flags;
// using {Flags.insert, Flags.remove, Flags.contains}
// for Flags.Data;
using {insert, remove, contains} for Data;
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
library Set {
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
contract C {
using Set for Data; // this is the crucial change
Data knownValues;
function register(uint value) public {
Expand All @@ -82,12 +100,13 @@ Let us rewrite the set example from the
}
}
It is also possible to extend elementary types in that way:
It is also possible to extend built-in types in that way.
In this example, we will use a library.

.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.8 <0.9.0;
pragma solidity ^0.8.13;
library Search {
function indexOf(uint[] storage self, uint value)
Expand All @@ -100,9 +119,9 @@ It is also possible to extend elementary types in that way:
return type(uint).max;
}
}
using Search for uint[];
contract C {
using Search for uint[];
uint[] data;
function append(uint value) public {
Expand Down
7 changes: 4 additions & 3 deletions docs/grammar/SolidityParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ options { tokenVocab=SolidityLexer; }
sourceUnit: (
pragmaDirective
| importDirective
| usingDirective
| contractDefinition
| interfaceDefinition
| libraryDefinition
Expand Down Expand Up @@ -311,10 +312,10 @@ errorDefinition:
Semicolon;

/**
* Using directive to bind library functions to types.
* Can occur within contracts and libraries.
* Using directive to bind library functions and free functions to types.
* Can occur within contracts and libraries and at the file level.
*/
usingDirective: Using identifierPath For (Mul | typeName) Semicolon;
usingDirective: Using (identifierPath | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Semicolon;
/**
* A type name can be an elementary type, a function type, a mapping type, a user-defined type
* (e.g. a contract or struct) or an array type.
Expand Down
38 changes: 33 additions & 5 deletions libsolidity/analysis/DeclarationTypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <liblangutil/ErrorReporter.h>

#include <libsolutil/Algorithms.h>
#include <libsolutil/Visitor.h>

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

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

bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor)
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.libraryName().annotation().referencedDeclaration
);
if (_usingFor.usesBraces())
{
for (ASTPointer<IdentifierPath> const& function: _usingFor.functionsOrLibrary())
if (auto functionDefinition = dynamic_cast<FunctionDefinition const*>(function->annotation().referencedDeclaration))
{
if (!functionDefinition->isFree() && !(
dynamic_cast<ContractDefinition const*>(functionDefinition->scope()) &&
dynamic_cast<ContractDefinition const*>(functionDefinition->scope())->isLibrary()
))
m_errorReporter.typeError(
4167_error,
function->location(),
"Only file-level functions and library functions can be bound to a type in a \"using\" statement"
);
}
else
m_errorReporter.fatalTypeError(8187_error, function->location(), "Expected function name." );
}
else
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
m_errorReporter.fatalTypeError(
4357_error,
_usingFor.functionsOrLibrary().front()->location(),
"Library name expected. If you want to attach a function, use '{...}'."
);
}

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

if (_usingFor.typeName())
_usingFor.typeName()->accept(*this);
Expand Down
24 changes: 24 additions & 0 deletions libsolidity/analysis/SyntaxChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,30 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
m_currentContractKind = std::nullopt;
}

bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
{
if (!m_currentContractKind && !_usingFor.typeName())
m_errorReporter.syntaxError(
8118_error,
_usingFor.location(),
"The type has to be specified explicitly at file level (cannot use '*')."
);
else if (_usingFor.usesBraces() && !_usingFor.typeName())
m_errorReporter.syntaxError(
3349_error,
_usingFor.location(),
"The type has to be specified explicitly when attaching specific functions."
);
if (m_currentContractKind == ContractKind::Interface)
m_errorReporter.syntaxError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
);

return true;
}

bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");
Expand Down
3 changes: 3 additions & 0 deletions libsolidity/analysis/SyntaxChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ class SyntaxChecker: private ASTConstVisitor

bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;

bool visit(UsingForDirective const& _usingFor) override;

bool visit(FunctionDefinition const& _function) override;
bool visit(FunctionTypeName const& _node) override;

Expand Down
66 changes: 61 additions & 5 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/Views.h>
#include <libsolutil/Visitor.h>

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

void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{
if (m_currentContract->isInterface())
m_errorReporter.typeError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
if (!_usingFor.usesBraces())
{
solAssert(_usingFor.functionsOrLibrary().size() == 1);
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
);
solAssert(library && library->isLibrary());
// No type checking for libraries
return;
}

if (!_usingFor.typeName())
{
solAssert(m_errorReporter.hasErrors());
return;
}

solAssert(_usingFor.typeName()->annotation().type);
Type const* normalizedType = TypeProvider::withLocationIfReference(
DataLocation::Storage,
_usingFor.typeName()->annotation().type
);
solAssert(normalizedType);

for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
{
solAssert(path->annotation().referencedDeclaration);
FunctionDefinition const& functionDefinition =
dynamic_cast<FunctionDefinition const&>(*path->annotation().referencedDeclaration);

solAssert(functionDefinition.type());

if (functionDefinition.parameters().empty())
m_errorReporter.fatalTypeError(
4731_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" " +
"does not have any parameters, and therefore cannot be bound to the type \"" +
(normalizedType ? normalizedType->toString(true) : "*") + "\"."
);

FunctionType const* functionType = dynamic_cast<FunctionType const&>(*functionDefinition.type()).asBoundFunction();
solAssert(functionType && functionType->selfType(), "");
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionType->selfType())
);
if (!result)
m_errorReporter.typeError(
3100_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
"cannot be bound to the type \"" + _usingFor.typeName()->annotation().type->toString() +
"\" because the type cannot be implicitly converted to the first argument" +
" of the function (\"" + functionType->selfType()->toString() + "\")" +
(
result.message().empty() ?
"." :
": " + result.message()
)
);
}
}

void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)
Expand Down
Loading

0 comments on commit 672951c

Please sign in to comment.