From 5b03c13f90cc4187907e295486faa60d28d45683 Mon Sep 17 00:00:00 2001 From: wechman Date: Wed, 6 Jul 2022 09:17:59 +0200 Subject: [PATCH] User-defined operators: Analysis --- libsolidity/analysis/ControlFlowBuilder.cpp | 40 ++- libsolidity/analysis/ControlFlowBuilder.h | 1 + libsolidity/analysis/FunctionCallGraph.cpp | 14 + libsolidity/analysis/FunctionCallGraph.h | 2 + libsolidity/analysis/SyntaxChecker.cpp | 6 + libsolidity/analysis/TypeChecker.cpp | 327 +++++++++++++++++--- libsolidity/analysis/ViewPureChecker.cpp | 12 + libsolidity/analysis/ViewPureChecker.h | 2 + libsolidity/ast/Types.cpp | 39 ++- libsolidity/ast/Types.h | 15 + 10 files changed, 411 insertions(+), 47 deletions(-) diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 3794dcf6f..1f2cfcac9 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -64,17 +64,53 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) case Token::And: { visitNode(_operation); + solAssert(*_operation.annotation().userDefinedFunction == nullptr); appendControlFlow(_operation.leftExpression()); auto nodes = splitFlow<2>(); nodes[0] = createFlow(nodes[0], _operation.rightExpression()); mergeFlow(nodes, nodes[1]); - return false; } default: - return ASTConstVisitor::visit(_operation); + { + if (*_operation.annotation().userDefinedFunction != nullptr) + { + visitNode(_operation); + _operation.leftExpression().accept(*this); + _operation.rightExpression().accept(*this); + + m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction; + + auto nextNode = newLabel(); + + connect(m_currentNode, nextNode); + m_currentNode = nextNode; + return false; + } + } } + return ASTConstVisitor::visit(_operation); +} + +bool ControlFlowBuilder::visit(UnaryOperation const& _operation) +{ + solAssert(!!m_currentNode); + + if (*_operation.annotation().userDefinedFunction != nullptr) + { + visitNode(_operation); + _operation.subExpression().accept(*this); + m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction; + + auto nextNode = newLabel(); + + connect(m_currentNode, nextNode); + m_currentNode = nextNode; + return false; + } + + return ASTConstVisitor::visit(_operation); } bool ControlFlowBuilder::visit(Conditional const& _conditional) diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index 91ca0eefa..93bc52bd6 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -50,6 +50,7 @@ private: // Visits for constructing the control flow. bool visit(BinaryOperation const& _operation) override; + bool visit(UnaryOperation const& _operation) override; bool visit(Conditional const& _conditional) override; bool visit(TryStatement const& _tryStatement) override; bool visit(IfStatement const& _ifStatement) override; diff --git a/libsolidity/analysis/FunctionCallGraph.cpp b/libsolidity/analysis/FunctionCallGraph.cpp index e941c99c5..ce5c53934 100644 --- a/libsolidity/analysis/FunctionCallGraph.cpp +++ b/libsolidity/analysis/FunctionCallGraph.cpp @@ -204,6 +204,20 @@ bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess) return true; } +bool FunctionCallGraphBuilder::visit(BinaryOperation const& _binaryOperation) +{ + if (*_binaryOperation.annotation().userDefinedFunction != nullptr) + functionReferenced(**_binaryOperation.annotation().userDefinedFunction, true /* called directly */); + return true; +} + +bool FunctionCallGraphBuilder::visit(UnaryOperation const& _unaryOperation) +{ + if (*_unaryOperation.annotation().userDefinedFunction != nullptr) + functionReferenced(**_unaryOperation.annotation().userDefinedFunction, true /* called directly */); + return true; +} + bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation) { if (auto const* modifier = dynamic_cast(_modifierInvocation.name().annotation().referencedDeclaration)) diff --git a/libsolidity/analysis/FunctionCallGraph.h b/libsolidity/analysis/FunctionCallGraph.h index 3ea1f5b9e..a4e5787a2 100644 --- a/libsolidity/analysis/FunctionCallGraph.h +++ b/libsolidity/analysis/FunctionCallGraph.h @@ -72,6 +72,8 @@ private: bool visit(EmitStatement const& _emitStatement) override; bool visit(Identifier const& _identifier) override; bool visit(MemberAccess const& _memberAccess) override; + bool visit(BinaryOperation const& _binaryOperation) override; + bool visit(UnaryOperation const& _unaryOperation) override; bool visit(ModifierInvocation const& _modifierInvocation) override; bool visit(NewExpression const& _newExpression) override; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 8b6cd37eb..c26ed6767 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -411,6 +411,12 @@ void SyntaxChecker::endVisit(ContractDefinition const&) bool SyntaxChecker::visit(UsingForDirective const& _usingFor) { + if (!_usingFor.usesBraces()) + solAssert( + _usingFor.functionsAndOperators().size() == 1 && + !get<1>(_usingFor.functionsAndOperators().front()) + ); + if (!m_currentContractKind && !_usingFor.typeName()) m_errorReporter.syntaxError( 8118_error, diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index ff8cc87cd..a0ca580a3 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -1606,7 +1607,7 @@ bool TypeChecker::visit(Assignment const& _assignment) 7366_error, _assignment.location(), "Operator " + - string(TokenTraits::toString(_assignment.assignmentOperator())) + + string(TokenTraits::friendlyName(_assignment.assignmentOperator())) + " not compatible with types " + t->humanReadableName() + " and " + @@ -1729,16 +1730,45 @@ bool TypeChecker::visit(UnaryOperation const& _operation) requireLValue(_operation.subExpression(), false); else _operation.subExpression().accept(*this); - Type const* subExprType = type(_operation.subExpression()); - TypeResult result = type(_operation.subExpression())->unaryOperatorResult(op); - if (!result) + Type const* operandType = type(_operation.subExpression()); + + // Check if the operator is built-in or user-defined. + TypeResult builtinResult = operandType->unaryOperatorResult(op); + set matchingDefinitions = operandType->operatorDefinitions( + op, + *currentDefinitionScope(), + true // _unary + ); + + // Operator can't be both user-defined and built-in at the same time. + solAssert(!builtinResult || matchingDefinitions.empty()); + + // By default use the type we'd expect from correct code. This way we can continue analysis + // of other expressions in a sensible way in case of a non-fatal error. + Type const* resultType = operandType; + + FunctionDefinition const* operatorDefinition = nullptr; + if (builtinResult) + resultType = builtinResult; + else if (!matchingDefinitions.empty()) + { + // This is checked along with `using for` directive but the error is not fatal. + if (matchingDefinitions.size() != 1) + solAssert(m_errorReporter.hasErrors()); + + operatorDefinition = *matchingDefinitions.begin(); + } + else { string description = fmt::format( - "Built-in unary operator {} cannot be applied to type {}.{}", - TokenTraits::toString(op), - subExprType->humanReadableName(), - !result.message().empty() ? " " + result.message() : "" + "Built-in unary operator {} cannot be applied to type {}.", + TokenTraits::friendlyName(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, @@ -1746,14 +1776,21 @@ bool TypeChecker::visit(UnaryOperation const& _operation) m_errorReporter.fatalTypeError(9767_error, _operation.location(), description); else m_errorReporter.typeError(4907_error, _operation.location(), description); - _operation.annotation().type = subExprType; } - else - _operation.annotation().type = result.get(); + + _operation.annotation().userDefinedFunction = operatorDefinition; + + TypePointers const& returnParameterTypes = _operation.userDefinedFunctionType()->returnParameterTypes(); + 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. + resultType = returnParameterTypes[0]; + _operation.annotation().type = resultType; _operation.annotation().isConstant = false; _operation.annotation().isPure = !modifying && - *_operation.subExpression().annotation().isPure; + *_operation.subExpression().annotation().isPure && + (!_operation.userDefinedFunctionType() || _operation.userDefinedFunctionType()->isPure()); _operation.annotation().isLValue = false; return false; @@ -1763,31 +1800,95 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) { Type const* leftType = type(_operation.leftExpression()); Type const* rightType = type(_operation.rightExpression()); - TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - Type const* commonType = result.get(); - if (!commonType) + + // Check if the operator is built-in or user-defined. + TypeResult builtinResult = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + set matchingDefinitions = leftType->operatorDefinitions( + _operation.getOperator(), + *currentDefinitionScope(), + false // _unary + ); + + // Operator can't be both user-defined and built-in at the same time. + solAssert(!builtinResult || matchingDefinitions.empty()); + + Type const* commonType = nullptr; + FunctionDefinition const* operatorDefinition = nullptr; + if (builtinResult) + commonType = builtinResult.get(); + else if (!matchingDefinitions.empty()) { - m_errorReporter.typeError( - 2271_error, - _operation.location(), - "Built-in binary operator " + - string(TokenTraits::toString(_operation.getOperator())) + - " cannot be applied to types " + - leftType->humanReadableName() + - " and " + - rightType->humanReadableName() + "." + - (!result.message().empty() ? " " + result.message() : "") - ); + // This is checked along with `using for` directive but the error is not fatal. + if (matchingDefinitions.size() != 1) + solAssert(m_errorReporter.hasErrors()); + + operatorDefinition = *matchingDefinitions.begin(); + + // Set common type to the type used in the `using for` directive. commonType = leftType; } + else + { + string description = fmt::format( + "Built-in binary operator {} cannot be applied to types {} and {}.", + TokenTraits::friendlyName(_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; + } + _operation.annotation().commonType = commonType; - _operation.annotation().type = + _operation.annotation().userDefinedFunction = operatorDefinition; + FunctionType const* userDefinedFunctionType = _operation.userDefinedFunctionType(); + + // By default use the type we'd expect from correct code. This way we can continue analysis + // of other expressions in a sensible way in case of a non-fatal error. + Type const* resultType = TokenTraits::isCompareOp(_operation.getOperator()) ? TypeProvider::boolean() : commonType; + + if (operatorDefinition) + { + TypePointers const& parameterTypes = userDefinedFunctionType->parameterTypes(); + TypePointers const& returnParameterTypes = userDefinedFunctionType->returnParameterTypes(); + + // operatorDefinitions() filters out definitions with non-matching first argument. + solAssert(parameterTypes.size() == 2); + solAssert(parameterTypes[0] && *leftType == *parameterTypes[0]); + + if (*rightType != *parameterTypes[0]) + m_errorReporter.typeError( + 5653_error, + _operation.location(), + fmt::format( + "The type of the second operand of this user-defined binary operator {} " + "does not match the type of the first operand, which is {}.", + TokenTraits::friendlyName(_operation.getOperator()), + parameterTypes[0]->humanReadableName() + ) + ); + + 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. + resultType = returnParameterTypes[0]; + } + + _operation.annotation().type = resultType; _operation.annotation().isPure = *_operation.leftExpression().annotation().isPure && - *_operation.rightExpression().annotation().isPure; + *_operation.rightExpression().annotation().isPure && + (!userDefinedFunctionType || userDefinedFunctionType->isPure()); _operation.annotation().isLValue = false; _operation.annotation().isConstant = false; @@ -1814,14 +1915,14 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) m_errorReporter.warning( 3149_error, _operation.location(), - "The result type of the " + - operation + - " operation is equal to the type of the first operand (" + - commonType->humanReadableName() + - ") ignoring the (larger) type of the second operand (" + - rightType->humanReadableName() + - ") which might be unexpected. Silence this warning by either converting " - "the first or the second operand to the type of the other." + fmt::format( + "The result type of the {} operation is equal to the type of the first operand ({}) " + "ignoring the (larger) type of the second operand ({}) which might be unexpected. " + "Silence this warning by either converting the first or the second operand to the type of the other.", + operation, + commonType->humanReadableName(), + rightType->humanReadableName() + ) ); } } @@ -3820,7 +3921,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) ); solAssert(normalizedType); - for (ASTPointer const& path: _usingFor.functionsOrLibrary()) + for (auto const& [path, operator_]: _usingFor.functionsAndOperators()) { solAssert(path->annotation().referencedDeclaration); FunctionDefinition const& functionDefinition = @@ -3839,8 +3940,8 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) 4731_error, path->location(), SecondarySourceLocation().append( - "Function defined here:", - functionDefinition.location() + "Function defined here:", + functionDefinition.location() ), fmt::format( "The function \"{}\" does not have any parameters, and therefore cannot be attached to the type \"{}\".", @@ -3859,8 +3960,8 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) 6772_error, path->location(), SecondarySourceLocation().append( - "Function defined here:", - functionDefinition.location() + "Function defined here:", + functionDefinition.location() ), fmt::format( "Function \"{}\" is private and therefore cannot be attached" @@ -3875,13 +3976,13 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) BoolResult result = normalizedType->isImplicitlyConvertibleTo( *TypeProvider::withLocationIfReference(DataLocation::Storage, functionTypeWithBoundFirstArgument->selfType()) ); - if (!result) + if (!result && !operator_) m_errorReporter.typeError( 3100_error, path->location(), SecondarySourceLocation().append( - "Function defined here:", - functionDefinition.location() + "Function defined here:", + functionDefinition.location() ), fmt::format( "The function \"{}\" cannot be attached to the type \"{}\" because the type cannot " @@ -3892,6 +3993,144 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) result.message().empty() ? "." : ": " + result.message() ) ); + else if (operator_.has_value()) + { + if (!_usingFor.global()) + m_errorReporter.typeError( + 3320_error, + path->location(), + "Operators can only be defined in a global 'using for' directive." + ); + + if ( + functionType->stateMutability() != StateMutability::Pure || + !functionDefinition.isFree() + ) + m_errorReporter.typeError( + 7775_error, + path->location(), + SecondarySourceLocation().append( + "Function defined as non-pure here:", + functionDefinition.location() + ), + "Only pure free functions can be used to define operators." + ); + + solAssert(!functionType->hasBoundFirstArgument()); + TypePointers const& parameterTypes = functionType->parameterTypes(); + size_t const parameterCount = parameterTypes.size(); + if (usingForType->category() != Type::Category::UserDefinedValueType) + { + m_errorReporter.typeError( + 5332_error, + path->location(), + "Operators can only be implemented for user-defined value types." + ); + continue; + } + solAssert(usingForType->typeDefinition()); + + bool identicalFirstTwoParameters = (parameterCount < 2 || *parameterTypes.at(0) == *parameterTypes.at(1)); + bool isUnaryOnlyOperator = (!TokenTraits::isBinaryOp(operator_.value()) && TokenTraits::isUnaryOp(operator_.value())); + bool isBinaryOnlyOperator = + (TokenTraits::isBinaryOp(operator_.value()) && !TokenTraits::isUnaryOp(operator_.value())) || + operator_.value() == Token::Add; + bool firstParameterMatchesUsingFor = parameterCount == 0 || *usingForType == *parameterTypes.front(); + + optional wrongParametersMessage; + if (isBinaryOnlyOperator && (parameterCount != 2 || !identicalFirstTwoParameters)) + wrongParametersMessage = fmt::format("two parameters of type {} and the same data location", usingForType->canonicalName()); + else if (isUnaryOnlyOperator && (parameterCount != 1 || !firstParameterMatchesUsingFor)) + wrongParametersMessage = fmt::format("exactly one parameter of type {}", usingForType->canonicalName()); + else if (parameterCount >= 3 || !firstParameterMatchesUsingFor || !identicalFirstTwoParameters) + wrongParametersMessage = fmt::format("one or two parameters of type {} and the same data location", usingForType->canonicalName()); + + if (wrongParametersMessage.has_value()) + m_errorReporter.typeError( + 1884_error, + functionDefinition.parameterList().location(), + SecondarySourceLocation().append( + "Function was used to implement an operator here:", + path->location() + ), + fmt::format( + "Wrong parameters in operator definition. " + "The function \"{}\" needs to have {} to be used for the operator {}.", + joinHumanReadable(path->path(), "."), + wrongParametersMessage.value(), + TokenTraits::friendlyName(operator_.value()) + ) + ); + + // This case is separately validated for all attached functions and is a fatal error + solAssert(parameterCount != 0); + + TypePointers const& returnParameterTypes = functionType->returnParameterTypes(); + size_t const returnParameterCount = returnParameterTypes.size(); + + optional wrongReturnParametersMessage; + if (!TokenTraits::isCompareOp(operator_.value()) && operator_.value() != Token::Not) + { + if (returnParameterCount != 1 || *usingForType != *returnParameterTypes.front()) + 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"; + } + else if (returnParameterCount != 1 || *returnParameterTypes.front() != *TypeProvider::boolean()) + wrongReturnParametersMessage = "exactly one value of type bool"; + + solAssert(functionDefinition.returnParameterList()); + if (wrongReturnParametersMessage.has_value()) + m_errorReporter.typeError( + 7743_error, + functionDefinition.returnParameterList()->location(), + SecondarySourceLocation().append( + "Function was used to implement an operator here:", + path->location() + ), + fmt::format( + "Wrong return parameters in operator definition. " + "The function \"{}\" needs to return {} to be used for the operator {}.", + joinHumanReadable(path->path(), "."), + wrongReturnParametersMessage.value(), + TokenTraits::friendlyName(operator_.value()) + ) + ); + + if (parameterCount != 1 && parameterCount != 2) + solAssert(m_errorReporter.hasErrors()); + else + { + // TODO: This is pretty inefficient. For every operator binding we find, we're + // traversing all bindings in all `using for` directives in the current scope. + set matchingDefinitions = usingForType->operatorDefinitions( + operator_.value(), + *currentDefinitionScope(), + parameterCount == 1 // _unary + ); + + if (matchingDefinitions.size() >= 2) + { + // TODO: We should point at other places that bind the operator rather than at + // the definitions they bind. + SecondarySourceLocation secondaryLocation; + for (FunctionDefinition const* definition: matchingDefinitions) + if (functionDefinition != *definition) + secondaryLocation.append("Conflicting definition:", definition->location()); + + m_errorReporter.typeError( + 4705_error, + path->location(), + secondaryLocation, + fmt::format( + "User-defined {} operator {} has more than one definition matching the operand type visible in the current scope.", + parameterCount == 1 ? "unary" : "binary", + TokenTraits::friendlyName(operator_.value()) + ) + ); + } + } + } } } diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 076430e13..c370f8f9f 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -331,6 +331,18 @@ void ViewPureChecker::reportFunctionCallMutability(StateMutability _mutability, reportMutability(_mutability, _location); } +void ViewPureChecker::endVisit(BinaryOperation const& _binaryOperation) +{ + if (*_binaryOperation.annotation().userDefinedFunction != nullptr) + reportFunctionCallMutability((*_binaryOperation.annotation().userDefinedFunction)->stateMutability(), _binaryOperation.location()); +} + +void ViewPureChecker::endVisit(UnaryOperation const& _unaryOperation) +{ + if (*_unaryOperation.annotation().userDefinedFunction != nullptr) + reportFunctionCallMutability((*_unaryOperation.annotation().userDefinedFunction)->stateMutability(), _unaryOperation.location()); +} + void ViewPureChecker::endVisit(FunctionCall const& _functionCall) { if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index 6ba1384ce..0dcfb16d9 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -54,6 +54,8 @@ private: bool visit(FunctionDefinition const& _funDef) override; void endVisit(FunctionDefinition const& _funDef) override; + void endVisit(BinaryOperation const& _binaryOperation) override; + void endVisit(UnaryOperation const& _unaryOperation) override; bool visit(ModifierDefinition const& _modifierDef) override; void endVisit(ModifierDefinition const& _modifierDef) override; void endVisit(Identifier const& _identifier) override; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 0a148f2fb..8fa330da4 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -383,6 +383,38 @@ vector usingForDirectivesForType(Type const& _type, AS } +set Type::operatorDefinitions( + Token _token, + ASTNode const& _scope, + bool _unary +) const +{ + if (!typeDefinition()) + return {}; + + set matchingDefinitions; + for (UsingForDirective const* directive: usingForDirectivesForType(*this, _scope)) + for (auto const& [identifierPath, operator_]: directive->functionsAndOperators()) + { + if (operator_ != _token) + continue; + + auto const& functionDefinition = dynamic_cast( + *identifierPath->annotation().referencedDeclaration + ); + auto const* functionType = dynamic_cast( + functionDefinition.libraryFunction() ? functionDefinition.typeViaContractName() : functionDefinition.type() + ); + solAssert(functionType && !functionType->parameterTypes().empty()); + + size_t parameterCount = functionDefinition.parameterList().parameters().size(); + if (*this == *functionType->parameterTypes().front() && (_unary ? parameterCount == 1 : parameterCount == 2)) + matchingDefinitions.insert(&functionDefinition); + } + + return matchingDefinitions; +} + MemberList::MemberMap Type::attachedFunctions(Type const& _type, ASTNode const& _scope) { MemberList::MemberMap members; @@ -405,8 +437,13 @@ MemberList::MemberMap Type::attachedFunctions(Type const& _type, ASTNode const& }; for (UsingForDirective const* ufd: usingForDirectivesForType(_type, _scope)) - for (auto const& identifierPath: ufd->functionsOrLibrary()) + for (auto const& [identifierPath, operator_]: ufd->functionsAndOperators()) { + if (operator_.has_value()) + // Functions used to define operators are not automatically attached to the type. + // I.e. `using {f, f as +} for T` allows `T x; x.f()` but `using {f as +} for T` does not. + continue; + solAssert(identifierPath); Declaration const* declaration = identifierPath->annotation().referencedDeclaration; solAssert(declaration); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index cdb54ec07..d7312aa1d 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -377,6 +377,21 @@ public: /// Clears all internally cached values (if any). virtual void clearCache() const; + /// 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. + /// + /// @note: If the AST has passed analysis without errors, + /// 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. + std::set> operatorDefinitions( + Token _token, + ASTNode const& _scope, + bool _unary + ) const; + private: /// @returns a member list containing all members added to this type by `using for` directives. static MemberList::MemberMap attachedFunctions(Type const& _type, ASTNode const& _scope);