mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Syntax for custom errors.
This commit is contained in:
@@ -124,7 +124,7 @@ bool DeclarationContainer::registerDeclaration(
|
||||
// Do not warn about shadowing for structs and enums because their members are
|
||||
// not accessible without prefixes. Also do not warn about event parameters
|
||||
// because they do not participate in any proper scope.
|
||||
bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventParameter());
|
||||
bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventOrErrorParameter());
|
||||
if (m_enclosingContainer && !special)
|
||||
m_homonymCandidates.emplace_back(*_name, _location ? _location : &_declaration.location());
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||
}
|
||||
|
||||
// Find correct data location.
|
||||
if (_variable.isEventParameter())
|
||||
if (_variable.isEventOrErrorParameter())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = DataLocation::Memory;
|
||||
|
||||
@@ -157,6 +157,13 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocStringAnalyser::visit(ErrorDefinition const& _error)
|
||||
{
|
||||
handleCallable(_error, _error, _error.annotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocStringAnalyser::handleCallable(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
|
||||
@@ -43,6 +43,7 @@ private:
|
||||
bool visit(VariableDeclaration const& _variable) override;
|
||||
bool visit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(EventDefinition const& _event) override;
|
||||
bool visit(ErrorDefinition const& _error) override;
|
||||
|
||||
CallableDeclaration const* resolveInheritDoc(
|
||||
std::set<CallableDeclaration const*> const& _baseFunctions,
|
||||
|
||||
@@ -85,6 +85,13 @@ bool DocStringTagParser::visit(EventDefinition const& _event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocStringTagParser::visit(ErrorDefinition const& _error)
|
||||
{
|
||||
handleCallable(_error, _error, _error.annotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocStringTagParser::checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
@@ -127,11 +134,14 @@ void DocStringTagParser::handleCallable(
|
||||
)
|
||||
{
|
||||
static set<string> const validEventTags = set<string>{"dev", "notice", "return", "param"};
|
||||
static set<string> const validErrorTags = set<string>{"dev", "notice", "param"};
|
||||
static set<string> const validModifierTags = set<string>{"dev", "notice", "param", "inheritdoc"};
|
||||
static set<string> const validTags = set<string>{"dev", "notice", "return", "param", "inheritdoc"};
|
||||
|
||||
if (dynamic_cast<EventDefinition const*>(&_callable))
|
||||
parseDocStrings(_node, _annotation, validEventTags, "events");
|
||||
else if (dynamic_cast<ErrorDefinition const*>(&_callable))
|
||||
parseDocStrings(_node, _annotation, validErrorTags, "errors");
|
||||
else if (dynamic_cast<ModifierDefinition const*>(&_callable))
|
||||
parseDocStrings(_node, _annotation, validModifierTags, "modifiers");
|
||||
else
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
bool visit(VariableDeclaration const& _variable) override;
|
||||
bool visit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(EventDefinition const& _event) override;
|
||||
bool visit(ErrorDefinition const& _error) override;
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
|
||||
@@ -83,10 +83,8 @@ inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariable
|
||||
magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)),
|
||||
magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
|
||||
magicVarDecl("now", TypeProvider::uint256()),
|
||||
magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
magicVarDecl("require", TypeProvider::function(strings{}, strings{}, FunctionType::Kind::Require, true, StateMutability::Pure)),
|
||||
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, true, StateMutability::Pure)),
|
||||
magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
|
||||
magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
|
||||
|
||||
@@ -85,6 +85,11 @@ bool PostTypeChecker::visit(FunctionCall const& _functionCall)
|
||||
return callVisit(_functionCall);
|
||||
}
|
||||
|
||||
void PostTypeChecker::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
callEndVisit(_functionCall);
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(Identifier const& _identifier)
|
||||
{
|
||||
return callVisit(_identifier);
|
||||
@@ -314,6 +319,67 @@ private:
|
||||
bool m_insideEmitStatement = false;
|
||||
};
|
||||
|
||||
struct ErrorOutsideRequireRevertChecker: public PostTypeChecker::Checker
|
||||
{
|
||||
ErrorOutsideRequireRevertChecker(ErrorReporter& _errorReporter):
|
||||
Checker(_errorReporter) {}
|
||||
|
||||
bool visit(FunctionCall const& _functionCall) override
|
||||
{
|
||||
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
return true;
|
||||
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||
solAssert(functionType, "");
|
||||
switch (functionType->kind())
|
||||
{
|
||||
case FunctionType::Kind::Require:
|
||||
case FunctionType::Kind::Revert:
|
||||
{
|
||||
solAssert(!m_insideRequireRevert, "");
|
||||
m_insideRequireRevert = true;
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::Error:
|
||||
{
|
||||
// This will not catch situations like
|
||||
// revert(Error1(Error2())), but as long as we
|
||||
// do not exit the expression context inside "require" and "revert",
|
||||
// this will be caught by the type checker since errors
|
||||
// do not have return values.
|
||||
if (!m_insideRequireRevert)
|
||||
m_errorReporter.typeError(
|
||||
7757_error,
|
||||
_functionCall.location(),
|
||||
"Errors can only be created directly inside require or revert calls."
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void endVisit(FunctionCall const& _functionCall) override
|
||||
{
|
||||
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
return;
|
||||
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||
solAssert(functionType, "");
|
||||
if (
|
||||
functionType->kind() == FunctionType::Kind::Require ||
|
||||
functionType->kind() == FunctionType::Kind::Revert
|
||||
)
|
||||
{
|
||||
solAssert(m_insideRequireRevert, "");
|
||||
m_insideRequireRevert = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_insideRequireRevert = false;
|
||||
};
|
||||
|
||||
struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
|
||||
{
|
||||
NoVariablesInInterfaceChecker(ErrorReporter& _errorReporter):
|
||||
@@ -371,5 +437,6 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
|
||||
m_checkers.push_back(make_shared<OverrideSpecifierChecker>(_errorReporter));
|
||||
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
|
||||
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
|
||||
m_checkers.push_back(make_shared<ErrorOutsideRequireRevertChecker>(_errorReporter));
|
||||
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ private:
|
||||
void endVisit(EmitStatement const& _emit) override;
|
||||
|
||||
bool visit(FunctionCall const& _functionCall) override;
|
||||
void endVisit(FunctionCall const& _functionCall) override;
|
||||
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(MemberAccess const& _identifier) override;
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <range/v3/view/zip.hpp>
|
||||
#include <range/v3/view/drop_exactly.hpp>
|
||||
#include <range/v3/algorithm/count_if.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -681,30 +682,12 @@ void TypeChecker::visitManually(
|
||||
bool TypeChecker::visit(EventDefinition const& _eventDef)
|
||||
{
|
||||
solAssert(_eventDef.visibility() > Visibility::Internal, "");
|
||||
unsigned numIndexed = 0;
|
||||
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
|
||||
{
|
||||
if (var->isIndexed())
|
||||
numIndexed++;
|
||||
if (type(*var)->containsNestedMapping())
|
||||
m_errorReporter.typeError(
|
||||
3448_error,
|
||||
var->location(),
|
||||
"Type containing a (nested) mapping is not allowed as event parameter type."
|
||||
);
|
||||
if (!type(*var)->interfaceType(false))
|
||||
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||
if (
|
||||
!useABICoderV2() &&
|
||||
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
3061_error,
|
||||
var->location(),
|
||||
"This type is only supported in ABI coder v2. "
|
||||
"Use \"pragma abicoder v2;\" to enable the feature."
|
||||
);
|
||||
}
|
||||
checkErrorAndEventParameters(_eventDef);
|
||||
|
||||
auto numIndexed = ranges::count_if(
|
||||
_eventDef.parameters(),
|
||||
[](ASTPointer<VariableDeclaration> const& var) { return var->isIndexed(); }
|
||||
);
|
||||
if (_eventDef.isAnonymous() && numIndexed > 4)
|
||||
m_errorReporter.typeError(8598_error, _eventDef.location(), "More than 4 indexed arguments for anonymous event.");
|
||||
else if (!_eventDef.isAnonymous() && numIndexed > 3)
|
||||
@@ -712,6 +695,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(ErrorDefinition const& _errorDef)
|
||||
{
|
||||
solAssert(_errorDef.visibility() > Visibility::Internal, "");
|
||||
checkErrorAndEventParameters(_errorDef);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
{
|
||||
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
||||
@@ -2029,6 +2019,79 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::typeCheckRequireRevert(FunctionCall const& _functionCall, FunctionType::Kind _kind)
|
||||
{
|
||||
solAssert(_kind == FunctionType::Kind::Require || _kind == FunctionType::Kind::Revert, "");
|
||||
// Check for named arguments
|
||||
if (!_functionCall.names().empty())
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
1886_error,
|
||||
_functionCall.location(),
|
||||
"Named arguments cannot be used for this function call."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isRequire = _kind == FunctionType::Kind::Require;
|
||||
size_t argsExpected = isRequire ? 1 : 0;
|
||||
string name = isRequire ? "require" : "revert";
|
||||
if (
|
||||
_functionCall.arguments().size() != argsExpected &&
|
||||
_functionCall.arguments().size() != argsExpected + 1
|
||||
)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
7445_error,
|
||||
_functionCall.location(),
|
||||
"Function \"" +
|
||||
name +
|
||||
"\" needs " +
|
||||
to_string(argsExpected) +
|
||||
" or " +
|
||||
to_string(argsExpected + 1) +
|
||||
" arguments, but provided " +
|
||||
to_string(_functionCall.arguments().size()) +
|
||||
"."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isRequire)
|
||||
{
|
||||
BoolResult result = type(*_functionCall.arguments().front())->isImplicitlyConvertibleTo(*TypeProvider::boolean());
|
||||
if (!result)
|
||||
m_errorReporter.typeError(
|
||||
2956_error,
|
||||
_functionCall.arguments().front()->location(),
|
||||
"Invalid type for argument in function call. "
|
||||
"Invalid implicit conversion from " +
|
||||
type(*_functionCall.arguments().front())->toString() +
|
||||
" to " +
|
||||
TypeProvider::boolean()->toString(false) +
|
||||
" requested." +
|
||||
(result.message().empty() ? "" : " " + result.message())
|
||||
);
|
||||
}
|
||||
|
||||
// Event is omitted, nothing more to check.
|
||||
if (_functionCall.arguments().size() == argsExpected)
|
||||
return;
|
||||
|
||||
if (type(*_functionCall.arguments().back())->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()))
|
||||
return;
|
||||
|
||||
Declaration const* declaration = nullptr;
|
||||
if (auto const* errorCall = dynamic_cast<FunctionCall const*>(_functionCall.arguments().back().get()))
|
||||
if (*errorCall->annotation().kind == FunctionCallKind::FunctionCall)
|
||||
declaration = referencedDeclaration(errorCall->expression());
|
||||
if (!dynamic_cast<ErrorDefinition const*>(declaration))
|
||||
m_errorReporter.typeError(
|
||||
4423_error,
|
||||
_functionCall.arguments().back()->location(),
|
||||
"Expected error or string."
|
||||
);
|
||||
}
|
||||
|
||||
void TypeChecker::typeCheckFunctionGeneralChecks(
|
||||
FunctionCall const& _functionCall,
|
||||
FunctionTypePointer _functionType
|
||||
@@ -2248,7 +2311,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
|
||||
_functionType->kind() == FunctionType::Kind::DelegateCall ||
|
||||
_functionType->kind() == FunctionType::Kind::External ||
|
||||
_functionType->kind() == FunctionType::Kind::Creation ||
|
||||
_functionType->kind() == FunctionType::Kind::Event;
|
||||
_functionType->kind() == FunctionType::Kind::Event ||
|
||||
_functionType->kind() == FunctionType::Kind::Error;
|
||||
|
||||
if (callRequiresABIEncoding && !useABICoderV2())
|
||||
{
|
||||
@@ -2431,6 +2495,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::MetaType:
|
||||
returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall);
|
||||
break;
|
||||
case FunctionType::Kind::Require:
|
||||
case FunctionType::Kind::Revert:
|
||||
typeCheckRequireRevert(_functionCall, functionType->kind());
|
||||
break;
|
||||
default:
|
||||
{
|
||||
typeCheckFunctionCall(_functionCall, functionType);
|
||||
@@ -3392,6 +3460,32 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
);
|
||||
}
|
||||
|
||||
void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)
|
||||
{
|
||||
string kind = dynamic_cast<EventDefinition const*>(&_callable) ? "event" : "error";
|
||||
for (ASTPointer<VariableDeclaration> const& var: _callable.parameters())
|
||||
{
|
||||
if (type(*var)->containsNestedMapping())
|
||||
m_errorReporter.typeError(
|
||||
3448_error,
|
||||
var->location(),
|
||||
"Type containing a (nested) mapping is not allowed as " + kind + " parameter type."
|
||||
);
|
||||
if (!type(*var)->interfaceType(false))
|
||||
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as " + kind + " parameter type.");
|
||||
if (
|
||||
!useABICoderV2() &&
|
||||
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
3061_error,
|
||||
var->location(),
|
||||
"This type is only supported in ABI coder v2. "
|
||||
"Use \"pragma abicoder v2;\" to enable the feature."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool TypeChecker::contractDependenciesAreCyclic(
|
||||
ContractDefinition const& _contract,
|
||||
std::set<ContractDefinition const*> const& _seenContracts
|
||||
|
||||
@@ -111,6 +111,11 @@ private:
|
||||
FunctionTypePointer _functionType
|
||||
);
|
||||
|
||||
void typeCheckRequireRevert(
|
||||
FunctionCall const& _functionCall,
|
||||
FunctionType::Kind _kind
|
||||
);
|
||||
|
||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||
void endVisit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(FunctionDefinition const& _function) override;
|
||||
@@ -119,6 +124,7 @@ private:
|
||||
/// case this is a base constructor call.
|
||||
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);
|
||||
bool visit(EventDefinition const& _eventDef) override;
|
||||
bool visit(ErrorDefinition const& _errorDef) override;
|
||||
void endVisit(FunctionTypeName const& _funType) override;
|
||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||
bool visit(IfStatement const& _ifStatement) override;
|
||||
@@ -147,6 +153,8 @@ private:
|
||||
void endVisit(Literal const& _literal) override;
|
||||
void endVisit(UsingForDirective const& _usingForDirective) override;
|
||||
|
||||
void checkErrorAndEventParameters(CallableDeclaration const& _callable);
|
||||
|
||||
bool contractDependenciesAreCyclic(
|
||||
ContractDefinition const& _contract,
|
||||
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
|
||||
|
||||
Reference in New Issue
Block a user