From 05f4617275a62d72d328487aa7a057da4d6d72dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Tue, 6 Dec 2022 16:39:23 +0100 Subject: [PATCH] User-defined operators on structs --- Changelog.md | 2 +- docs/contracts/using-for.rst | 6 +- libsolidity/analysis/TypeChecker.cpp | 135 ++++++++++++++---- libsolidity/ast/AST.h | 3 +- libsolidity/ast/Types.cpp | 8 +- libsolidity/ast/Types.h | 9 +- ...lling_operator_with_calldata_arguments.sol | 37 +++++ ...alling_operator_with_storage_arguments.sol | 55 +++++++ .../operator_overloading_location_only.sol | 54 +++++++ .../userDefined/unchecked_loop_counter.sol | 31 ++++ ...nd_operator_local_storage_variable_err.sol | 22 +++ ...or_operator_local_storage_variable_err.sol | 22 +++ .../binary_operator_struct.sol | 17 +++ .../unary_operator_struct.sol | 17 +++ ...ion_between_locations_of_all_arguments.sol | 36 +++++ ...on_between_locations_of_some_arguments.sol | 51 +++++++ ...cit_conversion_between_struct_location.sol | 70 +++++++++ .../defining_operator_for_builtin_types.sol | 10 +- .../defining_operator_for_contract.sol | 4 +- .../defining_operator_for_enum.sol | 2 +- .../defining_operator_for_interface.sol | 2 +- .../defining_operator_for_struct.sol | 2 - ...oaded_function_for_different_locations.sol | 8 ++ ...rloaded_function_for_different_structs.sol | 10 ++ ...ng_parameters_with_different_locations.sol | 24 ++++ ...king_or_returning_wrong_types_calldata.sol | 38 +++++ .../operator_taking_storage_parameters.sol | 59 ++++++++ 27 files changed, 685 insertions(+), 49 deletions(-) create mode 100644 test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_calldata_arguments.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_storage_arguments.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/operator_overloading_location_only.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/unchecked_loop_counter.sol create mode 100644 test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitand_operator_local_storage_variable_err.sol create mode 100644 test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitor_operator_local_storage_variable_err.sol create mode 100644 test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/binary_operator_struct.sol create mode 100644 test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/unary_operator_struct.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_all_arguments.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_some_arguments.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_struct_location.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_locations.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_structs.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_parameters_with_different_locations.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_wrong_types_calldata.sol create mode 100644 test/libsolidity/syntaxTests/operators/userDefined/operator_taking_storage_parameters.sol diff --git a/Changelog.md b/Changelog.md index eee92eca0..1f557aff1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ Language Features: * Allow named parameters in mapping types. -* Allow defining custom operators for user-defined value types via ``using {f as +} for Typename;`` syntax. +* Allow defining custom operators for user-defined value types and structs via ``using {f as +} for Typename;`` syntax. Compiler Features: diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index 493353d33..9a9f978fa 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -8,7 +8,7 @@ Using For The directive ``using A for B;`` can be used to attach functions (``A``) as operators to user-defined value types -or as member functions to any type (``B``). +and structs or as member functions to any type (``B``). The member functions receive the object they are called on as their first parameter (like the ``self`` variable in Python). The operator functions receive operands as parameters. @@ -44,10 +44,12 @@ performed even if none of these functions are called. Note that private library functions can only be specified when ``using for`` is inside a library. If you define an operator (e.g. ``using {f as +} for T``), then the type (``T``) must be a -:ref:`user-defined value type `. +:ref:`user-defined value type ` or a struct. The definition of an operator must be a ``pure`` function with the types of all parameters and the return value matching ``T``, except for comparison operators, where the return value must be of type ``bool``. +``T`` does not include data location. +The locations of the parameters and the return value of the definition do and must be the same. The following operators can be defined this way: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 7af2b79fb..dd38b2cea 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1773,22 +1773,55 @@ bool TypeChecker::visit(UnaryOperation const& _operation) } else { - string description = fmt::format( - "Built-in unary operator {} cannot be applied to type {}.", - TokenTraits::toString(op), - operandType->humanReadableName() + set definitionsWithDifferentLocation = operandType->operatorDefinitions( + _operation.getOperator(), + *currentDefinitionScope(), + true, // _unary + true // _anyDataLocation ); - if (!builtinResult.message().empty()) - description += " " + builtinResult.message(); - if (operandType->typeDefinition() && util::contains(userDefinableOperators, op)) - description += " No matching user-defined operator found."; - if (modifying) - // Cannot just report the error, ignore the unary operator, and continue, - // because the sub-expression was already processed with requireLValue() - m_errorReporter.fatalTypeError(9767_error, _operation.location(), description); + if (!definitionsWithDifferentLocation.empty()) + { + auto const* referenceType = dynamic_cast(operandType); + solAssert(referenceType); + solAssert(!modifying); + + SecondarySourceLocation secondaryLocation; + for (FunctionDefinition const* definition: definitionsWithDifferentLocation) + secondaryLocation.append("Candidate definition:", definition->location()); + + m_errorReporter.typeError( + 5652_error, + _operation.location(), + secondaryLocation, + fmt::format( + "User-defined unary operator {} cannot be applied to type {}. " + "None of the available definitions accepts {} arguments.", + TokenTraits::toString(op), + operandType->humanReadableName(), + referenceType->stringForReferencePart(false /* _showIndirection */) + ) + ); + } else - m_errorReporter.typeError(4907_error, _operation.location(), description); + { + string description = fmt::format( + "Built-in unary operator {} cannot be applied to type {}.", + TokenTraits::toString(op), + operandType->humanReadableName() + ); + if (!builtinResult.message().empty()) + description += " " + builtinResult.message(); + if (operandType->typeDefinition() && util::contains(userDefinableOperators, op)) + description += " No matching user-defined operator found."; + + if (modifying) + // Cannot just report the error, ignore the unary operator, and continue, + // because the sub-expression was already processed with requireLValue() + m_errorReporter.fatalTypeError(9767_error, _operation.location(), description); + else + m_errorReporter.typeError(4907_error, _operation.location(), description); + } } _operation.annotation().userDefinedFunction = operatorDefinition; @@ -1797,6 +1830,8 @@ bool TypeChecker::visit(UnaryOperation const& _operation) if (operatorDefinition && !returnParameterTypes.empty()) // Use the actual result type from operator definition. Ignore all values but the // first one - in valid code there will be only one anyway. + // NOTE: Watch out for the difference between storage ref and ptr. Even in valid code the + // types will differ when the argument is a ref and the result is a ptr. resultType = returnParameterTypes[0]; _operation.annotation().type = resultType; _operation.annotation().isConstant = false; @@ -1855,18 +1890,50 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) } else { - string description = fmt::format( - "Built-in binary operator {} cannot be applied to types {} and {}.", - TokenTraits::toString(_operation.getOperator()), - leftType->humanReadableName(), - rightType->humanReadableName() + set definitionsWithDifferentLocation = leftType->operatorDefinitions( + _operation.getOperator(), + *currentDefinitionScope(), + false, // _unary + true // _anyDataLocation ); - if (!builtinResult.message().empty()) - description += " " + builtinResult.message(); - if (leftType->typeDefinition() && util::contains(userDefinableOperators, _operation.getOperator())) - description += " No matching user-defined operator found."; - m_errorReporter.typeError(2271_error, _operation.location(), description); + if (!definitionsWithDifferentLocation.empty()) + { + auto const* referenceType = dynamic_cast(leftType); + solAssert(referenceType); + + SecondarySourceLocation secondaryLocation; + for (FunctionDefinition const* definition: definitionsWithDifferentLocation) + secondaryLocation.append("Candidate definition:", definition->location()); + + m_errorReporter.typeError( + 1349_error, + _operation.location(), + secondaryLocation, + fmt::format( + "User-defined binary operator {} cannot be applied to type {}. " + "None of the available definitions accepts {} arguments.", + TokenTraits::toString(_operation.getOperator()), + leftType->humanReadableName(), + referenceType->stringForReferencePart(false /* _showIndirection */) + ) + ); + } + else + { + string description = fmt::format( + "Built-in binary operator {} cannot be applied to types {} and {}.", + TokenTraits::toString(_operation.getOperator()), + leftType->humanReadableName(), + rightType->humanReadableName() + ); + if (!builtinResult.message().empty()) + description += " " + builtinResult.message(); + if (leftType->typeDefinition() && util::contains(userDefinableOperators, _operation.getOperator())) + description += " No matching user-defined operator found."; + + m_errorReporter.typeError(2271_error, _operation.location(), description); + } // Set common type to something we'd expect from correct code just so that we can continue analysis. commonType = leftType; @@ -1890,9 +1957,9 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) // operatorDefinitions() filters out definitions with non-matching first argument. solAssert(parameterTypes.size() == 2); - solAssert(parameterTypes[0] && *leftType == *parameterTypes[0]); + solAssert(parameterTypes[0] && leftType->sameTypeOrPointerTo(*parameterTypes[0])); - if (*rightType != *parameterTypes[0]) + if (!rightType->sameTypeOrPointerTo(*parameterTypes[0])) m_errorReporter.typeError( 5653_error, _operation.location(), @@ -1907,6 +1974,8 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) if (!returnParameterTypes.empty()) // Use the actual result type from operator definition. Ignore all values but the // first one - in valid code there will be only one anyway. + // NOTE: Watch out for the difference between storage ref and ptr. Even in valid code the + // types will differ when the argument is a ref and the result is a ptr. resultType = returnParameterTypes[0]; } @@ -3990,12 +4059,15 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) { TypePointers const& parameterTypes = functionType->parameterTypesIncludingSelf(); size_t const parameterCount = parameterTypes.size(); - if (usingForType->category() != Type::Category::UserDefinedValueType) + if ( + usingForType->category() != Type::Category::UserDefinedValueType && + usingForType->category() != Type::Category::Struct + ) { m_errorReporter.typeError( 5332_error, path->location(), - "Operators can only be implemented for user-defined value types." + "Operators can only be implemented for user-defined value types and structs." ); continue; } @@ -4013,7 +4085,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) bool isBinaryOnlyOperator = (TokenTraits::isBinaryOp(*operator_) && !TokenTraits::isUnaryOp(*operator_)) || *operator_ == Token::Add; - bool firstParameterMatchesUsingFor = parameterCount == 0 || *usingForType == *parameterTypes.front(); + bool firstParameterMatchesUsingFor = + parameterCount == 0 || + usingForType->sameTypeOrPointerTo(*parameterTypes.front(), true /* _excludeLocation */); optional wrongParametersMessage; if (isBinaryOnlyOperator && (parameterCount != 2 || !identicalFirstTwoParameters)) @@ -4049,7 +4123,10 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) optional wrongReturnParametersMessage; if (!TokenTraits::isCompareOp(*operator_) && *operator_ != Token::Not) { - if (returnParameterCount != 1 || *usingForType != *returnParameterTypes.front()) + if ( + returnParameterCount != 1 || + !usingForType->sameTypeOrPointerTo(*returnParameterTypes.front(), true /* _excludeLocation */) + ) wrongReturnParametersMessage = "exactly one value of type " + usingForType->canonicalName(); else if (*returnParameterTypes.front() != *parameterTypes.front()) wrongReturnParametersMessage = "a value of the same type and data location as its parameters"; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9db0873fc..bbd0d263b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -653,8 +653,9 @@ private: * 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. * - * For version 4, T has to be user-defined value type and the function must be pure. + * For version 4, T has to be user-defined value type or struct and the function must be pure. * All parameters and return value of all the functions have to be of type T. + * Locations of all parameters and the return value must be identical. * This version can be combined with version 3 - a single directive may attach functions to the * type and define operators on it at the same time. * diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index de180c8cc..fb0515970 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -406,7 +406,8 @@ vector usingForDirectivesForType(Type const& _type, AS set Type::operatorDefinitions( Token _token, ASTNode const& _scope, - bool _unary + bool _unary, + bool _anyDataLocation ) const { if (!typeDefinition()) @@ -428,7 +429,10 @@ set Type::operatorDefinitions( solAssert(functionType && !functionType->parameterTypes().empty()); size_t parameterCount = functionDefinition.parameterList().parameters().size(); - if (*this == *functionType->parameterTypes().front() && (_unary ? parameterCount == 1 : parameterCount == 2)) + if ( + sameTypeOrPointerTo(*functionType->parameterTypes().front(), _anyDataLocation) && + (_unary ? parameterCount == 1 : parameterCount == 2) + ) matchingDefinitions.insert(&functionDefinition); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 4472824d2..d49deea41 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -388,17 +388,20 @@ public: /// Scans all "using for" directives in the @a _scope for functions implementing /// the operator represented by @a _token. Returns the set of all definitions where the type - /// of the first argument matches this type object. + /// of the first argument matches this type object. Accepts both pointer and non-pointer types. /// - /// @note: If the AST has passed analysis without errors, + /// @note: If the AST has passed analysis without errors and @a _anyDataLocation is false, /// the function will find at most one definition for an operator. /// /// @param _unary If true, only definitions that accept exactly one argument are included. /// Otherwise only definitions that accept exactly two arguments. + /// @param _anyDataLocation If true, ignores data location when comparing types. + /// Use this if you want to get all the overloads of the operator. std::set operatorDefinitions( Token _token, ASTNode const& _scope, - bool _unary + bool _unary, + bool _anyDataLocation = false ) const; private: diff --git a/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_calldata_arguments.sol b/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_calldata_arguments.sol new file mode 100644 index 000000000..ac0d99300 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_calldata_arguments.sol @@ -0,0 +1,37 @@ +pragma abicoder v2; + +struct S { + int value; +} + +using {add as +, mul as *, unsub as -} for S; + +function add(S calldata a, S calldata) returns (S calldata) { + return a; +} + +function mul(S calldata, S calldata b) returns (S calldata) { + return b; +} + +function unsub(S calldata a) returns (S calldata) { + return a; +} + +contract C { + function testAdd(S calldata a, S calldata b) public returns (int) { + return (a + b).value; + } + + function testMul(S calldata a, S calldata b) public returns (int) { + return (a * b).value; + } + + function testUnsub(S calldata a) public returns (int) { + return (-a).value; + } +} +// ---- +// testAdd((int256),(int256)): 3, 7 -> 3 +// testMul((int256),(int256)): 3, 7 -> 7 +// testUnsub((int256)): 3 -> 3 diff --git a/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_storage_arguments.sol b/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_storage_arguments.sol new file mode 100644 index 000000000..d0a097b90 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/calling_operator_with_storage_arguments.sol @@ -0,0 +1,55 @@ +struct S { + int value; +} + +using {add as +, unsub as -} for S; + +function add(S storage a, S storage b) returns (S storage) { + a.value = a.value + b.value; + return a; +} + +function unsub(S storage a) returns (S storage) { + a.value = -a.value; + return a; +} + +contract C { + S a = S(10); + S b = S(3); + + function addStorageRef() public returns (int) { + return (a + b).value; + } + + function addStoragePtr() public returns (int) { + // Storage pointers are technically a different type internally. The point of this test is + // to make sure they go find through both the codegen and the analysis. + S storage c = a; + S storage d = b; + + return (c + d).value; + } + + function addStoragePtrAndRef() public returns (int) { + S storage c = a; + + return (a + c).value; + } + + function unsubStorageRef() public returns (int) { + return (-a).value; + } + + function unsubStoragePtr() public returns (int) { + S storage c = a; + + return (-c).value; + } +} +// ---- +// addStorageRef() -> 13 +// addStoragePtr() -> 16 +// addStoragePtrAndRef() -> 32 +// unsubStorageRef() -> -32 +// unsubStoragePtr() -> 32 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_overloading_location_only.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_overloading_location_only.sol new file mode 100644 index 000000000..9e0862c86 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_overloading_location_only.sol @@ -0,0 +1,54 @@ +pragma abicoder v2; + +using { + addC as +, + addM as +, + addS as + +} for S; + +struct S { + uint v; +} + +function addC(S calldata _s, S calldata) pure returns (S calldata) { + return _s; +} + +function addM(S memory _s, S memory) pure returns (S memory) { + _s.v = 7; + return _s; +} + +function addS(S storage _s, S storage) returns (S storage) { + _s.v = 13; + return _s; +} + +contract C { + S s; + + function testC(S calldata _s) public returns (S calldata) { + return _s + _s; + } + + function testM() public returns (S memory) { + S memory sTmp; + return sTmp + sTmp; + } + + function testS() public returns (uint) { + s + s; + return s.v; + } + + function testSTmp() public returns (uint) { + S storage sTmp = s; + sTmp + sTmp; + return sTmp.v; + } +} +// ---- +// testC((uint256)): 3 -> 3 +// testM()-> 7 +// testS() -> 13 +// testSTmp() -> 13 diff --git a/test/libsolidity/semanticTests/operators/userDefined/unchecked_loop_counter.sol b/test/libsolidity/semanticTests/operators/userDefined/unchecked_loop_counter.sol new file mode 100644 index 000000000..e068a9180 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/unchecked_loop_counter.sol @@ -0,0 +1,31 @@ +type UncheckedCounter is uint; + +using { + add as +, + lt as < +} for UncheckedCounter; + +UncheckedCounter constant ONE = UncheckedCounter.wrap(1); + +function add(UncheckedCounter x, UncheckedCounter y) pure returns (UncheckedCounter) { + unchecked { + return UncheckedCounter.wrap(UncheckedCounter.unwrap(x) + UncheckedCounter.unwrap(y)); + } +} + +function lt(UncheckedCounter x, UncheckedCounter y) pure returns (bool) { + return UncheckedCounter.unwrap(x) < UncheckedCounter.unwrap(y); +} + +contract C { + uint internalCounter = 12; + + function testCounter() public returns (uint) { + for (UncheckedCounter i = UncheckedCounter.wrap(12); i < UncheckedCounter.wrap(20); i = i + ONE) { + ++internalCounter; + } + return internalCounter; + } +} +// ---- +// testCounter() -> 20 diff --git a/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitand_operator_local_storage_variable_err.sol b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitand_operator_local_storage_variable_err.sol new file mode 100644 index 000000000..28f7e8f02 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitand_operator_local_storage_variable_err.sol @@ -0,0 +1,22 @@ +struct S { + uint v; +} + +using {bitand as &} for S; + +function bitand(S storage, S storage) returns (S storage) { + S storage rTmp; + return rTmp; +} + +contract C { + S s; + function f() public { + S storage sTmp; + sTmp & s; + } +} + +// ---- +// TypeError 3464: (145-149): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError 3464: (234-238): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitor_operator_local_storage_variable_err.sol b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitor_operator_local_storage_variable_err.sol new file mode 100644 index 000000000..a439062b2 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/localStorageVariables/custom_bitor_operator_local_storage_variable_err.sol @@ -0,0 +1,22 @@ +struct S { + uint v; +} + +using {bitor as |} for S; + +function bitor(S storage, S storage) returns (S storage) { + S storage rTmp; + return rTmp; +} + +contract C { + S s; + function f() public { + S storage sTmp; + sTmp | s; + } +} + +// ---- +// TypeError 3464: (143-147): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError 3464: (232-236): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/binary_operator_struct.sol b/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/binary_operator_struct.sol new file mode 100644 index 000000000..128217362 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/binary_operator_struct.sol @@ -0,0 +1,17 @@ +struct S { Z z; } +struct Z { int x; } + +using {addS as +} for S; +using {addZ as +} for Z; + +function addS(S memory, S memory) pure returns (S memory) {} +function addZ(Z memory, Z memory) pure returns (Z memory) { revert(); } + +contract C { + function f() public pure { + S(Z(1)) + S(Z(2) + Z(3)); + S(Z(4)) + S(Z(5)); // Unreachable + } +} +// ---- +// Warning 5740: (310-327): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/unary_operator_struct.sol b/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/unary_operator_struct.sol new file mode 100644 index 000000000..a81db89f1 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/userDefinedOperators/unary_operator_struct.sol @@ -0,0 +1,17 @@ +struct S { Z z; } +struct Z { int x; } + +using {unsubS as -} for S; +using {unsubZ as -} for Z; + +function unsubS(S memory) pure returns (S memory) {} +function unsubZ(Z memory) pure returns (Z memory) { revert(); } + +contract C { + function f() public pure { + -S(-Z(1)); + -S(Z(2)); // Unreachable + } +} +// ---- +// Warning 5740: (283-291): Unreachable code. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_all_arguments.sol b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_all_arguments.sol new file mode 100644 index 000000000..693cb833b --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_all_arguments.sol @@ -0,0 +1,36 @@ +struct S { + uint8 a; +} + +using { + add as +, + sub as -, + mul as * +} for S; + +function add(S memory, S memory) pure returns (S memory) {} +function sub(S calldata, S calldata) pure returns (S calldata) {} +function mul(S storage, S storage) pure returns (S storage) {} + +contract C { + S s; + function test(S calldata c) public { + S memory m; + + c + c; // operator accepts memory, arguments are calldata + s + s; // operator accepts memory, arguments are storage + + m - m; // operator accepts calldata, arguments are memory + s - s; // operator accepts calldata, arguments are storage + + c * c; // operator accepts storage, arguments are calldata + m * m; // operator accepts storage, arguments are memory + } +} +// ---- +// TypeError 1349: (368-373): User-defined binary operator + cannot be applied to type struct S calldata. None of the available definitions accepts calldata arguments. +// TypeError 1349: (434-439): User-defined binary operator + cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (500-505): User-defined binary operator - cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 1349: (566-571): User-defined binary operator - cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (634-639): User-defined binary operator * cannot be applied to type struct S calldata. None of the available definitions accepts calldata arguments. +// TypeError 1349: (701-706): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_some_arguments.sol b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_some_arguments.sol new file mode 100644 index 000000000..e4a421bfa --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_locations_of_some_arguments.sol @@ -0,0 +1,51 @@ +struct S { + uint8 a; +} + +using { + add as +, + sub as -, + mul as * +} for S; + +function add(S memory, S memory) pure returns (S memory) {} +function sub(S calldata, S calldata) pure returns (S calldata) {} +function mul(S storage, S storage) pure returns (S storage) {} + +contract C { + S s; + function test(S calldata c) public { + S memory m; + + // operator accepts only memory + m + c; + m + s; + c + m; + s + m; + + // operator accepts only calldata + c - m; + c - s; + m - c; + s - c; + + // operator accepts only storage + s * c; + s * m; + c * s; + m * s; + } +} +// ---- +// TypeError 5653: (408-413): The type of the second operand of this user-defined binary operator + does not match the type of the first operand, which is struct S memory. +// TypeError 5653: (423-428): The type of the second operand of this user-defined binary operator + does not match the type of the first operand, which is struct S memory. +// TypeError 1349: (438-443): User-defined binary operator + cannot be applied to type struct S calldata. None of the available definitions accepts calldata arguments. +// TypeError 1349: (453-458): User-defined binary operator + cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 5653: (511-516): The type of the second operand of this user-defined binary operator - does not match the type of the first operand, which is struct S calldata. +// TypeError 5653: (526-531): The type of the second operand of this user-defined binary operator - does not match the type of the first operand, which is struct S calldata. +// TypeError 1349: (541-546): User-defined binary operator - cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 1349: (556-561): User-defined binary operator - cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 5653: (613-618): The type of the second operand of this user-defined binary operator * does not match the type of the first operand, which is struct S storage pointer. +// TypeError 5653: (628-633): The type of the second operand of this user-defined binary operator * does not match the type of the first operand, which is struct S storage pointer. +// TypeError 1349: (643-648): User-defined binary operator * cannot be applied to type struct S calldata. None of the available definitions accepts calldata arguments. +// TypeError 1349: (658-663): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_struct_location.sol b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_struct_location.sol new file mode 100644 index 000000000..9c5fc0b65 --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/calling_operator_with_implicit_conversion_between_struct_location.sol @@ -0,0 +1,70 @@ +using {add as +, unsub as -} for S; +using {mul as *, not as !} for S; + +struct S { + uint x; +} + +function add(S memory, S memory) returns (S memory) {} +function mul(S storage, S storage) returns (S storage) {} +function unsub(S memory) returns (S memory) {} +function not(S storage) returns (S storage) {} + +contract C { + S sRef; + + function storageToMemory() public { + S storage sPtr; + S memory sMem; + + sMem + sPtr; + sPtr + sMem; + sPtr + sPtr; + + sMem + sRef; + sRef + sMem; + sRef + sRef; + + sRef + sPtr; + sPtr + sRef; + + -sPtr; + -sRef; + } + + function memoryToStorage() public { + S memory sMem; + S storage sPtr; + + sMem * sPtr; + sPtr * sMem; + sMem * sMem; + + sMem * sRef; + sRef * sMem; + sMem * sMem; + + sRef * sPtr; + sPtr * sRef; + + !sMem; + } +} +// ---- +// TypeError 5653: (427-438): The type of the second operand of this user-defined binary operator + does not match the type of the first operand, which is struct S memory. +// TypeError 1349: (448-459): User-defined binary operator + cannot be applied to type struct S storage pointer. None of the available definitions accepts storage arguments. +// TypeError 1349: (469-480): User-defined binary operator + cannot be applied to type struct S storage pointer. None of the available definitions accepts storage arguments. +// TypeError 5653: (491-502): The type of the second operand of this user-defined binary operator + does not match the type of the first operand, which is struct S memory. +// TypeError 1349: (512-523): User-defined binary operator + cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (533-544): User-defined binary operator + cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (555-566): User-defined binary operator + cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (576-587): User-defined binary operator + cannot be applied to type struct S storage pointer. None of the available definitions accepts storage arguments. +// TypeError 5652: (598-603): User-defined unary operator - cannot be applied to type struct S storage pointer. None of the available definitions accepts storage arguments. +// TypeError 5652: (613-618): User-defined unary operator - cannot be applied to type struct S storage ref. None of the available definitions accepts storage arguments. +// TypeError 1349: (723-734): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 5653: (744-755): The type of the second operand of this user-defined binary operator * does not match the type of the first operand, which is struct S storage pointer. +// TypeError 1349: (765-776): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 1349: (787-798): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 5653: (808-819): The type of the second operand of this user-defined binary operator * does not match the type of the first operand, which is struct S storage pointer. +// TypeError 1349: (829-840): User-defined binary operator * cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. +// TypeError 5652: (894-899): User-defined unary operator ! cannot be applied to type struct S memory. None of the available definitions accepts memory arguments. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_builtin_types.sol b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_builtin_types.sol index 1258fa71c..16d3fcf72 100644 --- a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_builtin_types.sol +++ b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_builtin_types.sol @@ -6,8 +6,8 @@ using {f as +} for string; function f(uint, uint) pure returns (uint) {} // ---- -// TypeError 5332: (7-8): Operators can only be implemented for user-defined value types. -// TypeError 5332: (32-33): Operators can only be implemented for user-defined value types. -// TypeError 5332: (60-61): Operators can only be implemented for user-defined value types. -// TypeError 5332: (102-103): Operators can only be implemented for user-defined value types. -// TypeError 5332: (158-159): Operators can only be implemented for user-defined value types. +// TypeError 5332: (7-8): Operators can only be implemented for user-defined value types and structs. +// TypeError 5332: (32-33): Operators can only be implemented for user-defined value types and structs. +// TypeError 5332: (60-61): Operators can only be implemented for user-defined value types and structs. +// TypeError 5332: (102-103): Operators can only be implemented for user-defined value types and structs. +// TypeError 5332: (158-159): Operators can only be implemented for user-defined value types and structs. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_contract.sol b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_contract.sol index 781e202a1..cab8c7a83 100644 --- a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_contract.sol +++ b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_contract.sol @@ -7,5 +7,5 @@ function fa(A, A) pure returns (A) {} contract C {} abstract contract A {} // ---- -// TypeError 5332: (7-9): Operators can only be implemented for user-defined value types. -// TypeError 5332: (30-32): Operators can only be implemented for user-defined value types. +// TypeError 5332: (7-9): Operators can only be implemented for user-defined value types and structs. +// TypeError 5332: (30-32): Operators can only be implemented for user-defined value types and structs. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_enum.sol b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_enum.sol index 8ec8a06ed..eb48b2205 100644 --- a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_enum.sol +++ b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_enum.sol @@ -7,4 +7,4 @@ enum E { function add(E, E) pure returns (E) {} // ---- -// TypeError 5332: (7-10): Operators can only be implemented for user-defined value types. +// TypeError 5332: (7-10): Operators can only be implemented for user-defined value types and structs. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_interface.sol b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_interface.sol index 4db808a96..a46827540 100644 --- a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_interface.sol +++ b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_interface.sol @@ -4,4 +4,4 @@ function f(I, I) pure returns (I) {} interface I {} // ---- -// TypeError 5332: (7-8): Operators can only be implemented for user-defined value types. +// TypeError 5332: (7-8): Operators can only be implemented for user-defined value types and structs. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_struct.sol b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_struct.sol index 6d4ba84a8..82dad9dfd 100644 --- a/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_struct.sol +++ b/test/libsolidity/syntaxTests/operators/userDefined/defining_operator_for_struct.sol @@ -5,5 +5,3 @@ struct S { } function add(S memory, S memory) pure returns (S memory) {} -// ---- -// TypeError 5332: (7-10): Operators can only be implemented for user-defined value types. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_locations.sol b/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_locations.sol new file mode 100644 index 000000000..83b2e890c --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_locations.sol @@ -0,0 +1,8 @@ +using {add as +} for S; + +function add(S memory, S memory) pure returns (S memory) {} +function add(S storage, S storage) pure returns (S storage) {} + +struct S { int x; } +// ---- +// DeclarationError 7920: (7-10): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_structs.sol b/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_structs.sol new file mode 100644 index 000000000..d20b233b3 --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/implementing_operator_with_overloaded_function_for_different_structs.sol @@ -0,0 +1,10 @@ +using {add as +} for S; +using {add as +} for Z; + +function add(S memory, S memory) pure returns (S memory) {} +function add(Z memory, Z memory) pure returns (Z memory) {} + +struct S { int x; } +struct Z { int x; } +// ---- +// DeclarationError 7920: (7-10): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_parameters_with_different_locations.sol b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_parameters_with_different_locations.sol new file mode 100644 index 000000000..76f752e0f --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_parameters_with_different_locations.sol @@ -0,0 +1,24 @@ +struct S { uint128 x; } + +using {add as +} for S; +using {sub as -} for S; +using {mul as *} for S; +using {div as *} for S; +using {bitor as |} for S; +using {unsub as -} for S; + +function add(S memory, S storage) returns (S memory) {} +function sub(S memory, S storage) returns (S storage) {} +function mul(S storage, S memory) returns (S memory) {} +function div(S storage, S memory) returns (S storage) {} +function bitor(S storage, S storage) pure returns (S memory) {} +function unsub(S memory, S memory) pure returns (S storage) {} +// ---- +// TypeError 1884: (186-207): Wrong parameters in operator definition. The function "add" needs to have two parameters of type S and the same data location to be used for the operator +. +// TypeError 1884: (242-263): Wrong parameters in operator definition. The function "sub" needs to have one or two parameters of type S and the same data location to be used for the operator -. +// TypeError 7743: (272-283): Wrong return parameters in operator definition. The function "sub" needs to return a value of the same type and data location as its parameters to be used for the operator -. +// TypeError 1884: (299-320): Wrong parameters in operator definition. The function "mul" needs to have two parameters of type S and the same data location to be used for the operator *. +// TypeError 7743: (329-339): Wrong return parameters in operator definition. The function "mul" needs to return a value of the same type and data location as its parameters to be used for the operator *. +// TypeError 1884: (355-376): Wrong parameters in operator definition. The function "div" needs to have two parameters of type S and the same data location to be used for the operator *. +// TypeError 7743: (450-460): Wrong return parameters in operator definition. The function "bitor" needs to return a value of the same type and data location as its parameters to be used for the operator |. +// TypeError 7743: (512-523): Wrong return parameters in operator definition. The function "unsub" needs to return a value of the same type and data location as its parameters to be used for the operator -. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_wrong_types_calldata.sol b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_wrong_types_calldata.sol new file mode 100644 index 000000000..16bdb4b4a --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_or_returning_wrong_types_calldata.sol @@ -0,0 +1,38 @@ +using { + sub as -, + mul as *, + div as /, + mod as %, + unsub as -, + bitnot as ~ +} for S; + +struct S { + uint x; +} + +function sub(S calldata, uint) pure returns (S calldata r) {} +function mul(S calldata) pure returns (S calldata r) {} +function div(S calldata, S calldata) pure returns (uint) {} +function mod(S calldata, S calldata) pure {} +function unsub(uint) pure returns (S calldata r) {} +function bitnot(S calldata) pure {} + +function test(S calldata s) pure { + s - s; + s * s; + s / s; + s % s; + -s; + ~s; +} +// ---- +// TypeError 1884: (144-162): Wrong parameters in operator definition. The function "sub" needs to have one or two parameters of type S and the same data location to be used for the operator -. +// TypeError 1884: (206-218): Wrong parameters in operator definition. The function "mul" needs to have two parameters of type S and the same data location to be used for the operator *. +// TypeError 7743: (300-306): Wrong return parameters in operator definition. The function "div" needs to return exactly one value of type S to be used for the operator /. +// TypeError 7743: (352-352): Wrong return parameters in operator definition. The function "mod" needs to return exactly one value of type S to be used for the operator %. +// TypeError 1884: (369-375): Wrong parameters in operator definition. The function "unsub" needs to have one or two parameters of type S and the same data location to be used for the operator -. +// TypeError 7743: (389-403): Wrong return parameters in operator definition. The function "unsub" needs to return a value of the same type and data location as its parameters to be used for the operator -. +// TypeError 7743: (440-440): Wrong return parameters in operator definition. The function "bitnot" needs to return exactly one value of type S to be used for the operator ~. +// TypeError 2271: (494-499): Built-in binary operator * cannot be applied to types struct S calldata and struct S calldata. No matching user-defined operator found. +// TypeError 4907: (527-529): Built-in unary operator - cannot be applied to type struct S calldata. No matching user-defined operator found. diff --git a/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_storage_parameters.sol b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_storage_parameters.sol new file mode 100644 index 000000000..04a2d5944 --- /dev/null +++ b/test/libsolidity/syntaxTests/operators/userDefined/operator_taking_storage_parameters.sol @@ -0,0 +1,59 @@ +using { + add as +, + sub as -, + mul as *, + div as /, + mod as %, + unsub as -, + bitnot as ~ +} for S; + +struct S { + uint x; +} + +function add(S storage a, S storage) pure returns (S storage) {} +function sub(S storage a, uint) pure returns (S storage) {} +function mul(S storage a) pure returns (S storage) {} +function div(S storage a, S storage) pure returns (uint) {} +function mod(S storage a, S storage) pure {} +function unsub(S storage a) pure {} +function bitnot(S storage a, S storage) pure returns (S storage) {} + +contract C { + S a; + S b; + + function test() public view { + S storage c; + S storage d; + + // storage ref + a + b; // OK + a - b; + a * b; + a / b; + a % b; + -a; + ~a; + + // storage ptr + c + d; // OK + c - d; + c * a; + a / d; + -c; + ~c; + } +} +// ---- +// TypeError 1884: (223-242): Wrong parameters in operator definition. The function "sub" needs to have one or two parameters of type S and the same data location to be used for the operator -. +// TypeError 1884: (283-296): Wrong parameters in operator definition. The function "mul" needs to have two parameters of type S and the same data location to be used for the operator *. +// TypeError 7743: (375-381): Wrong return parameters in operator definition. The function "div" needs to return exactly one value of type S to be used for the operator /. +// TypeError 7743: (427-427): Wrong return parameters in operator definition. The function "mod" needs to return exactly one value of type S to be used for the operator %. +// TypeError 7743: (463-463): Wrong return parameters in operator definition. The function "unsub" needs to return exactly one value of type S to be used for the operator -. +// TypeError 1884: (481-505): Wrong parameters in operator definition. The function "bitnot" needs to have exactly one parameter of type S to be used for the operator ~. +// TypeError 2271: (711-716): Built-in binary operator * cannot be applied to types struct S storage ref and struct S storage ref. No matching user-defined operator found. +// TypeError 4907: (768-770): Built-in unary operator ~ cannot be applied to type struct S storage ref. No matching user-defined operator found. +// TypeError 2271: (840-845): Built-in binary operator * cannot be applied to types struct S storage pointer and struct S storage ref. No matching user-defined operator found. +// TypeError 4907: (882-884): Built-in unary operator ~ cannot be applied to type struct S storage pointer. No matching user-defined operator found.