Skip to content

Commit

Permalink
Merge pull request #12288 from ethereum/exportUsing
Browse files Browse the repository at this point in the history
Using global
  • Loading branch information
chriseth authored Mar 14, 2022
2 parents e19c366 + 9188519 commit e154d43
Show file tree
Hide file tree
Showing 26 changed files with 307 additions and 6 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,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.
* General: ``using ... for T global;`` is allowed at file level where the user-defined type ``T`` has been defined, resulting in the effect of the statement being available everywhere ``T`` is available.


Compiler Features:
Expand Down
7 changes: 7 additions & 0 deletions docs/contracts/using-for.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ 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.

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
the type is available (including other files), not only in the
scope of the using statement.

Let us rewrite the set example from the
:ref:`libraries` section in this way, using file-level functions
instead of library functions.
Expand Down
1 change: 1 addition & 0 deletions docs/grammar/SolidityLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ FixedBytes:
'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32';
For: 'for';
Function: 'function';
Global: 'global'; // not a real keyword
Hex: 'hex';
If: 'if';
Immutable: 'immutable';
Expand Down
4 changes: 2 additions & 2 deletions docs/grammar/SolidityParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ errorDefinition:
* 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 | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Semicolon;
usingDirective: Using (identifierPath | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Global? 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 Expand Up @@ -389,7 +389,7 @@ inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;
/**
* Besides regular non-keyword Identifiers, some keywords like 'from' and 'error' can also be used as identifiers.
*/
identifier: Identifier | From | Error | Revert;
identifier: Identifier | From | Error | Revert | Global;

literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
booleanLiteral: True | False;
Expand Down
12 changes: 12 additions & 0 deletions libsolidity/analysis/SyntaxChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,18 @@ bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
_usingFor.location(),
"The type has to be specified explicitly when attaching specific functions."
);
if (_usingFor.global() && !_usingFor.typeName())
m_errorReporter.syntaxError(
2854_error,
_usingFor.location(),
"Can only globally bind functions to specific types."
);
if (_usingFor.global() && m_currentContractKind)
m_errorReporter.syntaxError(
3367_error,
_usingFor.location(),
"\"global\" can only be used at file level."
);
if (m_currentContractKind == ContractKind::Interface)
m_errorReporter.syntaxError(
9088_error,
Expand Down
22 changes: 22 additions & 0 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3656,6 +3656,28 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
);
solAssert(normalizedType);

if (_usingFor.global())
{
if (m_currentContract)
solAssert(m_errorReporter.hasErrors());
if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition())
{
if (typeDefinition->scope() != m_currentSourceUnit)
m_errorReporter.typeError(
4117_error,
_usingFor.location(),
"Can only use \"global\" with types defined in the same source unit at file level."
);
}
else
m_errorReporter.typeError(
8841_error,
_usingFor.location(),
"Can only use \"global\" with user-defined types."
);
}


for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
{
solAssert(path->annotation().referencedDeclaration);
Expand Down
15 changes: 13 additions & 2 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,10 @@ class InheritanceSpecifier: public ASTNode
* 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.
*
* 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.
*/
class UsingForDirective: public ASTNode
{
Expand All @@ -650,9 +654,14 @@ class UsingForDirective: public ASTNode
SourceLocation const& _location,
std::vector<ASTPointer<IdentifierPath>> _functions,
bool _usesBraces,
ASTPointer<TypeName> _typeName
ASTPointer<TypeName> _typeName,
bool _global
):
ASTNode(_id, _location), m_functions(_functions), m_usesBraces(_usesBraces), m_typeName(std::move(_typeName))
ASTNode(_id, _location),
m_functions(_functions),
m_usesBraces(_usesBraces),
m_typeName(std::move(_typeName)),
m_global{_global}
{
}

Expand All @@ -665,12 +674,14 @@ class UsingForDirective: public ASTNode
/// @returns a list of functions or the single library.
std::vector<ASTPointer<IdentifierPath>> const& functionsOrLibrary() const { return m_functions; }
bool usesBraces() const { return m_usesBraces; }
bool global() const { return m_global; }

private:
/// Either the single library or a list of functions.
std::vector<ASTPointer<IdentifierPath>> m_functions;
bool m_usesBraces;
ASTPointer<TypeName> m_typeName;
bool m_global = false;
};

class StructDefinition: public Declaration, public ScopeOpener
Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ bool ASTJsonConverter::visit(UsingForDirective const& _node)
}
else
attributes.emplace_back("libraryName", toJson(*_node.functionsOrLibrary().front()));
attributes.emplace_back("global", _node.global());

setJsonNode(_node, "UsingForDirective", move(attributes));

Expand Down
3 changes: 2 additions & 1 deletion libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ ASTPointer<UsingForDirective> ASTJsonImporter::createUsingForDirective(Json::Val
_node,
move(functions),
!_node.isMember("libraryName"),
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"])
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"]),
memberAsBool(_node, "global")
);
}

Expand Down
7 changes: 7 additions & 0 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc
solAssert(sourceUnit, "");
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());

if (Declaration const* typeDefinition = _type.typeDefinition())
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(typeDefinition->scope()))
for (auto usingFor: ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes()))
// We do not yet compare the type name because of normalization.
if (usingFor->global() && usingFor->typeName())
usingForDirectives.emplace_back(usingFor);

// Normalise data location of type.
DataLocation typeLocation = DataLocation::Storage;
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
Expand Down
8 changes: 7 additions & 1 deletion libsolidity/parsing/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,9 +987,15 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
advance();
else
typeName = parseTypeName();
bool global = false;
if (m_scanner->currentToken() == Token::Identifier && currentLiteral() == "global")
{
global = true;
advance();
}
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UsingForDirective>(move(functions), usesBraces, typeName);
return nodeFactory.createNode<UsingForDirective>(move(functions), usesBraces, typeName, global);
}

ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
Expand Down
2 changes: 2 additions & 0 deletions test/libsolidity/ASTJSON/using_for_directive.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
}
}
],
"global": false,
"id": 3,
"nodeType": "UsingForDirective",
"src": "0:19:1",
Expand Down Expand Up @@ -154,6 +155,7 @@
"nodes":
[
{
"global": false,
"id": 12,
"libraryName":
{
Expand Down
2 changes: 2 additions & 0 deletions test/libsolidity/ASTJSON/using_for_directive_parseOnly.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
}
}
],
"global": false,
"id": 3,
"nodeType": "UsingForDirective",
"src": "0:19:1",
Expand Down Expand Up @@ -111,6 +112,7 @@
"nodes":
[
{
"global": false,
"id": 12,
"libraryName":
{
Expand Down
25 changes: 25 additions & 0 deletions test/libsolidity/semanticTests/using/recursive_import.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
==== Source: A ====
import {T as U} from "A";
import "A" as X;

type T is uint;
function f(T x) pure returns (T) { return T.wrap(T.unwrap(x) + 1); }
function g(T x) pure returns (uint) { return T.unwrap(x) + 10; }

using { f } for X.X.U global;
using { g } for T global;

function cr() pure returns (T) {}

==== Source: B ====
import { cr } from "A";

contract C {
function f() public returns (uint) {
return cr().f().g();
}
}
// ====
// compileViaYul: also
// ----
// f() -> 11
20 changes: 20 additions & 0 deletions test/libsolidity/semanticTests/using/using_global_for_global.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
==== Source: A ====
type global is uint;
using { f } for global global;
function f(global x) pure returns (global) { return global.wrap(global.unwrap(x) + 1); }
==== Source: B ====
import { global } from "A";

function g(global x) pure returns (global) { return global.wrap(global.unwrap(x) + 10); }

contract C {
using { g } for global;
function f(global r) public pure returns (global) {
return r.f().g();
}
}

// ====
// compileViaYul: also
// ----
// f(uint256): 100 -> 111
45 changes: 45 additions & 0 deletions test/libsolidity/semanticTests/using/using_global_invisible.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
==== Source: A ====
type T is uint;
using L for T global;
library L {
function inc(T x) internal pure returns (T) {
return T.wrap(T.unwrap(x) + 1);
}
function dec(T x) external pure returns (T) {
return T.wrap(T.unwrap(x) - 1);
}
}
using {unwrap} for T global;
function unwrap(T x) pure returns (uint) {
return T.unwrap(x);
}

==== Source: B ====
contract C {
function f() public pure returns (T r1) {
r1 = r1.inc().inc();
}
}

import {T} from "A";

==== Source: C ====
import {C} from "B";

contract D {
function test() public returns (uint) {
C c = new C();
// This tests that bound functions are available
// even if the type is not available by name.
// This is a regular function call, a
// public and an internal library call
// and a free function call.
return c.f().inc().inc().dec().unwrap();
}
}
// ====
// compileViaYul: also
// ----
// library: "A":L
// test() -> 3
// gas legacy: 130369
27 changes: 27 additions & 0 deletions test/libsolidity/semanticTests/using/using_global_library.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
==== Source: A ====
type T is uint;
using L for T global;
library L {
function inc(T x) internal pure returns (T) {
return T.wrap(T.unwrap(x) + 1);
}
function dec(T x) external pure returns (T) {
return T.wrap(T.unwrap(x) - 1);
}
}

==== Source: B ====
contract C {
function f() public pure returns (T r1, T r2) {
r1 = r1.inc().inc();
r2 = r1.dec();
}
}

import {T} from "A";

// ====
// compileViaYul: also
// ----
// library: "A":L
// f() -> 2, 1
21 changes: 21 additions & 0 deletions test/libsolidity/syntaxTests/using/global_and_local.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
==== Source: A ====
using {f} for S global;
struct S { uint x; }
function gen() pure returns (S memory) {}
function f(S memory _x) pure returns (uint) { return _x.x; }
==== Source: B ====
contract C {
using {fun} for S;
// Adds the same function again with the same name,
// so it's fine.
using {A.f} for S;

function test() pure public
{
uint p = g().f();
p = g().fun();
}
}
import {gen as g, f as fun, S} from "A";
import "A" as A;
// ----
5 changes: 5 additions & 0 deletions test/libsolidity/syntaxTests/using/global_for_asterisk.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using {f} for * global;
function f(uint) pure{}
// ----
// SyntaxError 8118: (0-23): The type has to be specified explicitly at file level (cannot use '*').
// SyntaxError 2854: (0-23): Can only globally bind functions to specific types.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using {f} for uint global;
function f(uint) pure{}
// ----
// TypeError 8841: (0-26): Can only use "global" with user-defined types.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using {f} for L.S global;
function f(L.S memory) pure{}
library L {
struct S { uint x; }
}
// ----
// TypeError 4117: (0-25): Can only use "global" with types defined in the same source unit at file level.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
==== Source: A ====
struct S { uint x; }
==== Source: B ====

using {f} for S global;
using {f} for A.S global;

function f(S memory) pure{}

import {S} from "A";
import "A" as A;
// ----
// TypeError 4117: (B:1-24): Can only use "global" with types defined in the same source unit at file level.
// TypeError 4117: (B:25-50): Can only use "global" with types defined in the same source unit at file level.
Loading

0 comments on commit e154d43

Please sign in to comment.