mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
User-defined operators: Analysis
This commit is contained in:
parent
9445483d60
commit
5b03c13f90
@ -64,17 +64,53 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
|
|||||||
case Token::And:
|
case Token::And:
|
||||||
{
|
{
|
||||||
visitNode(_operation);
|
visitNode(_operation);
|
||||||
|
solAssert(*_operation.annotation().userDefinedFunction == nullptr);
|
||||||
appendControlFlow(_operation.leftExpression());
|
appendControlFlow(_operation.leftExpression());
|
||||||
|
|
||||||
auto nodes = splitFlow<2>();
|
auto nodes = splitFlow<2>();
|
||||||
nodes[0] = createFlow(nodes[0], _operation.rightExpression());
|
nodes[0] = createFlow(nodes[0], _operation.rightExpression());
|
||||||
mergeFlow(nodes, nodes[1]);
|
mergeFlow(nodes, nodes[1]);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
default:
|
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)
|
bool ControlFlowBuilder::visit(Conditional const& _conditional)
|
||||||
|
@ -50,6 +50,7 @@ private:
|
|||||||
|
|
||||||
// Visits for constructing the control flow.
|
// Visits for constructing the control flow.
|
||||||
bool visit(BinaryOperation const& _operation) override;
|
bool visit(BinaryOperation const& _operation) override;
|
||||||
|
bool visit(UnaryOperation const& _operation) override;
|
||||||
bool visit(Conditional const& _conditional) override;
|
bool visit(Conditional const& _conditional) override;
|
||||||
bool visit(TryStatement const& _tryStatement) override;
|
bool visit(TryStatement const& _tryStatement) override;
|
||||||
bool visit(IfStatement const& _ifStatement) override;
|
bool visit(IfStatement const& _ifStatement) override;
|
||||||
|
@ -204,6 +204,20 @@ bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
|
|||||||
return true;
|
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)
|
bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation)
|
||||||
{
|
{
|
||||||
if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration))
|
if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration))
|
||||||
|
@ -72,6 +72,8 @@ private:
|
|||||||
bool visit(EmitStatement const& _emitStatement) override;
|
bool visit(EmitStatement const& _emitStatement) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
bool visit(MemberAccess const& _memberAccess) 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(ModifierInvocation const& _modifierInvocation) override;
|
||||||
bool visit(NewExpression const& _newExpression) override;
|
bool visit(NewExpression const& _newExpression) override;
|
||||||
|
|
||||||
|
@ -411,6 +411,12 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
|
|||||||
|
|
||||||
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
|
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
|
||||||
{
|
{
|
||||||
|
if (!_usingFor.usesBraces())
|
||||||
|
solAssert(
|
||||||
|
_usingFor.functionsAndOperators().size() == 1 &&
|
||||||
|
!get<1>(_usingFor.functionsAndOperators().front())
|
||||||
|
);
|
||||||
|
|
||||||
if (!m_currentContractKind && !_usingFor.typeName())
|
if (!m_currentContractKind && !_usingFor.typeName())
|
||||||
m_errorReporter.syntaxError(
|
m_errorReporter.syntaxError(
|
||||||
8118_error,
|
8118_error,
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <libsolidity/analysis/TypeChecker.h>
|
#include <libsolidity/analysis/TypeChecker.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTUtils.h>
|
#include <libsolidity/ast/ASTUtils.h>
|
||||||
|
#include <libsolidity/ast/UserDefinableOperators.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
#include <libyul/AsmAnalysis.h>
|
#include <libyul/AsmAnalysis.h>
|
||||||
@ -1606,7 +1607,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
|||||||
7366_error,
|
7366_error,
|
||||||
_assignment.location(),
|
_assignment.location(),
|
||||||
"Operator " +
|
"Operator " +
|
||||||
string(TokenTraits::toString(_assignment.assignmentOperator())) +
|
string(TokenTraits::friendlyName(_assignment.assignmentOperator())) +
|
||||||
" not compatible with types " +
|
" not compatible with types " +
|
||||||
t->humanReadableName() +
|
t->humanReadableName() +
|
||||||
" and " +
|
" and " +
|
||||||
@ -1729,16 +1730,45 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
|||||||
requireLValue(_operation.subExpression(), false);
|
requireLValue(_operation.subExpression(), false);
|
||||||
else
|
else
|
||||||
_operation.subExpression().accept(*this);
|
_operation.subExpression().accept(*this);
|
||||||
Type const* subExprType = type(_operation.subExpression());
|
Type const* operandType = type(_operation.subExpression());
|
||||||
TypeResult result = type(_operation.subExpression())->unaryOperatorResult(op);
|
|
||||||
if (!result)
|
// Check if the operator is built-in or user-defined.
|
||||||
|
TypeResult builtinResult = operandType->unaryOperatorResult(op);
|
||||||
|
set<FunctionDefinition const*, ASTNode::CompareByID> 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(
|
string description = fmt::format(
|
||||||
"Built-in unary operator {} cannot be applied to type {}.{}",
|
"Built-in unary operator {} cannot be applied to type {}.",
|
||||||
TokenTraits::toString(op),
|
TokenTraits::friendlyName(op),
|
||||||
subExprType->humanReadableName(),
|
operandType->humanReadableName()
|
||||||
!result.message().empty() ? " " + result.message() : ""
|
|
||||||
);
|
);
|
||||||
|
if (!builtinResult.message().empty())
|
||||||
|
description += " " + builtinResult.message();
|
||||||
|
if (operandType->typeDefinition() && util::contains(userDefinableOperators, op))
|
||||||
|
description += " No matching user-defined operator found.";
|
||||||
|
|
||||||
if (modifying)
|
if (modifying)
|
||||||
// Cannot just report the error, ignore the unary operator, and continue,
|
// 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);
|
m_errorReporter.fatalTypeError(9767_error, _operation.location(), description);
|
||||||
else
|
else
|
||||||
m_errorReporter.typeError(4907_error, _operation.location(), description);
|
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().isConstant = false;
|
||||||
_operation.annotation().isPure =
|
_operation.annotation().isPure =
|
||||||
!modifying &&
|
!modifying &&
|
||||||
*_operation.subExpression().annotation().isPure;
|
*_operation.subExpression().annotation().isPure &&
|
||||||
|
(!_operation.userDefinedFunctionType() || _operation.userDefinedFunctionType()->isPure());
|
||||||
_operation.annotation().isLValue = false;
|
_operation.annotation().isLValue = false;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -1763,31 +1800,95 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
|||||||
{
|
{
|
||||||
Type const* leftType = type(_operation.leftExpression());
|
Type const* leftType = type(_operation.leftExpression());
|
||||||
Type const* rightType = type(_operation.rightExpression());
|
Type const* rightType = type(_operation.rightExpression());
|
||||||
TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
|
|
||||||
Type const* commonType = result.get();
|
// Check if the operator is built-in or user-defined.
|
||||||
if (!commonType)
|
TypeResult builtinResult = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
|
||||||
|
set<FunctionDefinition const*, ASTNode::CompareByID> 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(
|
// This is checked along with `using for` directive but the error is not fatal.
|
||||||
2271_error,
|
if (matchingDefinitions.size() != 1)
|
||||||
_operation.location(),
|
solAssert(m_errorReporter.hasErrors());
|
||||||
"Built-in binary operator " +
|
|
||||||
string(TokenTraits::toString(_operation.getOperator())) +
|
operatorDefinition = *matchingDefinitions.begin();
|
||||||
" cannot be applied to types " +
|
|
||||||
leftType->humanReadableName() +
|
// Set common type to the type used in the `using for` directive.
|
||||||
" and " +
|
|
||||||
rightType->humanReadableName() + "." +
|
|
||||||
(!result.message().empty() ? " " + result.message() : "")
|
|
||||||
);
|
|
||||||
commonType = leftType;
|
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().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()) ?
|
TokenTraits::isCompareOp(_operation.getOperator()) ?
|
||||||
TypeProvider::boolean() :
|
TypeProvider::boolean() :
|
||||||
commonType;
|
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.annotation().isPure =
|
||||||
*_operation.leftExpression().annotation().isPure &&
|
*_operation.leftExpression().annotation().isPure &&
|
||||||
*_operation.rightExpression().annotation().isPure;
|
*_operation.rightExpression().annotation().isPure &&
|
||||||
|
(!userDefinedFunctionType || userDefinedFunctionType->isPure());
|
||||||
_operation.annotation().isLValue = false;
|
_operation.annotation().isLValue = false;
|
||||||
_operation.annotation().isConstant = false;
|
_operation.annotation().isConstant = false;
|
||||||
|
|
||||||
@ -1814,14 +1915,14 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
|||||||
m_errorReporter.warning(
|
m_errorReporter.warning(
|
||||||
3149_error,
|
3149_error,
|
||||||
_operation.location(),
|
_operation.location(),
|
||||||
"The result type of the " +
|
fmt::format(
|
||||||
operation +
|
"The result type of the {} operation is equal to the type of the first operand ({}) "
|
||||||
" operation is equal to the type of the first operand (" +
|
"ignoring the (larger) type of the second operand ({}) which might be unexpected. "
|
||||||
commonType->humanReadableName() +
|
"Silence this warning by either converting the first or the second operand to the type of the other.",
|
||||||
") ignoring the (larger) type of the second operand (" +
|
operation,
|
||||||
rightType->humanReadableName() +
|
commonType->humanReadableName(),
|
||||||
") which might be unexpected. Silence this warning by either converting "
|
rightType->humanReadableName()
|
||||||
"the first or the second operand to the type of the other."
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3820,7 +3921,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|||||||
);
|
);
|
||||||
solAssert(normalizedType);
|
solAssert(normalizedType);
|
||||||
|
|
||||||
for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
|
for (auto const& [path, operator_]: _usingFor.functionsAndOperators())
|
||||||
{
|
{
|
||||||
solAssert(path->annotation().referencedDeclaration);
|
solAssert(path->annotation().referencedDeclaration);
|
||||||
FunctionDefinition const& functionDefinition =
|
FunctionDefinition const& functionDefinition =
|
||||||
@ -3839,8 +3940,8 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|||||||
4731_error,
|
4731_error,
|
||||||
path->location(),
|
path->location(),
|
||||||
SecondarySourceLocation().append(
|
SecondarySourceLocation().append(
|
||||||
"Function defined here:",
|
"Function defined here:",
|
||||||
functionDefinition.location()
|
functionDefinition.location()
|
||||||
),
|
),
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"The function \"{}\" does not have any parameters, and therefore cannot be attached to the type \"{}\".",
|
"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,
|
6772_error,
|
||||||
path->location(),
|
path->location(),
|
||||||
SecondarySourceLocation().append(
|
SecondarySourceLocation().append(
|
||||||
"Function defined here:",
|
"Function defined here:",
|
||||||
functionDefinition.location()
|
functionDefinition.location()
|
||||||
),
|
),
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"Function \"{}\" is private and therefore cannot be attached"
|
"Function \"{}\" is private and therefore cannot be attached"
|
||||||
@ -3875,13 +3976,13 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|||||||
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
|
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
|
||||||
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionTypeWithBoundFirstArgument->selfType())
|
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionTypeWithBoundFirstArgument->selfType())
|
||||||
);
|
);
|
||||||
if (!result)
|
if (!result && !operator_)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
3100_error,
|
3100_error,
|
||||||
path->location(),
|
path->location(),
|
||||||
SecondarySourceLocation().append(
|
SecondarySourceLocation().append(
|
||||||
"Function defined here:",
|
"Function defined here:",
|
||||||
functionDefinition.location()
|
functionDefinition.location()
|
||||||
),
|
),
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"The function \"{}\" cannot be attached to the type \"{}\" because the type cannot "
|
"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()
|
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<string> 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<string> 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<FunctionDefinition const*, ASTNode::CompareByID> 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())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +331,18 @@ void ViewPureChecker::reportFunctionCallMutability(StateMutability _mutability,
|
|||||||
reportMutability(_mutability, _location);
|
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)
|
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
|
||||||
{
|
{
|
||||||
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||||
|
@ -54,6 +54,8 @@ private:
|
|||||||
|
|
||||||
bool visit(FunctionDefinition const& _funDef) override;
|
bool visit(FunctionDefinition const& _funDef) override;
|
||||||
void endVisit(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;
|
bool visit(ModifierDefinition const& _modifierDef) override;
|
||||||
void endVisit(ModifierDefinition const& _modifierDef) override;
|
void endVisit(ModifierDefinition const& _modifierDef) override;
|
||||||
void endVisit(Identifier const& _identifier) override;
|
void endVisit(Identifier const& _identifier) override;
|
||||||
|
@ -383,6 +383,38 @@ vector<UsingForDirective const*> usingForDirectivesForType(Type const& _type, AS
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set<FunctionDefinition const*, ASTNode::CompareByID> Type::operatorDefinitions(
|
||||||
|
Token _token,
|
||||||
|
ASTNode const& _scope,
|
||||||
|
bool _unary
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
if (!typeDefinition())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
set<FunctionDefinition const*, ASTNode::CompareByID> matchingDefinitions;
|
||||||
|
for (UsingForDirective const* directive: usingForDirectivesForType(*this, _scope))
|
||||||
|
for (auto const& [identifierPath, operator_]: directive->functionsAndOperators())
|
||||||
|
{
|
||||||
|
if (operator_ != _token)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(
|
||||||
|
*identifierPath->annotation().referencedDeclaration
|
||||||
|
);
|
||||||
|
auto const* functionType = dynamic_cast<FunctionType const*>(
|
||||||
|
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 Type::attachedFunctions(Type const& _type, ASTNode const& _scope)
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
@ -405,8 +437,13 @@ MemberList::MemberMap Type::attachedFunctions(Type const& _type, ASTNode const&
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (UsingForDirective const* ufd: usingForDirectivesForType(_type, _scope))
|
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);
|
solAssert(identifierPath);
|
||||||
Declaration const* declaration = identifierPath->annotation().referencedDeclaration;
|
Declaration const* declaration = identifierPath->annotation().referencedDeclaration;
|
||||||
solAssert(declaration);
|
solAssert(declaration);
|
||||||
|
@ -377,6 +377,21 @@ public:
|
|||||||
/// Clears all internally cached values (if any).
|
/// Clears all internally cached values (if any).
|
||||||
virtual void clearCache() const;
|
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<FunctionDefinition const*, ASTCompareByID<ASTNode>> operatorDefinitions(
|
||||||
|
Token _token,
|
||||||
|
ASTNode const& _scope,
|
||||||
|
bool _unary
|
||||||
|
) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// @returns a member list containing all members added to this type by `using for` directives.
|
/// @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);
|
static MemberList::MemberMap attachedFunctions(Type const& _type, ASTNode const& _scope);
|
||||||
|
Loading…
Reference in New Issue
Block a user