From 84b68006ba765bc9608c8a0bf6e85c5de5260fb7 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 19 Mar 2019 17:12:21 +0100 Subject: [PATCH] Fix function calls with named arguments for overloaded functions --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 23 +++++++----- libsolidity/ast/ASTAnnotations.h | 10 +++-- libsolidity/ast/ASTEnums.h | 16 ++++++++ libsolidity/ast/ASTJsonConverter.cpp | 8 ++-- libsolidity/ast/ASTJsonConverter.h | 2 +- libsolidity/ast/Types.cpp | 37 ++++++++++++++++--- libsolidity/ast/Types.h | 8 +++- .../functionCall/named_args_overload.sol | 32 ++++++++++++++++ .../named_arguments_overload.sol | 14 +++++++ .../named_arguments_overload_failing1.sol | 14 +++++++ .../named_arguments_overload_failing2.sol | 12 ++++++ .../named_arguments_overload_failing3.sol | 11 ++++++ 13 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 test/libsolidity/semanticTests/functionCall/named_args_overload.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol diff --git a/Changelog.md b/Changelog.md index 26995cf73..f1878b4d5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Function calls with named arguments now work with overloaded functions. * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index ce73164a1..d1e4516b0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1807,13 +1807,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) argumentsArePure = false; } - // For positional calls only, store argument types - if (_functionCall.names().empty()) + // Store argument types - and names if given - for overload resolution { - shared_ptr argumentTypes = make_shared(); + FuncCallArguments funcCallArgs; + + funcCallArgs.names = _functionCall.names(); + for (ASTPointer const& argument: arguments) - argumentTypes->push_back(type(*argument)); - _functionCall.expression().annotation().argumentTypes = move(argumentTypes); + funcCallArgs.types.push_back(type(*argument)); + + _functionCall.expression().annotation().arguments = std::move(funcCallArgs); } _functionCall.expression().accept(*this); @@ -2010,16 +2013,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ASTString const& memberName = _memberAccess.memberName(); // Retrieve the types of the arguments if this is used to call a function. - auto const& argumentTypes = _memberAccess.annotation().argumentTypes; + auto const& arguments = _memberAccess.annotation().arguments; MemberList::MemberMap possibleMembers = exprType->members(m_scope).membersByName(memberName); size_t const initialMemberCount = possibleMembers.size(); - if (initialMemberCount > 1 && argumentTypes) + if (initialMemberCount > 1 && arguments) { // do overload resolution for (auto it = possibleMembers.begin(); it != possibleMembers.end();) if ( it->type->category() == Type::Category::Function && - !dynamic_cast(*it->type).canTakeArguments(*argumentTypes, exprType) + !dynamic_cast(*it->type).canTakeArguments(*arguments, exprType) ) it = possibleMembers.erase(it); else @@ -2274,7 +2277,7 @@ bool TypeChecker::visit(Identifier const& _identifier) IdentifierAnnotation& annotation = _identifier.annotation(); if (!annotation.referencedDeclaration) { - if (!annotation.argumentTypes) + if (!annotation.arguments) { // The identifier should be a public state variable shadowing other functions vector candidates; @@ -2303,7 +2306,7 @@ bool TypeChecker::visit(Identifier const& _identifier) { FunctionTypePointer functionType = declaration->functionType(true); solAssert(!!functionType, "Requested type not present."); - if (functionType->canTakeArguments(*annotation.argumentTypes)) + if (functionType->canTakeArguments(*annotation.arguments)) candidates.push_back(declaration); } if (candidates.empty()) diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index d1acf90b3..93e793ace 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -23,8 +23,11 @@ #pragma once #include +#include #include +#include + #include #include #include @@ -176,9 +179,10 @@ struct ExpressionAnnotation: ASTAnnotation bool isLValue = false; /// Whether the expression is used in a context where the LValue is actually required. bool lValueRequested = false; - /// Types of arguments if the expression is a function that is called - used - /// for overload resolution. - std::shared_ptr> argumentTypes; + + /// Types and - if given - names of arguments if the expr. is a function + /// that is called, used for overload resoultion + boost::optional arguments; }; struct IdentifierAnnotation: ExpressionAnnotation diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index f44c34353..a4101eccf 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include @@ -50,5 +51,20 @@ inline std::string stateMutabilityToString(StateMutability const& _stateMutabili } } +class Type; + +/// Container for function call parameter types & names +struct FuncCallArguments +{ + /// Types of arguments + std::vector> types; + /// Names of the arguments if given, otherwise unset + std::vector> names; + + size_t numArguments() const { return types.size(); } + size_t numNames() const { return names.size(); } + bool hasNamedArguments() const { return !names.empty(); } +}; + } } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 726ef9808..36540bc08 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -144,12 +144,12 @@ Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short) return typeDescriptions; } -Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr> _tps) +Json::Value ASTJsonConverter::typePointerToJson(boost::optional const& _tps) { if (_tps) { Json::Value arguments(Json::arrayValue); - for (auto const& tp: *_tps) + for (auto const& tp: _tps->types) appendMove(arguments, typePointerToJson(tp)); return arguments; } @@ -168,7 +168,7 @@ void ASTJsonConverter::appendExpressionAttributes( make_pair("isPure", _annotation.isPure), make_pair("isLValue", _annotation.isLValue), make_pair("lValueRequested", _annotation.lValueRequested), - make_pair("argumentTypes", typePointerToJson(_annotation.argumentTypes)) + make_pair("argumentTypes", typePointerToJson(_annotation.arguments)) }; _attributes += exprAttributes; } @@ -701,7 +701,7 @@ bool ASTJsonConverter::visit(Identifier const& _node) make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), make_pair("overloadedDeclarations", overloads), make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)), - make_pair("argumentTypes", typePointerToJson(_node.annotation().argumentTypes)) + make_pair("argumentTypes", typePointerToJson(_node.annotation().arguments)) }); return false; } diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index da3c8605a..abc84f813 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -159,7 +159,7 @@ private: return tmp; } static Json::Value typePointerToJson(TypePointer _tp, bool _short = false); - static Json::Value typePointerToJson(std::shared_ptr> _tps); + static Json::Value typePointerToJson(boost::optional const& _tps); void appendExpressionAttributes( std::vector> &_attributes, ExpressionAnnotation const& _annotation diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 9498d35ae..3ba1f928d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2986,26 +2986,53 @@ TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const return TypePointer(); } -bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const +bool FunctionType::canTakeArguments( + FuncCallArguments const& _arguments, + TypePointer const& _selfType +) const { solAssert(!bound() || _selfType, ""); if (bound() && !_selfType->isImplicitlyConvertibleTo(*selfType())) return false; TypePointers paramTypes = parameterTypes(); + std::vector const paramNames = parameterNames(); + if (takesArbitraryParameters()) return true; - else if (_argumentTypes.size() != paramTypes.size()) + else if (_arguments.numArguments() != paramTypes.size()) return false; - else + else if (!_arguments.hasNamedArguments()) return equal( - _argumentTypes.cbegin(), - _argumentTypes.cend(), + _arguments.types.cbegin(), + _arguments.types.cend(), paramTypes.cbegin(), [](TypePointer const& argumentType, TypePointer const& parameterType) { return argumentType->isImplicitlyConvertibleTo(*parameterType); } ); + else if (paramNames.size() != _arguments.numNames()) + return false; + else + { + solAssert(_arguments.numArguments() == _arguments.numNames(), "Expected equal sized type & name vectors"); + + size_t matchedNames = 0; + + for (auto const& argName: _arguments.names) + for (size_t i = 0; i < paramNames.size(); i++) + if (*argName == paramNames[i]) + { + matchedNames++; + if (!_arguments.types[i]->isImplicitlyConvertibleTo(*paramTypes[i])) + return false; + } + + if (matchedNames == _arguments.numNames()) + return true; + + return false; + } } bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 8aea1803d..a6c299187 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1107,11 +1107,15 @@ public: /// external type. FunctionTypePointer interfaceFunctionType() const; - /// @returns true if this function can take the given argument types (possibly + /// @returns true if this function can take the given arguments (possibly /// after implicit conversion). /// @param _selfType if the function is bound, this has to be supplied and is the type of the /// expression the function is called on. - bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const; + bool canTakeArguments( + FuncCallArguments const& _arguments, + TypePointer const& _selfType = TypePointer() + ) const; + /// @returns true if the types of parameters are equal (does not check return parameter types) bool hasEqualParameterTypes(FunctionType const& _other) const; /// @returns true iff the return types are equal (does not check parameter types) diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol new file mode 100644 index 000000000..f2016c967 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -0,0 +1,32 @@ +contract C { + function f() public returns (uint) { + return 0; + } + function f(uint a) public returns (uint) { + return a; + } + function f(uint a, uint b) public returns (uint) { + return a+b; + } + function f(uint a, uint b, uint c) public returns (uint) { + return a+b+c; + } + function call(uint num) public returns (uint256) { + if (num == 0) + return f(); + if (num == 1) + return f({a: 1}); + if (num == 2) + return f({b: 1, a: 2}); + if (num == 3) + return f({c: 1, a: 2, b: 3}); + + return 500; + } +} +// ---- +// call(uint256): 0 -> 0 +// call(uint256): 1 -> 1 +// call(uint256): 2 -> 3 +// call(uint256): 3 -> 6 +// call(uint256): 4 -> 500 diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol new file mode 100644 index 000000000..589d22ff3 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload.sol @@ -0,0 +1,14 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f({x: 1}); + f({x: 1, y: 2}); + f({y: 2, x: 1}); + f({x: 1, y: 2, z: 3}); + f({z: 3, x: 1, y: 2}); + f({y: 2, z: 3, x: 1}); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol new file mode 100644 index 000000000..49ec2921a --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing1.sol @@ -0,0 +1,14 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f(1, 2); + f(1); + + f({x: 1, y: 2}); + f({y: 2}); + } +} +// ---- +// TypeError: (241-242): No matching declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol new file mode 100644 index 000000000..be08f7e42 --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing2.sol @@ -0,0 +1,12 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + + f({x:1, y: 2}); + f({x:1, z: 3}); + } +} +// ---- +// TypeError: (209-210): No matching declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol new file mode 100644 index 000000000..1f315579e --- /dev/null +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_overload_failing3.sol @@ -0,0 +1,11 @@ +contract C { + function f(uint x) internal { } + function f(uint x, uint y) internal { } + function f(uint x, uint y, uint z) internal { } + function call() internal { + f({x:1, y: 2, z: 3}); + f({y:2, v: 10, z: 3}); + } +} +// ---- +// TypeError: (214-215): No matching declaration found after argument-dependent lookup.