diff --git a/Changelog.md b/Changelog.md index 9e8a65618..85c71df8e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.7.6 (unreleased) +Compiler Features: + * SMTChecker: Support named arguments in function calls. + ### 0.7.5 (2020-11-18) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 188562576..a0f15a5a9 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -757,6 +757,44 @@ FunctionCallAnnotation& FunctionCall::annotation() const return initAnnotation(); } +vector> FunctionCall::sortedArguments() const +{ + // normal arguments + if (m_names.empty()) + return arguments(); + + // named arguments + FunctionTypePointer functionType; + if (*annotation().kind == FunctionCallKind::StructConstructorCall) + { + auto const& type = dynamic_cast(*m_expression->annotation().type); + auto const& structType = dynamic_cast(*type.actualType()); + functionType = structType.constructorType(); + } + else + functionType = dynamic_cast(m_expression->annotation().type); + + vector> sorted; + for (auto const& parameterName: functionType->parameterNames()) + { + bool found = false; + for (size_t j = 0; j < m_names.size() && !found; j++) + if ((found = (parameterName == *m_names.at(j)))) + // we found the actual parameter position + sorted.push_back(m_arguments.at(j)); + solAssert(found, ""); + } + + if (!functionType->takesArbitraryParameters()) + { + solAssert(m_arguments.size() == functionType->parameterTypes().size(), ""); + solAssert(m_arguments.size() == m_names.size(), ""); + solAssert(m_arguments.size() == sorted.size(), ""); + } + + return sorted; +} + IdentifierAnnotation& Identifier::annotation() const { return initAnnotation(); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 87a4e826a..fd07a3781 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1908,7 +1908,13 @@ public: void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } + /// @returns the given arguments in the order they were written. std::vector> arguments() const { return {m_arguments.begin(), m_arguments.end()}; } + /// @returns the given arguments sorted by how the called function takes them. + std::vector> sortedArguments() const; + /// @returns the list of given argument names if this is a named call, + /// in the order they were written. + /// If this is not a named call, this is empty. std::vector> const& names() const { return m_names; } FunctionCallAnnotation& annotation() const override; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 444e312ad..dce853e8c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -539,26 +539,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) functionType = dynamic_cast(_functionCall.expression().annotation().type); TypePointers parameterTypes = functionType->parameterTypes(); - vector> const& callArguments = _functionCall.arguments(); - vector> const& callArgumentNames = _functionCall.names(); - if (!functionType->takesArbitraryParameters()) - solAssert(callArguments.size() == parameterTypes.size(), ""); - vector> arguments; - if (callArgumentNames.empty()) - // normal arguments - arguments = callArguments; - else - // named arguments - for (auto const& parameterName: functionType->parameterNames()) - { - bool found = false; - for (size_t j = 0; j < callArgumentNames.size() && !found; j++) - if ((found = (parameterName == *callArgumentNames[j]))) - // we found the actual parameter position - arguments.push_back(callArguments[j]); - solAssert(found, ""); - } + vector> const& arguments = _functionCall.sortedArguments(); if (functionCallKind == FunctionCallKind::StructConstructorCall) { diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 98a8fd294..add458d50 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -846,26 +846,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) functionType = dynamic_cast(_functionCall.expression().annotation().type); TypePointers parameterTypes = functionType->parameterTypes(); - vector> const& callArguments = _functionCall.arguments(); - vector> const& callArgumentNames = _functionCall.names(); - if (!functionType->takesArbitraryParameters()) - solAssert(callArguments.size() == parameterTypes.size(), ""); - vector> arguments; - if (callArgumentNames.empty()) - // normal arguments - arguments = callArguments; - else - // named arguments - for (auto const& parameterName: functionType->parameterNames()) - { - auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer const& _argName) { - return *_argName == parameterName; - }); - - solAssert(it != callArgumentNames.cend(), ""); - arguments.push_back(callArguments[static_cast(std::distance(callArgumentNames.begin(), it))]); - } + vector> const& arguments = _functionCall.sortedArguments(); if (functionCallKind == FunctionCallKind::StructConstructorCall) { diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index d58abc2ee..6dd4caff2 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -2530,8 +2530,8 @@ vector SMTEncoder::symbolicArguments(FunctionCall const& _f auto const& funType = dynamic_cast(calledExpr->annotation().type); solAssert(funType, ""); + vector> arguments = _funCall.sortedArguments(); auto const& functionParams = function->parameters(); - auto const& arguments = _funCall.arguments(); unsigned firstParam = 0; if (funType->bound()) { diff --git a/test/libsolidity/semanticTests/functionCall/named_args.sol b/test/libsolidity/semanticTests/functionCall/named_args.sol index e959eba44..d4bc4c7bc 100644 --- a/test/libsolidity/semanticTests/functionCall/named_args.sol +++ b/test/libsolidity/semanticTests/functionCall/named_args.sol @@ -1,8 +1,10 @@ contract test { function a(uint a, uint b, uint c) public returns (uint r) { r = a * 100 + b * 10 + c * 1; } function b() public returns (uint r) { r = a({a: 1, b: 2, c: 3}); } + function c() public returns (uint r) { r = a({b: 2, c: 3, a: 1}); } } // ==== // compileViaYul: also // ---- // b() -> 123 +// c() -> 123 diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol index b77e5be69..da94fed0c 100644 --- a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -20,6 +20,8 @@ contract C { return f({b: 1, a: 2}); if (num == 3) return f({c: 1, a: 2, b: 3}); + if (num == 4) + return f({b: 5, c: 1, a: 2}); return 500; } @@ -31,4 +33,5 @@ contract C { // call(uint256): 1 -> 1 // call(uint256): 2 -> 3 // call(uint256): 3 -> 6 -// call(uint256): 4 -> 500 +// call(uint256): 4 -> 8 +// call(uint256): 5 -> 500 diff --git a/test/libsolidity/semanticTests/structs/struct_named_constructor.sol b/test/libsolidity/semanticTests/structs/struct_named_constructor.sol index 12e69bdfc..7eb6ee4b9 100644 --- a/test/libsolidity/semanticTests/structs/struct_named_constructor.sol +++ b/test/libsolidity/semanticTests/structs/struct_named_constructor.sol @@ -6,7 +6,7 @@ contract C { S public s; constructor() { - s = S({a: 1, x: true}); + s = S({x: true, a: 1}); } } diff --git a/test/libsolidity/smtCheckerTests/operators/function_call_named_arguments.sol b/test/libsolidity/smtCheckerTests/operators/function_call_named_arguments.sol new file mode 100644 index 000000000..5fe62e3ce --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/function_call_named_arguments.sol @@ -0,0 +1,34 @@ +pragma experimental SMTChecker; +library L { + function l(uint x, uint y) internal pure returns (uint) { + return x + y; + } +} + +contract C { + function f(uint u, uint s, bool b) internal pure returns (uint z) { + if (b) + z = u; + else + z = s; + } + + using L for uint; + + function call() public pure { + uint a = 2; + uint b = a.l({y: 3}); + assert(b == 5); + b = L.l({x: 3, y: 3}); + assert(b == 6); + b = f({b: true, u: 1, s: 2}); + assert(b == 1); + b = f({b: false, u: 1, s: 2}); + // Fails, should be 2. + assert(b == 6); + } +} +// ---- +// Warning 8364: (360-361): Assertion checker does not yet implement type type(library L) +// Warning 6328: (507-521): CHC: Assertion violation happens here. +// Warning 8364: (360-361): Assertion checker does not yet implement type type(library L) diff --git a/test/libsolidity/smtCheckerTests/operators/named_arguments_in_any_order.sol b/test/libsolidity/smtCheckerTests/operators/named_arguments_in_any_order.sol new file mode 100644 index 000000000..805698357 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/named_arguments_in_any_order.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; +contract C { + function f(uint u, string memory s, bool b) internal {} + + function call() public { + f({s: "abc", u: 1, b: true}); + f({s: "abc", b: true, u: 1}); + f({u: 1, s: "abc", b: true}); + f({b: true, s: "abc", u: 1}); + f({u: 1, b: true, s: "abc"}); + f({b: true, u: 1, s: "abc"}); + } +} +// ---- diff --git a/test/libsolidity/smtCheckerTests/operators/named_arguments_overload_in_any_order.sol b/test/libsolidity/smtCheckerTests/operators/named_arguments_overload_in_any_order.sol new file mode 100644 index 000000000..840033a8b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/named_arguments_overload_in_any_order.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; +contract C { + function f(uint u, string memory s, bool b) internal {} + function f(uint u, uint s, uint b) internal {} + + function call() public { + f({s: "abc", u: 1, b: true}); + f({s: "abc", b: true, u: 1}); + f({u: 1, s: "abc", b: true}); + f({b: true, s: "abc", u: 1}); + f({u: 1, b: true, s: "abc"}); + f({b: true, u: 1, s: "abc"}); + } +} +// ----