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:
parent
1716dcfb57
commit
780fa1e658
@ -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,25 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra
|
||||
);
|
||||
hashes.insert(hash);
|
||||
}
|
||||
|
||||
map<uint32_t, SourceLocation> errorHashes;
|
||||
// TODO all used errors?
|
||||
for (ErrorDefinition const* error: _contract.errors())
|
||||
{
|
||||
if (!error->functionType(true)->interfaceFunctionType())
|
||||
// Will create an error later on.
|
||||
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: ", errorHashes[hash]),
|
||||
string("Error signature hash collision for ") + error->functionType(true)->externalSignature()
|
||||
);
|
||||
else
|
||||
errorHashes[hash] = error->location();
|
||||
}
|
||||
}
|
||||
|
||||
void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract)
|
||||
|
@ -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);
|
||||
@ -313,6 +318,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):
|
||||
@ -370,5 +436,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;
|
||||
|
@ -192,6 +192,17 @@ void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
|
||||
m_placeholderFound = false;
|
||||
}
|
||||
|
||||
bool SyntaxChecker::visit(ErrorDefinition const& _error)
|
||||
{
|
||||
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."
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _statement)
|
||||
{
|
||||
auto varDecl = dynamic_cast<VariableDeclarationStatement const*>(&_statement);
|
||||
|
@ -40,6 +40,7 @@ namespace solidity::frontend
|
||||
* - issues deprecation warning for throw
|
||||
* - whether the msize instruction is used and the Yul optimizer is enabled at the same time.
|
||||
* - selection of the ABI coder through pragmas.
|
||||
* - whether user-defined errors are called Error or Panic.
|
||||
*/
|
||||
class SyntaxChecker: private ASTConstVisitor
|
||||
{
|
||||
@ -61,6 +62,8 @@ private:
|
||||
bool visit(ModifierDefinition const& _modifier) override;
|
||||
void endVisit(ModifierDefinition const& _modifier) override;
|
||||
|
||||
bool visit(ErrorDefinition const& _error) override;
|
||||
|
||||
/// Reports an error if _statement is a VariableDeclarationStatement.
|
||||
/// Used by if/while/for to check for single statement variable declarations
|
||||
/// without a block.
|
||||
|
@ -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."
|
||||
checkErrorAndEventParameters(_eventDef);
|
||||
|
||||
auto numIndexed = ranges::count_if(
|
||||
_eventDef.parameters(),
|
||||
[](ASTPointer<VariableDeclaration> const& var) { return var->isIndexed(); }
|
||||
);
|
||||
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."
|
||||
);
|
||||
}
|
||||
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,85 @@ 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 instance or string." + string(
|
||||
dynamic_cast<Identifier const*>(_functionCall.arguments().back().get()) ?
|
||||
" Did you forget the \"()\" after the error?" :
|
||||
""
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::typeCheckFunctionGeneralChecks(
|
||||
FunctionCall const& _functionCall,
|
||||
FunctionTypePointer _functionType
|
||||
@ -2248,7 +2317,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 +2501,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 +3466,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*>()
|
||||
|
@ -450,6 +450,24 @@ EventDefinitionAnnotation& EventDefinition::annotation() const
|
||||
return initAnnotation<EventDefinitionAnnotation>();
|
||||
}
|
||||
|
||||
TypePointer 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();
|
||||
@ -492,10 +510,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
|
||||
@ -641,7 +659,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())
|
||||
{
|
||||
|
@ -268,7 +268,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.
|
||||
@ -509,6 +509,7 @@ 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;
|
||||
std::vector<ErrorDefinition const*> errors() const { return filteredNodes<ErrorDefinition>(m_subNodes); }
|
||||
bool isInterface() const { return m_contractKind == ContractKind::Interface; }
|
||||
bool isLibrary() const { return m_contractKind == ContractKind::Library; }
|
||||
|
||||
@ -736,7 +737,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
|
||||
{
|
||||
@ -1155,6 +1156,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;
|
||||
|
||||
TypePointer 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
|
||||
|
@ -178,6 +178,11 @@ struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDoc
|
||||
{
|
||||
};
|
||||
|
||||
struct ErrorDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||
{
|
||||
};
|
||||
|
@ -57,6 +57,7 @@ class VariableDeclaration;
|
||||
class ModifierDefinition;
|
||||
class ModifierInvocation;
|
||||
class EventDefinition;
|
||||
class ErrorDefinition;
|
||||
class MagicVariableDeclaration;
|
||||
class TypeName;
|
||||
class ElementaryTypeName;
|
||||
|
@ -473,6 +473,18 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTJsonConverter::visit(ErrorDefinition const& _node)
|
||||
{
|
||||
m_inEvent = true;
|
||||
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 = {
|
||||
|
@ -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;
|
||||
|
@ -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")
|
||||
@ -536,6 +538,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;
|
||||
|
@ -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);
|
||||
|
@ -67,4 +67,14 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
|
||||
return rootDecl;
|
||||
}
|
||||
|
||||
Declaration const* referencedDeclaration(Expression const& _expression)
|
||||
{
|
||||
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
|
||||
return memberAccess->annotation().referencedDeclaration;
|
||||
else if (auto const* identifier = dynamic_cast<Identifier const*>(&_expression))
|
||||
return identifier->annotation().referencedDeclaration;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ namespace solidity::frontend
|
||||
{
|
||||
|
||||
class VariableDeclaration;
|
||||
class Declaration;
|
||||
class Expression;
|
||||
|
||||
/// Find the topmost referenced constant variable declaration when the given variable
|
||||
/// declaration value is an identifier. Works only for constant variable declarations.
|
||||
@ -31,4 +33,8 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
|
||||
/// Returns true if the constant variable declaration is recursive.
|
||||
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// @returns the declaration referenced from the expression which has to be MemberAccess
|
||||
/// or Identifier. Returns nullptr otherwise.
|
||||
Declaration const* referencedDeclaration(Expression const& _expression);
|
||||
|
||||
}
|
||||
|
@ -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); }
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -2738,6 +2738,27 @@ FunctionType::FunctionType(EventDefinition const& _event):
|
||||
);
|
||||
}
|
||||
|
||||
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.empty() && m_returnParameterTypes.empty(),
|
||||
"Return parameter names list must match return parameter types list!"
|
||||
);
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
||||
m_parameterNames(_typeName.parameterTypes().size(), ""),
|
||||
m_returnParameterNames(_typeName.returnParameterTypes().size(), ""),
|
||||
@ -2863,6 +2884,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;
|
||||
@ -3100,7 +3122,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();
|
||||
|
||||
@ -3389,15 +3411,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();
|
||||
|
||||
|
@ -1149,6 +1149,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
|
||||
@ -1180,6 +1181,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).
|
||||
|
@ -792,7 +792,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
// function-sel(Error(string)) + encoding
|
||||
solAssert(arguments.size() == 1, "");
|
||||
solAssert(function.parameterTypes().size() == 1, "");
|
||||
solUnimplementedAssert(arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), "");
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!*arguments.front()->annotation().isPure)
|
||||
@ -908,6 +908,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);
|
||||
@ -1076,7 +1080,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::Assert:
|
||||
case FunctionType::Kind::Require:
|
||||
{
|
||||
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
|
||||
acceptAndConvert(*arguments.front(), *TypeProvider::boolean(), false);
|
||||
|
||||
bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip;
|
||||
|
||||
@ -1087,6 +1091,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// function call.
|
||||
solAssert(arguments.size() == 2, "");
|
||||
solAssert(function.kind() == FunctionType::Kind::Require, "");
|
||||
solUnimplementedAssert(arguments.back()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), "");
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!*arguments.at(1)->annotation().isPure)
|
||||
@ -1381,6 +1386,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();
|
||||
@ -2052,6 +2062,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
|
||||
|
@ -1041,11 +1041,20 @@ 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:
|
||||
{
|
||||
solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
|
||||
solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
|
||||
if (arguments.size() == 2)
|
||||
solAssert(functionType->kind() == FunctionType::Kind::Require, "");
|
||||
|
||||
if (arguments.size() == 2)
|
||||
solUnimplementedAssert(arguments.back()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), "");
|
||||
|
||||
Type const* messageArgumentType =
|
||||
arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip ?
|
||||
@ -1192,12 +1201,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
}
|
||||
case FunctionType::Kind::Revert:
|
||||
{
|
||||
solAssert(arguments.size() == parameterTypes.size(), "");
|
||||
if (arguments.empty())
|
||||
m_code << "revert(0, 0)\n";
|
||||
else
|
||||
{
|
||||
solAssert(arguments.size() == 1, "");
|
||||
solUnimplementedAssert(arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), "");
|
||||
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
m_code << "revert(0, 0)\n";
|
||||
@ -1947,6 +1956,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";
|
||||
@ -2275,6 +2291,11 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else if (dynamic_cast<ErrorDefinition const*>(declaration))
|
||||
{
|
||||
// TODO should this rather be an assertion? Can we get here?
|
||||
// no-op
|
||||
}
|
||||
else if (dynamic_cast<EnumDefinition const*>(declaration))
|
||||
{
|
||||
// no-op
|
||||
|
@ -725,6 +725,8 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
arrayPop(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Event:
|
||||
case FunctionType::Kind::Error:
|
||||
// TODO but are side-effects of arguments taken into account?
|
||||
// This can be safely ignored.
|
||||
break;
|
||||
case FunctionType::Kind::ObjectCreation:
|
||||
|
@ -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, documentation, parameters);
|
||||
}
|
||||
|
||||
ASTPointer<UsingForDirective> Parser::parseUsingDirective()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
|
@ -102,6 +102,7 @@ private:
|
||||
);
|
||||
ASTPointer<ModifierDefinition> parseModifierDefinition();
|
||||
ASTPointer<EventDefinition> parseEventDefinition();
|
||||
ASTPointer<ErrorDefinition> parseErrorDefinition();
|
||||
ASTPointer<UsingForDirective> parseUsingDirective();
|
||||
ASTPointer<ModifierInvocation> parseModifierInvocation();
|
||||
ASTPointer<Identifier> parseIdentifier();
|
||||
|
6
test/libsolidity/syntaxTests/errors/basic.sol
Normal file
6
test/libsolidity/syntaxTests/errors/basic.sol
Normal file
@ -0,0 +1,6 @@
|
||||
contract C {
|
||||
error MyError();
|
||||
error MyError2(uint x);
|
||||
error MyError3(uint x, bytes);
|
||||
}
|
||||
// ----
|
9
test/libsolidity/syntaxTests/errors/basic_usage.sol
Normal file
9
test/libsolidity/syntaxTests/errors/basic_usage.sol
Normal file
@ -0,0 +1,9 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
revert(E());
|
||||
}
|
||||
function g() pure {
|
||||
bool x;
|
||||
require(x, E());
|
||||
}
|
||||
// ----
|
@ -0,0 +1,9 @@
|
||||
error E(uint,string);
|
||||
function f() pure {
|
||||
revert(E(2, "abc"));
|
||||
}
|
||||
function g(string storage s) pure {
|
||||
bool x;
|
||||
require(x, E(7, s));
|
||||
}
|
||||
// ----
|
@ -0,0 +1,13 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
revert((E()));
|
||||
}
|
||||
function g() pure {
|
||||
bool x;
|
||||
require(x, (E()));
|
||||
}
|
||||
// ----
|
||||
// TypeError 6473: (43-46): Tuple component cannot be empty.
|
||||
// TypeError 4423: (42-47): Expected error or string.
|
||||
// TypeError 6473: (100-103): Tuple component cannot be empty.
|
||||
// TypeError 4423: (99-104): Expected error or string.
|
@ -0,0 +1,4 @@
|
||||
function Err() pure {}
|
||||
error Err();
|
||||
// ----
|
||||
// DeclarationError 2333: (23-35): Identifier already declared.
|
@ -0,0 +1,4 @@
|
||||
contract A { function Err() public pure {} }
|
||||
contract B is A { error Err(); }
|
||||
// ----
|
||||
// DeclarationError 9097: (63-75): Identifier already declared.
|
@ -0,0 +1,3 @@
|
||||
error Error(uint);
|
||||
// ----
|
||||
// SyntaxError 1855: (44-62): The built-in errors "Error" and "Panic" cannot be re-defined.
|
6
test/libsolidity/syntaxTests/errors/file_level.sol
Normal file
6
test/libsolidity/syntaxTests/errors/file_level.sol
Normal file
@ -0,0 +1,6 @@
|
||||
error MyError();
|
||||
error MyError2(uint x);
|
||||
contract C {
|
||||
error MyError3(uint x, bytes);
|
||||
}
|
||||
// ----
|
@ -0,0 +1,6 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
revert((E)());
|
||||
}
|
||||
// ----
|
||||
// TypeError 4423: (42-47): Expected error or string.
|
5
test/libsolidity/syntaxTests/errors/named_error.sol
Normal file
5
test/libsolidity/syntaxTests/errors/named_error.sol
Normal file
@ -0,0 +1,5 @@
|
||||
error E(uint a);
|
||||
function f() pure {
|
||||
revert(E({a: 2}));
|
||||
}
|
||||
// ----
|
6
test/libsolidity/syntaxTests/errors/named_require.sol
Normal file
6
test/libsolidity/syntaxTests/errors/named_require.sol
Normal file
@ -0,0 +1,6 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
require({error: E()});
|
||||
}
|
||||
// ----
|
||||
// TypeError 1886: (35-56): Named arguments cannot be used for this function call.
|
6
test/libsolidity/syntaxTests/errors/named_revert.sol
Normal file
6
test/libsolidity/syntaxTests/errors/named_revert.sol
Normal file
@ -0,0 +1,6 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
revert({error: E()});
|
||||
}
|
||||
// ----
|
||||
// TypeError 1886: (35-55): Named arguments cannot be used for this function call.
|
@ -0,0 +1,7 @@
|
||||
error E1();
|
||||
error E2();
|
||||
function f() pure {
|
||||
revert(E1(E2));
|
||||
}
|
||||
// ----
|
||||
// TypeError 6160: (55-61): Wrong argument count for function call: 1 arguments given but expected 0.
|
@ -0,0 +1,7 @@
|
||||
error E1(uint);
|
||||
error E2();
|
||||
function f() pure {
|
||||
revert(E1(E2));
|
||||
}
|
||||
// ----
|
||||
// TypeError 9553: (62-64): Invalid type for argument in function call. Invalid implicit conversion from function () pure to uint256 requested.
|
@ -0,0 +1,7 @@
|
||||
error E1(uint);
|
||||
error E2();
|
||||
function f() pure {
|
||||
revert(E1(E2));
|
||||
}
|
||||
// ----
|
||||
// TypeError 9553: (62-64): Invalid type for argument in function call. Invalid implicit conversion from function () pure to uint256 requested.
|
@ -0,0 +1,7 @@
|
||||
error E1(uint);
|
||||
error E2();
|
||||
function f() pure {
|
||||
revert(E1([E2()]));
|
||||
}
|
||||
// ----
|
||||
// TypeError 5604: (63-67): Array component cannot be empty.
|
9
test/libsolidity/syntaxTests/errors/no_mappings.sol
Normal file
9
test/libsolidity/syntaxTests/errors/no_mappings.sol
Normal 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.
|
4
test/libsolidity/syntaxTests/errors/no_overloading.sol
Normal file
4
test/libsolidity/syntaxTests/errors/no_overloading.sol
Normal file
@ -0,0 +1,4 @@
|
||||
error Err(uint);
|
||||
error Err(bytes32);
|
||||
// ----
|
||||
// DeclarationError 2333: (17-36): Identifier already declared.
|
@ -0,0 +1,8 @@
|
||||
contract A {
|
||||
error Err(uint);
|
||||
}
|
||||
contract B is A {
|
||||
error Err(bytes32);
|
||||
}
|
||||
// ----
|
||||
// DeclarationError 9097: (58-77): Identifier already declared.
|
@ -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.
|
@ -0,0 +1,5 @@
|
||||
// TODO: What if an error is imported and alias as Panic?
|
||||
// TODO I Think the best way would be to have Error in the global scope.
|
||||
error Panic(bytes2);
|
||||
// ----
|
||||
// SyntaxError 1855: (131-151): The built-in errors "Error" and "Panic" cannot be re-defined.
|
@ -0,0 +1,6 @@
|
||||
// Test that the parser workaround is not breaking.
|
||||
struct error {uint a;}
|
||||
contract C {
|
||||
error x;
|
||||
}
|
||||
// ----
|
6
test/libsolidity/syntaxTests/errors/usage_standalone.sol
Normal file
6
test/libsolidity/syntaxTests/errors/usage_standalone.sol
Normal file
@ -0,0 +1,6 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
E();
|
||||
}
|
||||
// ----
|
||||
// TypeError 7757: (35-38): Errors can only be created directly inside require or revert calls.
|
@ -0,0 +1,7 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
// TODO is it a problem that we do not get an error here?
|
||||
// we should at least get "statement has no effect"
|
||||
E;
|
||||
}
|
||||
// ----
|
@ -0,0 +1,6 @@
|
||||
error E1();
|
||||
function f() pure {
|
||||
revert(E1{gas: 10}());
|
||||
}
|
||||
// ----
|
||||
// TypeError 2193: (43-54): Function call options can only be set on external function calls or contract creations.
|
@ -0,0 +1,7 @@
|
||||
error E1();
|
||||
error E2();
|
||||
function f() pure {
|
||||
revert(E1);
|
||||
}
|
||||
// ----
|
||||
// TypeError 4423: (55-57): Expected error or string.
|
6
test/libsolidity/syntaxTests/errors/using_structs.sol
Normal file
6
test/libsolidity/syntaxTests/errors/using_structs.sol
Normal file
@ -0,0 +1,6 @@
|
||||
struct S {uint a;}
|
||||
contract C {
|
||||
error MyError(S);
|
||||
error MyError2(S t);
|
||||
}
|
||||
// ----
|
6
test/libsolidity/syntaxTests/errors/wrong_arguments.sol
Normal file
6
test/libsolidity/syntaxTests/errors/wrong_arguments.sol
Normal file
@ -0,0 +1,6 @@
|
||||
error E(uint,string);
|
||||
function f() pure {
|
||||
revert(E(2, 3));
|
||||
}
|
||||
// ----
|
||||
// TypeError 9553: (58-59): Invalid type for argument in function call. Invalid implicit conversion from int_const 3 to string memory requested.
|
@ -0,0 +1,10 @@
|
||||
error E();
|
||||
function f() pure {
|
||||
require();
|
||||
require(true, E(), 8);
|
||||
revert(E(), 3);
|
||||
}
|
||||
// ----
|
||||
// TypeError 7445: (35-44): Function "require" needs 1 or 2 arguments, but provided 0.
|
||||
// TypeError 7445: (50-71): Function "require" needs 1 or 2 arguments, but provided 3.
|
||||
// TypeError 7445: (77-91): Function "revert" needs 0 or 1 arguments, but provided 2.
|
@ -5,4 +5,4 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 2144: (81-87): No matching declaration found after variable lookup.
|
||||
// Warning 6133: (81-87): Statement has no effect.
|
||||
|
@ -3,4 +3,4 @@ contract C {
|
||||
function f() pure public { require; }
|
||||
}
|
||||
// ----
|
||||
// TypeError 2144: (101-108): No matching declaration found after variable lookup.
|
||||
// Warning 6133: (101-108): Statement has no effect.
|
||||
|
@ -26,4 +26,4 @@ contract C {
|
||||
// TypeError 5547: (401-404): Empty tuple on the left hand side.
|
||||
// TypeError 4289: (399-466): Compound assignment is not allowed for tuple types.
|
||||
// TypeError 7407: (410-466): Type bytes32 is not implicitly convertible to expected type tuple().
|
||||
// TypeError 9322: (389-396): No matching declaration found after argument-dependent lookup.
|
||||
// TypeError 2956: (399-466): Invalid type for argument in function call. Invalid implicit conversion from tuple() to bool requested.
|
||||
|
Loading…
Reference in New Issue
Block a user