diff --git a/Changelog.md b/Changelog.md index 213ecd269..6227ada01 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index c95a419e6..a0f68104c 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -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. diff --git a/docs/grammar/SolidityLexer.g4 b/docs/grammar/SolidityLexer.g4 index 653255ae3..9250835dd 100644 --- a/docs/grammar/SolidityLexer.g4 +++ b/docs/grammar/SolidityLexer.g4 @@ -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'; diff --git a/docs/grammar/SolidityParser.g4 b/docs/grammar/SolidityParser.g4 index 415dd5985..92718b975 100644 --- a/docs/grammar/SolidityParser.g4 +++ b/docs/grammar/SolidityParser.g4 @@ -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; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index b6a30e664..bdf138e20 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -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, diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4bf30d4eb..43334563b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -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 const& path: _usingFor.functionsOrLibrary()) { solAssert(path->annotation().referencedDeclaration); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 613d91ad9..4eba73c5d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -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> _functions, bool _usesBraces, - ASTPointer _typeName + ASTPointer _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> 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> m_functions; bool m_usesBraces; ASTPointer m_typeName; + bool m_global = false; }; class StructDefinition: public Declaration, public ScopeOpener diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index bd9ce129a..4338cc64f 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -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)); diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index ce0575fab..2f36cdead 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -359,7 +359,8 @@ ASTPointer ASTJsonImporter::createUsingForDirective(Json::Val _node, move(functions), !_node.isMember("libraryName"), - _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]) + _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]), + memberAsBool(_node, "global") ); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 29475fc4a..cedac68f3 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -342,6 +342,13 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc solAssert(sourceUnit, ""); usingForDirectives += ASTNode::filteredNodes(sourceUnit->nodes()); + if (Declaration const* typeDefinition = _type.typeDefinition()) + if (auto const* sourceUnit = dynamic_cast(typeDefinition->scope())) + for (auto usingFor: ASTNode::filteredNodes(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(&_type)) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 0ded8ea04..a1b49a81f 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -987,9 +987,15 @@ ASTPointer 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(move(functions), usesBraces, typeName); + return nodeFactory.createNode(move(functions), usesBraces, typeName, global); } ASTPointer Parser::parseModifierInvocation() diff --git a/test/libsolidity/ASTJSON/using_for_directive.json b/test/libsolidity/ASTJSON/using_for_directive.json index 37877ff17..c9052b0d7 100644 --- a/test/libsolidity/ASTJSON/using_for_directive.json +++ b/test/libsolidity/ASTJSON/using_for_directive.json @@ -33,6 +33,7 @@ } } ], + "global": false, "id": 3, "nodeType": "UsingForDirective", "src": "0:19:1", @@ -154,6 +155,7 @@ "nodes": [ { + "global": false, "id": 12, "libraryName": { diff --git a/test/libsolidity/ASTJSON/using_for_directive_parseOnly.json b/test/libsolidity/ASTJSON/using_for_directive_parseOnly.json index dcc5ce8d1..d704b4e81 100644 --- a/test/libsolidity/ASTJSON/using_for_directive_parseOnly.json +++ b/test/libsolidity/ASTJSON/using_for_directive_parseOnly.json @@ -17,6 +17,7 @@ } } ], + "global": false, "id": 3, "nodeType": "UsingForDirective", "src": "0:19:1", @@ -111,6 +112,7 @@ "nodes": [ { + "global": false, "id": 12, "libraryName": { diff --git a/test/libsolidity/semanticTests/using/recursive_import.sol b/test/libsolidity/semanticTests/using/recursive_import.sol new file mode 100644 index 000000000..09d27db60 --- /dev/null +++ b/test/libsolidity/semanticTests/using/recursive_import.sol @@ -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 diff --git a/test/libsolidity/semanticTests/using/using_global_for_global.sol b/test/libsolidity/semanticTests/using/using_global_for_global.sol new file mode 100644 index 000000000..7d7809d2a --- /dev/null +++ b/test/libsolidity/semanticTests/using/using_global_for_global.sol @@ -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 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/using/using_global_invisible.sol b/test/libsolidity/semanticTests/using/using_global_invisible.sol new file mode 100644 index 000000000..e4bf286ee --- /dev/null +++ b/test/libsolidity/semanticTests/using/using_global_invisible.sol @@ -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 diff --git a/test/libsolidity/semanticTests/using/using_global_library.sol b/test/libsolidity/semanticTests/using/using_global_library.sol new file mode 100644 index 000000000..747ed6bd0 --- /dev/null +++ b/test/libsolidity/semanticTests/using/using_global_library.sol @@ -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 diff --git a/test/libsolidity/syntaxTests/using/global_and_local.sol b/test/libsolidity/syntaxTests/using/global_and_local.sol new file mode 100644 index 000000000..7af04c0a6 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_and_local.sol @@ -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; +// ---- \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/using/global_for_asterisk.sol b/test/libsolidity/syntaxTests/using/global_for_asterisk.sol new file mode 100644 index 000000000..b5f6d8827 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_for_asterisk.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_for_non_user_defined.sol b/test/libsolidity/syntaxTests/using/global_for_non_user_defined.sol new file mode 100644 index 000000000..051874abc --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_for_non_user_defined.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_for_type_defined_elsewhere.sol b/test/libsolidity/syntaxTests/using/global_for_type_defined_elsewhere.sol new file mode 100644 index 000000000..232ffafad --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_for_type_defined_elsewhere.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_for_type_from_other_file.sol b/test/libsolidity/syntaxTests/using/global_for_type_from_other_file.sol new file mode 100644 index 000000000..458a644e9 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_for_type_from_other_file.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_inside_contract.sol b/test/libsolidity/syntaxTests/using/global_inside_contract.sol new file mode 100644 index 000000000..edaeea922 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_inside_contract.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_local_clash.sol b/test/libsolidity/syntaxTests/using/global_local_clash.sol new file mode 100644 index 000000000..1d149417f --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_local_clash.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_nonglobal.sol b/test/libsolidity/syntaxTests/using/global_nonglobal.sol new file mode 100644 index 000000000..972a34a8d --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_nonglobal.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/using/global_working.sol b/test/libsolidity/syntaxTests/using/global_working.sol new file mode 100644 index 000000000..944720245 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_working.sol @@ -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"; +// ---- \ No newline at end of file