User-defined operators on structs

This commit is contained in:
Kamil Śliwak 2022-12-06 16:39:23 +01:00
parent 5657515f45
commit 05f4617275
27 changed files with 685 additions and 49 deletions

View File

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

View File

@ -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 <user-defined-value-types>`.
:ref:`user-defined value type <user-defined-value-types>` 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:

View File

@ -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<FunctionDefinition const*> 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<ReferenceType const*>(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<FunctionDefinition const*> 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<ReferenceType const*>(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<string> wrongParametersMessage;
if (isBinaryOnlyOperator && (parameterCount != 2 || !identicalFirstTwoParameters))
@ -4049,7 +4123,10 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
optional<string> 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";

View File

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

View File

@ -406,7 +406,8 @@ vector<UsingForDirective const*> usingForDirectivesForType(Type const& _type, AS
set<FunctionDefinition const*> Type::operatorDefinitions(
Token _token,
ASTNode const& _scope,
bool _unary
bool _unary,
bool _anyDataLocation
) const
{
if (!typeDefinition())
@ -428,7 +429,10 @@ set<FunctionDefinition const*> 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);
}

View File

@ -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<FunctionDefinition const*> operatorDefinitions(
Token _token,
ASTNode const& _scope,
bool _unary
bool _unary,
bool _anyDataLocation = false
) const;
private:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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