Merge pull request #12288 from ethereum/exportUsing

Using global
This commit is contained in:
chriseth 2022-03-14 18:57:26 +01:00 committed by GitHub
commit e154d43176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 307 additions and 6 deletions

View File

@ -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:

View File

@ -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.

View File

@ -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';

View File

@ -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.
@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -641,6 +641,10 @@ private:
* 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
{
@ -650,9 +654,14 @@ public:
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}
{
}
@ -665,12 +674,14 @@ public:
/// @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

View File

@ -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));

View File

@ -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")
);
}

View File

@ -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))

View File

@ -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()

View File

@ -33,6 +33,7 @@
}
}
],
"global": false,
"id": 3,
"nodeType": "UsingForDirective",
"src": "0:19:1",
@ -154,6 +155,7 @@
"nodes":
[
{
"global": false,
"id": 12,
"libraryName":
{

View File

@ -17,6 +17,7 @@
}
}
],
"global": false,
"id": 3,
"nodeType": "UsingForDirective",
"src": "0:19:1",
@ -111,6 +112,7 @@
"nodes":
[
{
"global": false,
"id": 12,
"libraryName":
{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
// ----

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,7 @@
contract C {
using {f} for uint global;
}
function f(uint) pure{}
// ----
// SyntaxError 3367: (17-43): "global" can only be used at file level.
// TypeError 8841: (17-43): Can only use "global" with user-defined types.

View File

@ -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; }
function f1(S memory _x) pure returns (uint) { return _x.x + 1; }
==== Source: B ====
contract C {
// Here, f points to f1, so we end up with two different functions
// bound as S.f
using {f} for S;
function test() pure public
{
uint p = g().f();
}
}
import {gen as g, f1 as f, S} from "A";
import "A" as A;
// ----
// TypeError 6675: (B:181-186): Member "f" not unique after argument-dependent lookup in struct S memory.

View File

@ -0,0 +1,17 @@
==== Source: A ====
using {f} for S global;
using {g} for S;
struct S { uint x; }
function gen() pure returns (S memory) {}
function f(S memory _x) pure { _x.g(); }
function g(S memory _x) pure { }
==== Source: B ====
import "A";
function test() pure
{
gen().f();
gen().g();
}
// ----
// TypeError 9582: (B:54-61): Member "g" not found or not visible after argument-dependent lookup in struct S memory.

View File

@ -0,0 +1,15 @@
==== Source: A ====
using {f} for S global;
// this should not conflict
using {f} for S;
struct S { uint x; }
function gen() pure returns (S memory) {}
function f(S memory _x) pure returns (uint) { return _x.x; }
==== Source: B ====
function test() pure
{
uint p = g().f();
p++;
}
import {gen as g} from "A";
// ----