Syntax for custom errors.

This commit is contained in:
chriseth 2021-01-28 12:56:22 +01:00
parent 1716dcfb57
commit 780fa1e658
67 changed files with 708 additions and 54 deletions

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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>
@ -681,30 +682,12 @@ void TypeChecker::visitManually(
bool TypeChecker::visit(EventDefinition const& _eventDef)
{
solAssert(_eventDef.visibility() > Visibility::Internal, "");
unsigned numIndexed = 0;
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
{
if (var->isIndexed())
numIndexed++;
if (type(*var)->containsNestedMapping())
m_errorReporter.typeError(
3448_error,
var->location(),
"Type containing a (nested) mapping is not allowed as event parameter type."
);
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
!useABICoderV2() &&
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
)
m_errorReporter.typeError(
3061_error,
var->location(),
"This type is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
checkErrorAndEventParameters(_eventDef);
auto numIndexed = ranges::count_if(
_eventDef.parameters(),
[](ASTPointer<VariableDeclaration> const& var) { return var->isIndexed(); }
);
if (_eventDef.isAnonymous() && numIndexed > 4)
m_errorReporter.typeError(8598_error, _eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
@ -712,6 +695,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
return true;
}
bool TypeChecker::visit(ErrorDefinition const& _errorDef)
{
solAssert(_errorDef.visibility() > Visibility::Internal, "");
checkErrorAndEventParameters(_errorDef);
return true;
}
void TypeChecker::endVisit(FunctionTypeName const& _funType)
{
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
@ -2029,6 +2019,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

View File

@ -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*>()

View File

@ -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())
{

View File

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

View File

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

@ -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 = {

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

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

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

View File

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

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

@ -2729,13 +2729,34 @@ 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() == m_returnParameterTypes.size(),
"Return parameter names list must match return parameter types list!"
);
}
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):
@ -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();

View File

@ -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).

View File

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

View File

@ -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";
@ -1945,7 +1954,14 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration),
"Event not found"
);
// the call will do the resolving
// 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());
@ -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

View File

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

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, 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,6 @@
contract C {
error MyError();
error MyError2(uint x);
error MyError3(uint x, bytes);
}
// ----

View File

@ -0,0 +1,9 @@
error E();
function f() pure {
revert(E());
}
function g() pure {
bool x;
require(x, E());
}
// ----

View File

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

View File

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

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,3 @@
error Error(uint);
// ----
// SyntaxError 1855: (44-62): 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 @@
error E();
function f() pure {
revert((E)());
}
// ----
// TypeError 4423: (42-47): Expected error or string.

View File

@ -0,0 +1,5 @@
error E(uint a);
function f() pure {
revert(E({a: 2}));
}
// ----

View 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.

View 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.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
error E1(uint);
error E2();
function f() pure {
revert(E1([E2()]));
}
// ----
// TypeError 5604: (63-67): Array component cannot be empty.

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

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,6 @@
error E();
function f() pure {
E();
}
// ----
// TypeError 7757: (35-38): Errors can only be created directly inside require or revert calls.

View File

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

View File

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

View File

@ -0,0 +1,7 @@
error E1();
error E2();
function f() pure {
revert(E1);
}
// ----
// TypeError 4423: (55-57): Expected error or string.

View File

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

View 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.

View File

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

View File

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

View File

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

View File

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