mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Syntax changes for catching custom errors.
This commit is contained in:
parent
b3261d7b0f
commit
edf3529db6
@ -994,9 +994,10 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
||||
TryCatchClause const* panicClause = nullptr;
|
||||
TryCatchClause const* errorClause = nullptr;
|
||||
TryCatchClause const* lowLevelClause = nullptr;
|
||||
map<ErrorDefinition const*, TryCatchClause const*> seenErrors;
|
||||
for (auto const& clause: _tryStatement.clauses() | ranges::views::drop_exactly(1) | views::dereferenceChecked)
|
||||
{
|
||||
if (clause.errorName() == "")
|
||||
if (clause.kind() == TryCatchClause::Kind::Fallback)
|
||||
{
|
||||
if (lowLevelClause)
|
||||
m_errorReporter.typeError(
|
||||
@ -1022,59 +1023,85 @@ void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
||||
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (clause.errorName() == "Error" || clause.errorName() == "Panic")
|
||||
{
|
||||
if (!m_evmVersion.supportsReturndata())
|
||||
m_errorReporter.typeError(
|
||||
1812_error,
|
||||
clause.location(),
|
||||
"This catch clause type cannot be used on the selected EVM version (" +
|
||||
m_evmVersion.name() +
|
||||
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
|
||||
);
|
||||
|
||||
if (clause.errorName() == "Error")
|
||||
{
|
||||
if (errorClause)
|
||||
m_errorReporter.typeError(
|
||||
1036_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
|
||||
"This try statement already has an \"Error\" catch clause."
|
||||
);
|
||||
errorClause = &clause;
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != 1 ||
|
||||
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
|
||||
)
|
||||
m_errorReporter.typeError(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (panicClause)
|
||||
m_errorReporter.typeError(
|
||||
6732_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", panicClause->location()),
|
||||
"This try statement already has a \"Panic\" catch clause."
|
||||
);
|
||||
panicClause = &clause;
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != 1 ||
|
||||
*clause.parameters()->parameters().front()->type() != *TypeProvider::uint256()
|
||||
)
|
||||
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
|
||||
}
|
||||
if (!m_evmVersion.supportsReturndata())
|
||||
m_errorReporter.typeError(
|
||||
1812_error,
|
||||
clause.location(),
|
||||
"This catch clause type cannot be used on the selected EVM version (" +
|
||||
m_evmVersion.name() +
|
||||
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
|
||||
);
|
||||
|
||||
if (clause.kind() == TryCatchClause::Kind::Error)
|
||||
{
|
||||
if (errorClause)
|
||||
m_errorReporter.typeError(
|
||||
1036_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
|
||||
"This try statement already has an \"Error\" catch clause."
|
||||
);
|
||||
errorClause = &clause;
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != 1 ||
|
||||
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
|
||||
)
|
||||
m_errorReporter.typeError(2943_error, clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
|
||||
}
|
||||
else if (clause.kind() == TryCatchClause::Kind::Panic)
|
||||
{
|
||||
if (panicClause)
|
||||
m_errorReporter.typeError(
|
||||
6732_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", panicClause->location()),
|
||||
"This try statement already has a \"Panic\" catch clause."
|
||||
);
|
||||
panicClause = &clause;
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != 1 ||
|
||||
*clause.parameters()->parameters().front()->type() != *TypeProvider::uint256()
|
||||
)
|
||||
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
|
||||
}
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
3542_error,
|
||||
clause.location(),
|
||||
"Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`."
|
||||
);
|
||||
{
|
||||
solAssert(clause.kind() == TryCatchClause::Kind::UserDefined, "");
|
||||
solAssert(*clause.errorName().annotation().requiredLookup == VirtualLookup::Static, "");
|
||||
ErrorDefinition const* error = dynamic_cast<ErrorDefinition const*>(clause.errorName().annotation().referencedDeclaration);
|
||||
if (!error)
|
||||
{
|
||||
m_errorReporter.typeError(1178_error, clause.location(), "Expected the name of an error.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!seenErrors.emplace(error, &clause).second)
|
||||
m_errorReporter.typeError(
|
||||
6853_error,
|
||||
clause.location(),
|
||||
SecondarySourceLocation{}.append("The first clause is here:", seenErrors[error]->location()),
|
||||
"This try statement already has a \"" + error->name() + "\" catch clause."
|
||||
);
|
||||
if (
|
||||
!clause.parameters() ||
|
||||
clause.parameters()->parameters().size() != error->parameters().size()
|
||||
)
|
||||
m_errorReporter.typeError(1271_error, clause.location(), "Expected `catch Panic(uint ...) { ... }`.");
|
||||
else
|
||||
for (auto&& [varDecl, parameter]: ranges::views::zip(clause.parameters()->parameters(), error->parameters()))
|
||||
if (*varDecl->type() != *parameter->type())
|
||||
m_errorReporter.typeError(
|
||||
63958_error,
|
||||
varDecl->location(),
|
||||
("Expected a parameter of type \"" + parameter->type()->toString(true) + "\" ") +
|
||||
("but got \"" + varDecl->type()->toString(true) + "\"")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,13 +856,14 @@ string Literal::getChecksummedAddress() const
|
||||
TryCatchClause const* TryStatement::successClause() const
|
||||
{
|
||||
solAssert(m_clauses.size() > 0, "");
|
||||
solAssert(m_clauses.front()->kind() == TryCatchClause::Kind::Success, "");
|
||||
return m_clauses[0].get();
|
||||
}
|
||||
|
||||
TryCatchClause const* TryStatement::panicClause() const
|
||||
{
|
||||
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||
if (m_clauses[i]->errorName() == "Panic")
|
||||
if (m_clauses[i]->kind() == TryCatchClause::Kind::Panic)
|
||||
return m_clauses[i].get();
|
||||
return nullptr;
|
||||
}
|
||||
@ -870,7 +871,7 @@ TryCatchClause const* TryStatement::panicClause() const
|
||||
TryCatchClause const* TryStatement::errorClause() const
|
||||
{
|
||||
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||
if (m_clauses[i]->errorName() == "Error")
|
||||
if (m_clauses[i]->kind() == TryCatchClause::Kind::Error)
|
||||
return m_clauses[i].get();
|
||||
return nullptr;
|
||||
}
|
||||
@ -878,7 +879,7 @@ TryCatchClause const* TryStatement::errorClause() const
|
||||
TryCatchClause const* TryStatement::fallbackClause() const
|
||||
{
|
||||
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||
if (m_clauses[i]->errorName().empty())
|
||||
if (m_clauses[i]->kind() == TryCatchClause::Kind::Fallback)
|
||||
return m_clauses[i].get();
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1503,29 +1503,46 @@ private:
|
||||
class TryCatchClause: public ASTNode, public Scopable, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
enum Kind
|
||||
{
|
||||
Success,
|
||||
Panic,
|
||||
Error,
|
||||
Fallback,
|
||||
UserDefined
|
||||
};
|
||||
|
||||
TryCatchClause(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> _errorName,
|
||||
Kind _kind,
|
||||
ASTPointer<IdentifierPath> _errorName,
|
||||
ASTPointer<ParameterList> _parameters,
|
||||
ASTPointer<Block> _block
|
||||
):
|
||||
ASTNode(_id, _location),
|
||||
m_kind(_kind),
|
||||
m_errorName(std::move(_errorName)),
|
||||
m_parameters(std::move(_parameters)),
|
||||
m_block(std::move(_block))
|
||||
{}
|
||||
{
|
||||
solAssert(!!m_errorName == (m_kind == Kind::UserDefined), "");
|
||||
}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
ASTString const& errorName() const { return *m_errorName; }
|
||||
|
||||
Kind kind() const { return m_kind; }
|
||||
/// @returns the name of the error. Should only be called if catch kind is UserDefined.
|
||||
IdentifierPath const& errorName() const { return *m_errorName; }
|
||||
ParameterList const* parameters() const { return m_parameters.get(); }
|
||||
Block const& block() const { return *m_block; }
|
||||
|
||||
TryCatchClauseAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
ASTPointer<ASTString> m_errorName;
|
||||
Kind m_kind;
|
||||
ASTPointer<IdentifierPath> m_errorName;
|
||||
ASTPointer<ParameterList> m_parameters;
|
||||
ASTPointer<Block> m_block;
|
||||
};
|
||||
@ -1535,6 +1552,8 @@ private:
|
||||
* Syntax:
|
||||
* try <call> returns (uint x, uint y) {
|
||||
* // success code
|
||||
* } catch Custom(uint data) {
|
||||
* // custom error handler
|
||||
* } catch Panic(uint errorCode) {
|
||||
* // panic
|
||||
* } catch Error(string memory cause) {
|
||||
|
@ -578,7 +578,10 @@ bool ASTJsonConverter::visit(IfStatement const& _node)
|
||||
bool ASTJsonConverter::visit(TryCatchClause const& _node)
|
||||
{
|
||||
setJsonNode(_node, "TryCatchClause", {
|
||||
make_pair("errorName", _node.errorName()),
|
||||
make_pair("kind", catchClauseKind(_node.kind())),
|
||||
make_pair("errorName", toJsonOrNull(
|
||||
_node.kind() == TryCatchClause::Kind::UserDefined ? &_node.errorName() : nullptr
|
||||
)),
|
||||
make_pair("parameters", toJsonOrNull(_node.parameters())),
|
||||
make_pair("block", toJson(_node.block()))
|
||||
});
|
||||
@ -931,6 +934,19 @@ string ASTJsonConverter::functionCallKind(FunctionCallKind _kind)
|
||||
}
|
||||
}
|
||||
|
||||
string ASTJsonConverter::catchClauseKind(TryCatchClause::Kind _kind)
|
||||
{
|
||||
switch (_kind)
|
||||
{
|
||||
case TryCatchClause::Kind::Success: return "success";
|
||||
case TryCatchClause::Kind::Panic: return "panic";
|
||||
case TryCatchClause::Kind::Error: return "error";
|
||||
case TryCatchClause::Kind::Fallback: return "fallback";
|
||||
case TryCatchClause::Kind::UserDefined: return "userDefined";
|
||||
}
|
||||
solAssert(false, "Unknown kind of catch clause.");
|
||||
}
|
||||
|
||||
string ASTJsonConverter::literalTokenKind(Token _token)
|
||||
{
|
||||
switch (_token)
|
||||
|
@ -153,6 +153,7 @@ private:
|
||||
static std::string location(VariableDeclaration::Location _location);
|
||||
static std::string contractKind(ContractKind _kind);
|
||||
static std::string functionCallKind(FunctionCallKind _kind);
|
||||
static std::string catchClauseKind(TryCatchClause::Kind _kind);
|
||||
static std::string literalTokenKind(Token _token);
|
||||
static std::string type(Expression const& _expression);
|
||||
static std::string type(VariableDeclaration const& _varDecl);
|
||||
|
@ -642,7 +642,8 @@ ASTPointer<TryCatchClause> ASTJsonImporter::createTryCatchClause(Json::Value con
|
||||
{
|
||||
return createASTNode<TryCatchClause>(
|
||||
_node,
|
||||
memberAsASTString(_node, "errorName"),
|
||||
tryCatchClauseKind(_node),
|
||||
nullOrCast<IdentifierPath>(member(_node, "errorName")),
|
||||
nullOrCast<ParameterList>(member(_node, "parameters")),
|
||||
convertJsonToASTNode<Block>(member(_node, "block"))
|
||||
);
|
||||
@ -959,6 +960,24 @@ bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name
|
||||
|
||||
// =========== JSON to definition helpers =======================
|
||||
|
||||
TryCatchClause::Kind ASTJsonImporter::tryCatchClauseKind(Json::Value const& _node)
|
||||
{
|
||||
astAssert(!member(_node, "kind").isNull(), "");
|
||||
if (_node["kind"].asString() == "success")
|
||||
return TryCatchClause::Kind::Success;
|
||||
else if (_node["kind"].asString() == "error")
|
||||
return TryCatchClause::Kind::Error;
|
||||
else if (_node["kind"].asString() == "panic")
|
||||
return TryCatchClause::Kind::Panic;
|
||||
else if (_node["kind"].asString() == "fallback")
|
||||
return TryCatchClause::Kind::Fallback;
|
||||
else if (_node["kind"].asString() == "userDefined")
|
||||
return TryCatchClause::Kind::UserDefined;
|
||||
else
|
||||
astAssert(false, "Unknown try catch clause kind");
|
||||
return {};
|
||||
}
|
||||
|
||||
ContractKind ASTJsonImporter::contractKind(Json::Value const& _node)
|
||||
{
|
||||
ContractKind kind;
|
||||
|
@ -144,6 +144,7 @@ private:
|
||||
Visibility visibility(Json::Value const& _node);
|
||||
StateMutability stateMutability(Json::Value const& _node);
|
||||
VariableDeclaration::Location location(Json::Value const& _node);
|
||||
TryCatchClause::Kind tryCatchClauseKind(Json::Value const& _node);
|
||||
ContractKind contractKind(Json::Value const& _node);
|
||||
Token literalTokenKind(Json::Value const& _node);
|
||||
Literal::SubDenomination subdenomination(Json::Value const& _node);
|
||||
|
@ -542,6 +542,8 @@ void TryCatchClause::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_errorName)
|
||||
m_errorName->accept(_visitor);
|
||||
if (m_parameters)
|
||||
m_parameters->accept(_visitor);
|
||||
m_block->accept(_visitor);
|
||||
@ -553,6 +555,8 @@ void TryCatchClause::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_errorName)
|
||||
m_errorName->accept(_visitor);
|
||||
if (m_parameters)
|
||||
m_parameters->accept(_visitor);
|
||||
m_block->accept(_visitor);
|
||||
|
@ -975,14 +975,14 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
||||
ASTPointer<TryCatchClause> panic{};
|
||||
ASTPointer<TryCatchClause> fallback{};
|
||||
for (size_t i = 1; i < _catchClauses.size(); ++i)
|
||||
if (_catchClauses[i]->errorName() == "Error")
|
||||
if (_catchClauses[i]->kind() == TryCatchClause::Kind::Error)
|
||||
error = _catchClauses[i];
|
||||
else if (_catchClauses[i]->errorName() == "Panic")
|
||||
else if (_catchClauses[i]->kind() == TryCatchClause::Kind::Panic)
|
||||
panic = _catchClauses[i];
|
||||
else if (_catchClauses[i]->errorName().empty())
|
||||
else if (_catchClauses[i]->kind() == TryCatchClause::Kind::Fallback)
|
||||
fallback = _catchClauses[i];
|
||||
else
|
||||
solAssert(false, "");
|
||||
solUnimplementedAssert(false, "");
|
||||
|
||||
solAssert(_catchClauses.size() == 1ul + (error ? 1 : 0) + (panic ? 1 : 0) + (fallback ? 1 : 0), "");
|
||||
|
||||
|
@ -1323,7 +1323,7 @@ ASTPointer<TryStatement> Parser::parseTryStatement(ASTPointer<ASTString> const&
|
||||
ASTPointer<Block> successBlock = parseBlock();
|
||||
successClauseFactory.setEndPositionFromNode(successBlock);
|
||||
clauses.emplace_back(successClauseFactory.createNode<TryCatchClause>(
|
||||
make_shared<ASTString>(), returnsParameters, successBlock
|
||||
TryCatchClause::Kind::Success, ASTPointer<IdentifierPath>{}, returnsParameters, successBlock
|
||||
));
|
||||
|
||||
do
|
||||
@ -1342,20 +1342,42 @@ ASTPointer<TryCatchClause> Parser::parseCatchClause()
|
||||
RecursionGuard recursionGuard(*this);
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
expectToken(Token::Catch);
|
||||
ASTPointer<ASTString> errorName = make_shared<string>();
|
||||
TryCatchClause::Kind kind;
|
||||
ASTPointer<IdentifierPath> errorName;
|
||||
ASTPointer<ParameterList> errorParameters;
|
||||
if (m_scanner->currentToken() != Token::LBrace)
|
||||
{
|
||||
if (m_scanner->currentToken() == Token::Identifier)
|
||||
errorName = expectIdentifierToken();
|
||||
{
|
||||
string name = m_scanner->currentLiteral();
|
||||
if (name == "Error")
|
||||
{
|
||||
kind = TryCatchClause::Kind::Error;
|
||||
m_scanner->next();
|
||||
}
|
||||
else if (name == "Panic")
|
||||
{
|
||||
kind = TryCatchClause::Kind::Panic;
|
||||
m_scanner->next();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorName = parseIdentifierPath();
|
||||
kind = TryCatchClause::Kind::UserDefined;
|
||||
}
|
||||
}
|
||||
else
|
||||
kind = TryCatchClause::Kind::Fallback;
|
||||
VarDeclParserOptions options;
|
||||
options.allowEmptyName = true;
|
||||
options.allowLocationSpecifier = true;
|
||||
errorParameters = parseParameterList(options, !errorName->empty());
|
||||
errorParameters = parseParameterList(options);
|
||||
}
|
||||
else
|
||||
kind = TryCatchClause::Kind::Fallback;
|
||||
ASTPointer<Block> block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
return nodeFactory.createNode<TryCatchClause>(errorName, errorParameters, block);
|
||||
return nodeFactory.createNode<TryCatchClause>(kind, errorName, errorParameters, block);
|
||||
}
|
||||
|
||||
ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString)
|
||||
|
@ -7,5 +7,4 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 3542: (93-119): Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`.
|
||||
// TypeError 3542: (120-143): Invalid catch clause name. Expected either `catch (...)`, `catch Error(...)`, or `catch Panic(...)`.
|
||||
// DeclarationError 7920: (99-105): Identifier not found or not unique.
|
||||
|
Loading…
Reference in New Issue
Block a user