Merge pull request #10987 from ethereum/customErrorDeclaration

Syntax for defining custom errors.
This commit is contained in:
chriseth 2021-03-30 23:03:36 +02:00 committed by GitHub
commit 317eaf643c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 629 additions and 47 deletions

View File

@ -19,6 +19,7 @@ sourceUnit: (
| constantVariableDeclaration
| structDefinition
| enumDefinition
| errorDefinition
)* EOF;
//@doc: inline
@ -90,6 +91,7 @@ contractBodyElement:
| enumDefinition
| stateVariableDeclaration
| eventDefinition
| errorDefinition
| usingDirective;
//@doc:inline
namedArgument: name=identifier Colon value=expression;
@ -289,6 +291,18 @@ eventDefinition:
Anonymous?
Semicolon;
/**
* Parameter of an error.
*/
errorParameter: type=typeName name=identifier?;
/**
* Definition of an error.
*/
errorDefinition:
Error name=identifier
LParen (parameters+=errorParameter (Comma parameters+=errorParameter)*)? RParen
Semicolon;
/**
* Using directive to bind library functions to types.
* Can occur within contracts and libraries.
@ -365,9 +379,9 @@ tupleExpression: LParen (expression? ( Comma expression?)* ) RParen;
inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;
/**
* Besides regular non-keyword Identifiers, the 'from' keyword can also occur as identifier outside of import statements.
* Besides regular non-keyword Identifiers, some keywords like 'from' and 'error' can also be used as identifiers.
*/
identifier: Identifier | From;
identifier: Identifier | From | Error;
literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
booleanLiteral: True | False;

View File

@ -29,12 +29,13 @@ Do: 'do';
Else: 'else';
Emit: 'emit';
Enum: 'enum';
Error: 'error'; // not a real keyword
Event: 'event';
External: 'external';
Fallback: 'fallback';
False: 'false';
Fixed: 'fixed' | ('fixed' [1-9][0-9]* 'x' [1-9][0-9]*);
From: 'from';
From: 'from'; // not a real keyword
/**
* Bytes types of fixed length.
*/

View File

@ -25,10 +25,10 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolutil/FunctionSelector.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
@ -433,6 +433,24 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra
);
hashes.insert(hash);
}
map<uint32_t, SourceLocation> errorHashes;
for (ErrorDefinition const* error: _contract.interfaceErrors())
{
if (!error->functionType(true)->interfaceFunctionType())
// This will result in an error later on, so we can ignore it here.
continue;
uint32_t hash = selectorFromSignature32(error->functionType(true)->externalSignature());
if (errorHashes.count(hash))
m_errorReporter.typeError(
4883_error,
_contract.location(),
SecondarySourceLocation{}.append("This error has the same selector: "s, errorHashes[hash]),
"Error signature hash collision for " + error->functionType(true)->externalSignature()
);
else
errorHashes[hash] = error->location();
}
}
void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract)

View File

@ -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());
}

View File

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

View File

@ -158,6 +158,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,

View File

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

View File

@ -92,6 +92,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,
@ -134,11 +141,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

View File

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

View File

@ -23,6 +23,7 @@
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/FunctionSelector.h>
#include <boost/range/adaptor/map.hpp>
#include <memory>
@ -70,6 +71,11 @@ void PostTypeChecker::endVisit(VariableDeclaration const& _variable)
callEndVisit(_variable);
}
void PostTypeChecker::endVisit(ErrorDefinition const& _error)
{
callEndVisit(_error);
}
bool PostTypeChecker::visit(EmitStatement const& _emit)
{
return callVisit(_emit);
@ -362,6 +368,33 @@ private:
/// Flag indicating whether we are currently inside a StructDefinition.
int m_insideStruct = 0;
};
struct ReservedErrorSelector: public PostTypeChecker::Checker
{
ReservedErrorSelector(ErrorReporter& _errorReporter):
Checker(_errorReporter)
{}
void endVisit(ErrorDefinition const& _error) override
{
if (_error.name() == "Error" || _error.name() == "Panic")
m_errorReporter.syntaxError(
1855_error,
_error.location(),
"The built-in errors \"Error\" and \"Panic\" cannot be re-defined."
);
else
{
uint32_t selector = selectorFromSignature32(_error.functionType(true)->externalSignature());
if (selector == 0 || ~selector == 0)
m_errorReporter.syntaxError(
2855_error,
_error.location(),
"The selector 0x" + toHex(toCompactBigEndian(selector, 4)) + " is reserved. Please rename the error to avoid the collision."
);
}
}
};
}
PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter)
@ -371,4 +404,5 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
m_checkers.push_back(make_shared<ReservedErrorSelector>(_errorReporter));
}

View File

@ -40,6 +40,7 @@ namespace solidity::frontend
* - whether a modifier is in a function header
* - whether an event is used outside of an emit statement
* - whether a variable is declared in a interface
* - whether an error uses a reserved signature
*
* When adding a new checker, make sure a visitor that forwards calls that your
* checker uses exists in PostTypeChecker. Add missing ones.
@ -77,6 +78,8 @@ private:
bool visit(VariableDeclaration const& _variable) override;
void endVisit(VariableDeclaration const& _variable) override;
void endVisit(ErrorDefinition const& _error) override;
bool visit(EmitStatement const& _emit) override;
void endVisit(EmitStatement const& _emit) override;

View File

@ -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>
@ -686,30 +687,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)
@ -717,6 +700,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);
@ -2279,7 +2269,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())
{
@ -3426,6 +3417,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

View File

@ -125,6 +125,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;
@ -153,6 +154,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*>()

View File

@ -174,6 +174,15 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
});
}
vector<ErrorDefinition const*> ContractDefinition::interfaceErrors() const
{
set<ErrorDefinition const*, CompareByID> result;
// TODO add all referenced errors
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
result += filteredNodes<ErrorDefinition>(contract->m_subNodes);
return convertContainer<vector<ErrorDefinition const*>>(move(result));
}
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{
@ -462,6 +471,24 @@ EventDefinitionAnnotation& EventDefinition::annotation() const
return initAnnotation<EventDefinitionAnnotation>();
}
Type const* ErrorDefinition::type() const
{
return TypeProvider::function(*this);
}
FunctionTypePointer ErrorDefinition::functionType(bool _internal) const
{
if (_internal)
return TypeProvider::function(*this);
else
return nullptr;
}
ErrorDefinitionAnnotation& ErrorDefinition::annotation() const
{
return initAnnotation<ErrorDefinitionAnnotation>();
}
SourceUnit const& Scopable::sourceUnit() const
{
ASTNode const* s = scope();
@ -504,10 +531,10 @@ bool Declaration::isStructMember() const
return dynamic_cast<StructDefinition const*>(scope());
}
bool Declaration::isEventParameter() const
bool Declaration::isEventOrErrorParameter() const
{
solAssert(scope(), "");
return dynamic_cast<EventDefinition const*>(scope());
return dynamic_cast<EventDefinition const*>(scope()) || dynamic_cast<ErrorDefinition const*>(scope());
}
DeclarationAnnotation& Declaration::annotation() const
@ -653,7 +680,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
{
using Location = VariableDeclaration::Location;
if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
if (!hasReferenceOrMappingType() || isStateVariable() || isEventOrErrorParameter())
return set<Location>{ Location::Unspecified };
else if (isCallableOrCatchParameter())
{

View File

@ -272,7 +272,7 @@ public:
/// @returns true if this is a declaration of a struct member.
bool isStructMember() const;
/// @returns true if this is a declaration of a parameter of an event.
bool isEventParameter() const;
bool isEventOrErrorParameter() const;
/// @returns the type of expressions referencing this declaration.
/// This can only be called once types of variable declarations have already been resolved.
@ -513,6 +513,9 @@ public:
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }
std::vector<EventDefinition const*> const& interfaceEvents() const;
/// @returns all errors defined in this contract or any base contract
/// and all errors referenced during execution.
std::vector<ErrorDefinition const*> interfaceErrors() const;
bool isInterface() const { return m_contractKind == ContractKind::Interface; }
bool isLibrary() const { return m_contractKind == ContractKind::Library; }
@ -740,7 +743,7 @@ private:
/**
* Base class for all nodes that define function-like objects, i.e. FunctionDefinition,
* EventDefinition and ModifierDefinition.
* EventDefinition, ErrorDefinition and ModifierDefinition.
*/
class CallableDeclaration: public Declaration, public VariableScope
{
@ -1159,6 +1162,47 @@ private:
bool m_anonymous = false;
};
/**
* Definition of an error type usable in ``revert(MyError(x))``, ``require(condition, MyError(x))``
* and ``catch MyError(_x)``.
*/
class ErrorDefinition: public CallableDeclaration, public StructurallyDocumented, public ScopeOpener
{
public:
ErrorDefinition(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
SourceLocation _nameLocation,
ASTPointer<StructuredDocumentation> const& _documentation,
ASTPointer<ParameterList> const& _parameters
):
CallableDeclaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default, _parameters),
StructurallyDocumented(_documentation)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Type const* type() const override;
FunctionTypePointer functionType(bool _internal) const override;
bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
ErrorDefinitionAnnotation& annotation() const override;
CallableDeclaration const& resolveVirtual(
ContractDefinition const&,
ContractDefinition const*
) const override
{
return *this;
}
};
/**
* Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global
* functions when such an identifier is encountered. Will never have a valid location in the source code

View File

@ -184,6 +184,11 @@ struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDoc
{
};
struct ErrorDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
{
};
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
{
};

View File

@ -57,6 +57,7 @@ class VariableDeclaration;
class ModifierDefinition;
class ModifierInvocation;
class EventDefinition;
class ErrorDefinition;
class MagicVariableDeclaration;
class TypeName;
class ElementaryTypeName;

View File

@ -481,6 +481,17 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
return false;
}
bool ASTJsonConverter::visit(ErrorDefinition const& _node)
{
setJsonNode(_node, "ErrorDefinition", {
make_pair("name", _node.name()),
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList()))
});
return false;
}
bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {

View File

@ -88,6 +88,7 @@ public:
bool visit(ModifierDefinition const& _node) override;
bool visit(ModifierInvocation const& _node) override;
bool visit(EventDefinition const& _node) override;
bool visit(ErrorDefinition const& _node) override;
bool visit(ElementaryTypeName const& _node) override;
bool visit(UserDefinedTypeName const& _node) override;
bool visit(FunctionTypeName const& _node) override;

View File

@ -151,6 +151,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createModifierInvocation(_json);
if (nodeType == "EventDefinition")
return createEventDefinition(_json);
if (nodeType == "ErrorDefinition")
return createErrorDefinition(_json);
if (nodeType == "ElementaryTypeName")
return createElementaryTypeName(_json);
if (nodeType == "UserDefinedTypeName")
@ -539,6 +541,17 @@ ASTPointer<EventDefinition> ASTJsonImporter::createEventDefinition(Json::Value c
);
}
ASTPointer<ErrorDefinition> ASTJsonImporter::createErrorDefinition(Json::Value const& _node)
{
return createASTNode<ErrorDefinition>(
_node,
memberAsASTString(_node, "name"),
createNameSourceLocation(_node),
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
createParameterList(member(_node, "parameters"))
);
}
ASTPointer<ElementaryTypeName> ASTJsonImporter::createElementaryTypeName(Json::Value const& _node)
{
unsigned short firstNum;

View File

@ -88,6 +88,7 @@ private:
ASTPointer<ModifierDefinition> createModifierDefinition(Json::Value const& _node);
ASTPointer<ModifierInvocation> createModifierInvocation(Json::Value const& _node);
ASTPointer<EventDefinition> createEventDefinition(Json::Value const& _node);
ASTPointer<ErrorDefinition> createErrorDefinition(Json::Value const& _node);
ASTPointer<ElementaryTypeName> createElementaryTypeName(Json::Value const& _node);
ASTPointer<UserDefinedTypeName> createUserDefinedTypeName(Json::Value const& _node);
ASTPointer<FunctionTypeName> createFunctionTypeName(Json::Value const& _node);

View File

@ -22,6 +22,12 @@ namespace solidity::frontend
{
class VariableDeclaration;
class Declaration;
class Expression;
/// @returns the declaration referenced from the expression which has to be MemberAccess
/// or Identifier. Returns nullptr otherwise.
Declaration const* referencedDeclaration(Expression const& _expression);
/// Find the topmost referenced constant variable declaration when the given variable
/// declaration value is an identifier. Works only for constant variable declarations.

View File

@ -71,6 +71,7 @@ public:
virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); }
virtual bool visit(ModifierInvocation& _node) { return visitNode(_node); }
virtual bool visit(EventDefinition& _node) { return visitNode(_node); }
virtual bool visit(ErrorDefinition& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); }
virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); }
@ -124,6 +125,7 @@ public:
virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierInvocation& _node) { endVisitNode(_node); }
virtual void endVisit(EventDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(ErrorDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); }
@ -199,6 +201,7 @@ public:
virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); }
virtual bool visit(ModifierInvocation const& _node) { return visitNode(_node); }
virtual bool visit(EventDefinition const& _node) { return visitNode(_node); }
virtual bool visit(ErrorDefinition const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); }
virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); }
@ -252,6 +255,7 @@ public:
virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierInvocation const& _node) { endVisitNode(_node); }
virtual void endVisit(EventDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(ErrorDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); }

View File

@ -366,6 +366,28 @@ void EventDefinition::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void ErrorDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor);
}
_visitor.endVisit(*this);
}
void ErrorDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor);
}
_visitor.endVisit(*this);
}
void ElementaryTypeName::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);

View File

@ -431,6 +431,11 @@ FunctionType const* TypeProvider::function(EventDefinition const& _def)
return createAndGet<FunctionType>(_def);
}
FunctionType const* TypeProvider::function(ErrorDefinition const& _def)
{
return createAndGet<FunctionType>(_def);
}
FunctionType const* TypeProvider::function(FunctionTypeName const& _typeName)
{
return createAndGet<FunctionType>(_typeName);

View File

@ -139,6 +139,8 @@ public:
/// @returns the function type of an event.
static FunctionType const* function(EventDefinition const& _event);
static FunctionType const* function(ErrorDefinition const& _error);
/// @returns the type of a function type name.
static FunctionType const* function(FunctionTypeName const& _typeName);

View File

@ -2736,13 +2736,36 @@ FunctionType::FunctionType(EventDefinition const& _event):
}
solAssert(
m_parameterNames.size() == m_parameterTypes.size(),
"Parameter names list must match parameter types list!"
);
m_parameterNames.size() == m_parameterTypes.size(),
"Parameter names list must match parameter types list!"
);
solAssert(
m_returnParameterNames.size() == m_returnParameterTypes.size(),
"Return parameter names list must match return parameter types list!"
);
m_returnParameterNames.size() == 0 &&
m_returnParameterTypes.size() == 0,
""
);
}
FunctionType::FunctionType(ErrorDefinition const& _error):
m_kind(Kind::Error),
m_stateMutability(StateMutability::Pure),
m_declaration(&_error)
{
for (ASTPointer<VariableDeclaration> const& var: _error.parameters())
{
m_parameterNames.push_back(var->name());
m_parameterTypes.push_back(var->annotation().type);
}
solAssert(
m_parameterNames.size() == m_parameterTypes.size(),
"Parameter names list must match parameter types list!"
);
solAssert(
m_returnParameterNames.size() == 0 &&
m_returnParameterTypes.size() == 0,
""
);
}
FunctionType::FunctionType(FunctionTypeName const& _typeName):
@ -2870,6 +2893,7 @@ string FunctionType::richIdentifier() const
case Kind::RIPEMD160: id += "ripemd160"; break;
case Kind::GasLeft: id += "gasleft"; break;
case Kind::Event: id += "event"; break;
case Kind::Error: id += "error"; break;
case Kind::SetGas: id += "setgas"; break;
case Kind::SetValue: id += "setvalue"; break;
case Kind::BlockHash: id += "blockhash"; break;
@ -3107,7 +3131,7 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
// Note that m_declaration might also be a state variable!
solAssert(m_declaration, "Declaration needed to determine interface function type.");
bool isLibraryFunction = false;
if (kind() != Kind::Event)
if (kind() != Kind::Event && kind() != Kind::Error)
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope()))
isLibraryFunction = contract->isLibrary();
@ -3230,6 +3254,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const
}
return {};
}
case Kind::Error:
return {{"selector", TypeProvider::fixedBytes(4)}};
default:
return MemberList::MemberMap();
}
@ -3396,15 +3422,16 @@ string FunctionType::externalSignature() const
case Kind::External:
case Kind::DelegateCall:
case Kind::Event:
case Kind::Error:
case Kind::Declaration:
break;
default:
solAssert(false, "Invalid function type for requesting external signature.");
}
// "inLibrary" is only relevant if this is not an event.
// "inLibrary" is only relevant if this is neither an event nor an error.
bool inLibrary = false;
if (kind() != Kind::Event)
if (kind() != Kind::Event && kind() != Kind::Error)
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope()))
inLibrary = contract->isLibrary();

View File

@ -1151,6 +1151,7 @@ public:
SHA256, ///< CALL to special contract for sha256
RIPEMD160, ///< CALL to special contract for ripemd160
Event, ///< syntactic sugar for LOG*
Error, ///< creating an error instance in revert or require
SetGas, ///< modify the default gas value for the function call
SetValue, ///< modify the default value transfer for the function call
BlockHash, ///< BLOCKHASH
@ -1182,6 +1183,7 @@ public:
explicit FunctionType(VariableDeclaration const& _varDecl);
/// Creates the function type of an event.
explicit FunctionType(EventDefinition const& _event);
explicit FunctionType(ErrorDefinition const& _error);
/// Creates the type of a function type name.
explicit FunctionType(FunctionTypeName const& _typeName);
/// Function type constructor to be used for a plain type (not derived from a declaration).

View File

@ -912,6 +912,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << logInstruction(numIndexed);
break;
}
case FunctionType::Kind::Error:
{
solAssert(false, "");
}
case FunctionType::Kind::BlockHash:
{
acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true);
@ -1420,6 +1424,11 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "event not found");
// no-op, because the parent node will do the job
break;
case FunctionType::Kind::Error:
if (!dynamic_cast<ErrorDefinition const*>(_memberAccess.annotation().referencedDeclaration))
solAssert(false, "error not found");
// no-op, because the parent node will do the job
break;
case FunctionType::Kind::DelegateCall:
_memberAccess.expression().accept(*this);
m_context << funType->externalIdentifier();
@ -2091,6 +2100,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<ErrorDefinition const*>(declaration))
{
// no-op
}
else if (dynamic_cast<EnumDefinition const*>(declaration))
{
// no-op

View File

@ -1043,6 +1043,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
m_code << templ.render();
break;
}
case FunctionType::Kind::Error:
{
solAssert(false, "");
}
case FunctionType::Kind::Assert:
case FunctionType::Kind::Require:
{
@ -1710,13 +1714,18 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionSelector"));
else if (
functionType.kind() == FunctionType::Kind::Declaration ||
functionType.kind() == FunctionType::Kind::Error ||
// In some situations, internal function types also provide the "selector" member.
// See Types.cpp for details.
functionType.kind() == FunctionType::Kind::Internal
)
{
solAssert(functionType.hasDeclaration(), "");
solAssert(functionType.declaration().isPartOfExternalInterface(), "");
solAssert(
functionType.kind() == FunctionType::Kind::Error ||
functionType.declaration().isPartOfExternalInterface(),
""
);
define(IRVariable{_memberAccess}) << formatNumber(functionType.externalIdentifier() << 224) << "\n";
}
else
@ -1981,6 +1990,13 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
);
// the call will do the resolving
break;
case FunctionType::Kind::Error:
solAssert(
dynamic_cast<ErrorDefinition const*>(_memberAccess.annotation().referencedDeclaration),
"Error not found"
);
// the call will do the resolving
break;
case FunctionType::Kind::DelegateCall:
define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
define(IRVariable(_memberAccess).part("functionSelector")) << formatNumber(memberFunctionType->externalIdentifier()) << "\n";
@ -2310,6 +2326,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<ErrorDefinition const*>(declaration))
{
// no-op
}
else if (dynamic_cast<EnumDefinition const*>(declaration))
{
// no-op

View File

@ -699,6 +699,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
arrayPop(_funCall);
break;
case FunctionType::Kind::Event:
case FunctionType::Kind::Error:
// This can be safely ignored.
break;
case FunctionType::Kind::ObjectCreation:

View File

@ -112,8 +112,16 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
nodes.push_back(parseFunctionDefinition(true));
break;
default:
if (
// Workaround because `error` is not a keyword.
m_scanner->currentToken() == Token::Identifier &&
currentLiteral() == "error" &&
m_scanner->peekNextToken() == Token::Identifier &&
m_scanner->peekNextNextToken() == Token::LParen
)
nodes.push_back(parseErrorDefinition());
// Constant variable.
if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS)
else if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS)
{
VarDeclParserOptions options;
options.kind = VarDeclKind::FileLevel;
@ -351,6 +359,14 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition());
else if (
// Workaround because `error` is not a keyword.
currentTokenValue == Token::Identifier &&
currentLiteral() == "error" &&
m_scanner->peekNextToken() == Token::Identifier &&
m_scanner->peekNextNextToken() == Token::LParen
)
subNodes.push_back(parseErrorDefinition());
else if (variableDeclarationStart())
{
VarDeclParserOptions options;
@ -917,6 +933,21 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
return nodeFactory.createNode<EventDefinition>(name, nameLocation, documentation, parameters, anonymous);
}
ASTPointer<ErrorDefinition> Parser::parseErrorDefinition()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
solAssert(*expectIdentifierToken() == "error", "");
auto&& [name, nameLocation] = expectIdentifierWithLocation();
ASTPointer<ParameterList> parameters = parseParameterList({});
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<ErrorDefinition>(name, move(nameLocation), documentation, parameters);
}
ASTPointer<UsingForDirective> Parser::parseUsingDirective()
{
RecursionGuard recursionGuard(*this);

View File

@ -102,6 +102,7 @@ private:
);
ASTPointer<ModifierDefinition> parseModifierDefinition();
ASTPointer<EventDefinition> parseEventDefinition();
ASTPointer<ErrorDefinition> parseErrorDefinition();
ASTPointer<UsingForDirective> parseUsingDirective();
ASTPointer<ModifierInvocation> parseModifierInvocation();
ASTPointer<Identifier> parseIdentifier();

View File

@ -0,0 +1,20 @@
library L {
error E();
}
library S {
error E(uint);
}
library T {
error E();
}
contract C {
function f() public pure returns (bytes4, bytes4) {
assert(L.E.selector == T.E.selector);
assert(L.E.selector != S.E.selector);
return (L.E.selector, S.E.selector);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x92bbf6e800000000000000000000000000000000000000000000000000000000, 0x2ff06700000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1,9 @@
error E(uint);
contract C {
function f() public pure returns (bytes memory) {
return abi.decode(msg.data, (E));
}
}
// ----
// TypeError 1039: (119-120): Argument has to be a type name.
// TypeError 5132: (90-122): Different number of arguments in return statement than in returns declaration.

View File

@ -0,0 +1,8 @@
error E(uint);
contract C {
function f() public pure returns (bytes memory) {
return abi.encode(E);
}
}
// ----
// TypeError 2056: (108-109): This type cannot be encoded.

View File

@ -0,0 +1,8 @@
error E(uint);
contract C {
function f() public pure returns (bytes memory) {
return abi.encode(E(2));
}
}
// ----
// TypeError 2056: (108-112): This type cannot be encoded.

View File

@ -0,0 +1,8 @@
error test266151307();
contract C {
error test266151307();
}
// ----
// Warning 2519: (40-62): This declaration shadows an existing declaration.
// SyntaxError 2855: (0-22): The selector 0xffffffff is reserved. Please rename the error to avoid the collision.
// SyntaxError 2855: (40-62): The selector 0xffffffff is reserved. Please rename the error to avoid the collision.

View File

@ -0,0 +1,3 @@
error E() anonymous;
// ----
// ParserError 2314: (10-19): Expected ';' but got 'anonymous'

View File

@ -0,0 +1,6 @@
error E();
function f(bool x) pure {
assert(x, E());
}
// ----
// TypeError 6160: (41-55): Wrong argument count for function call: 2 arguments given but expected 1.

View File

@ -0,0 +1,6 @@
error E();
function f() pure {
assert(E());
}
// ----
// TypeError 9553: (42-45): Invalid type for argument in function call. Invalid implicit conversion from tuple() to bool requested.

View File

@ -0,0 +1,6 @@
contract C {
error MyError();
error MyError2(uint x);
error MyError3(uint x, bytes);
}
// ----

View File

@ -0,0 +1,4 @@
function Err() pure {}
error Err();
// ----
// DeclarationError 2333: (23-35): Identifier already declared.

View File

@ -0,0 +1,4 @@
contract A { function Err() public pure {} }
contract B is A { error Err(); }
// ----
// DeclarationError 9097: (63-75): Identifier already declared.

View File

@ -0,0 +1,5 @@
contract A { function Err() public pure {} }
contract B { error Err(); }
contract C is A, B {}
// ----
// DeclarationError 9097: (58-70): Identifier already declared.

View File

@ -0,0 +1,2 @@
error E(address payable x);
// ----

View File

@ -0,0 +1,4 @@
error E(uint);
function f(E x) pure returns (uint) {}
// ----
// TypeError 5172: (26-27): Name has to refer to a struct, enum or contract.

View File

@ -0,0 +1,4 @@
interface C {
error E(uint);
}
// ----

View File

@ -0,0 +1,4 @@
library L {
error E(uint);
}
// ----

View File

@ -0,0 +1,3 @@
error E(uint[] memory);
// ----
// ParserError 2314: (15-21): Expected ',' but got 'memory'

View File

@ -0,0 +1,3 @@
error E(uint[] calldata);
// ----
// ParserError 2314: (15-23): Expected ',' but got 'calldata'

View File

@ -0,0 +1,3 @@
error Error(uint);
// ----
// SyntaxError 1855: (0-18): The built-in errors "Error" and "Panic" cannot be re-defined.

View File

@ -0,0 +1,6 @@
error MyError();
error MyError2(uint x);
contract C {
error MyError3(uint x, bytes);
}
// ----

View File

@ -0,0 +1,6 @@
contract test {
error gsf();
error tgeo();
}
// ----
// TypeError 4883: (0-52): Error signature hash collision for tgeo()

View File

@ -0,0 +1,3 @@
error E(uint indexed);
// ----
// ParserError 2314: (13-20): Expected ',' but got 'indexed'

View File

@ -0,0 +1,9 @@
error MyError(mapping(uint => uint));
contract C {
error MyError2(mapping(uint => uint));
}
// ----
// TypeError 3448: (14-35): Type containing a (nested) mapping is not allowed as error parameter type.
// TypeError 3417: (14-35): Internal or recursive type is not allowed as error parameter type.
// TypeError 3448: (70-91): Type containing a (nested) mapping is not allowed as error parameter type.
// TypeError 3417: (70-91): Internal or recursive type is not allowed as error parameter type.

View File

@ -0,0 +1,4 @@
error Err(uint);
error Err(bytes32);
// ----
// DeclarationError 2333: (17-36): Identifier already declared.

View File

@ -0,0 +1,8 @@
contract A {
error Err(uint);
}
contract B is A {
error Err(bytes32);
}
// ----
// DeclarationError 9097: (58-77): Identifier already declared.

View File

@ -0,0 +1,7 @@
pragma abicoder v1;
struct S {uint a;}
contract C {
error MyError(S);
}
// ----
// TypeError 3061: (70-71): This type is only supported in ABI coder v2. Use "pragma abicoder v2;" to enable the feature.

View File

@ -0,0 +1,3 @@
error Panic(bytes2);
// ----
// SyntaxError 1855: (0-20): The built-in errors "Error" and "Panic" cannot be re-defined.

View File

@ -0,0 +1,6 @@
error E();
contract C {
bytes4 t = E.selector;
}
// ----

View File

@ -0,0 +1,7 @@
error E();
contract C {
bytes4 t = E().selector;
}
// ----
// TypeError 9582: (40-52): Member "selector" not found or not visible after argument-dependent lookup in tuple().

View File

@ -0,0 +1,6 @@
// Test that the parser workaround is not breaking.
struct error {uint a;}
contract C {
error x;
}
// ----

View File

@ -0,0 +1,12 @@
error E(uint);
library L {
function f(uint) internal {}
}
contract C {
using L for *;
function f() public pure {
E.f();
}
}
// ----
// TypeError 9582: (133-136): Member "f" not found or not visible after argument-dependent lookup in function (uint256) pure.

View File

@ -0,0 +1,12 @@
error E(uint);
library L {
function f(uint) internal {}
}
contract C {
using L for E;
function f() public pure {
E.f();
}
}
// ----
// TypeError 5172: (91-92): Name has to refer to a struct, enum or contract.

View File

@ -0,0 +1,6 @@
struct S {uint a;}
contract C {
error MyError(S);
error MyError2(S t);
}
// ----

View File

@ -0,0 +1,7 @@
error E();
contract C {
function() internal pure x = E;
}
// ----
// TypeError 7407: (58-59): Type function () pure is not implicitly convertible to expected type function () pure. Special functions can not be converted to function types.

View File

@ -0,0 +1,7 @@
error E();
contract C {
E x;
}
// ----
// TypeError 5172: (29-30): Name has to refer to a struct, enum or contract.

View File

@ -0,0 +1,9 @@
error E();
contract C {
function f() public pure {
E x;
}
}
// ----
// TypeError 5172: (64-65): Name has to refer to a struct, enum or contract.

View File

@ -0,0 +1,8 @@
error buyAndFree22457070633(uint256);
contract C {
error buyAndFree22457070633(uint256);
}
// ----
// Warning 2519: (55-92): This declaration shadows an existing declaration.
// SyntaxError 2855: (0-37): The selector 0x00000000 is reserved. Please rename the error to avoid the collision.
// SyntaxError 2855: (55-92): The selector 0x00000000 is reserved. Please rename the error to avoid the collision.