Extend using-for.

This commit is contained in:
hrkrshnn
2022-03-14 12:33:44 +01:00
committed by chriseth
parent db30f4d495
commit 672951ccc7
55 changed files with 992 additions and 150 deletions
@@ -25,6 +25,7 @@
#include <liblangutil/ErrorReporter.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/Visitor.h>
#include <range/v3/view/transform.hpp>
@@ -451,12 +452,39 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor)
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.libraryName().annotation().referencedDeclaration
);
if (_usingFor.usesBraces())
{
for (ASTPointer<IdentifierPath> const& function: _usingFor.functionsOrLibrary())
if (auto functionDefinition = dynamic_cast<FunctionDefinition const*>(function->annotation().referencedDeclaration))
{
if (!functionDefinition->isFree() && !(
dynamic_cast<ContractDefinition const*>(functionDefinition->scope()) &&
dynamic_cast<ContractDefinition const*>(functionDefinition->scope())->isLibrary()
))
m_errorReporter.typeError(
4167_error,
function->location(),
"Only file-level functions and library functions can be bound to a type in a \"using\" statement"
);
}
else
m_errorReporter.fatalTypeError(8187_error, function->location(), "Expected function name." );
}
else
{
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
m_errorReporter.fatalTypeError(
4357_error,
_usingFor.functionsOrLibrary().front()->location(),
"Library name expected. If you want to attach a function, use '{...}'."
);
}
if (!library || !library->isLibrary())
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected.");
// We do not visit _usingFor.functions() because it will lead to an error since
// library names cannot be mentioned stand-alone.
if (_usingFor.typeName())
_usingFor.typeName()->accept(*this);
+24
View File
@@ -403,6 +403,30 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
m_currentContractKind = std::nullopt;
}
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
{
if (!m_currentContractKind && !_usingFor.typeName())
m_errorReporter.syntaxError(
8118_error,
_usingFor.location(),
"The type has to be specified explicitly at file level (cannot use '*')."
);
else if (_usingFor.usesBraces() && !_usingFor.typeName())
m_errorReporter.syntaxError(
3349_error,
_usingFor.location(),
"The type has to be specified explicitly when attaching specific functions."
);
if (m_currentContractKind == ContractKind::Interface)
m_errorReporter.syntaxError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
);
return true;
}
bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");
+3
View File
@@ -88,6 +88,9 @@ private:
bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;
bool visit(UsingForDirective const& _usingFor) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(FunctionTypeName const& _node) override;
+61 -5
View File
@@ -35,6 +35,7 @@
#include <libsolutil/Algorithms.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/Views.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
@@ -3626,12 +3627,67 @@ void TypeChecker::endVisit(Literal const& _literal)
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{
if (m_currentContract->isInterface())
m_errorReporter.typeError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
if (!_usingFor.usesBraces())
{
solAssert(_usingFor.functionsOrLibrary().size() == 1);
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
_usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration
);
solAssert(library && library->isLibrary());
// No type checking for libraries
return;
}
if (!_usingFor.typeName())
{
solAssert(m_errorReporter.hasErrors());
return;
}
solAssert(_usingFor.typeName()->annotation().type);
Type const* normalizedType = TypeProvider::withLocationIfReference(
DataLocation::Storage,
_usingFor.typeName()->annotation().type
);
solAssert(normalizedType);
for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
{
solAssert(path->annotation().referencedDeclaration);
FunctionDefinition const& functionDefinition =
dynamic_cast<FunctionDefinition const&>(*path->annotation().referencedDeclaration);
solAssert(functionDefinition.type());
if (functionDefinition.parameters().empty())
m_errorReporter.fatalTypeError(
4731_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" " +
"does not have any parameters, and therefore cannot be bound to the type \"" +
(normalizedType ? normalizedType->toString(true) : "*") + "\"."
);
FunctionType const* functionType = dynamic_cast<FunctionType const&>(*functionDefinition.type()).asBoundFunction();
solAssert(functionType && functionType->selfType(), "");
BoolResult result = normalizedType->isImplicitlyConvertibleTo(
*TypeProvider::withLocationIfReference(DataLocation::Storage, functionType->selfType())
);
if (!result)
m_errorReporter.typeError(
3100_error,
path->location(),
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
"cannot be bound to the type \"" + _usingFor.typeName()->annotation().type->toString() +
"\" because the type cannot be implicitly converted to the first argument" +
" of the function (\"" + functionType->selfType()->toString() + "\")" +
(
result.message().empty() ?
"." :
": " + result.message()
)
);
}
}
void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)
+21 -8
View File
@@ -33,6 +33,7 @@
#include <libevmasm/Instruction.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h>
#include <libsolutil/Visitor.h>
#include <json/json.h>
@@ -630,9 +631,16 @@ private:
};
/**
* `using LibraryName for uint` will attach all functions from the library LibraryName
* to `uint` if the first parameter matches the type. `using LibraryName for *` attaches
* the function to any matching type.
* Using for directive:
*
* 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`
* 2. `using LibraryName for *` attaches to all types.
* 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ...,
* `fn`, respectively to `T`.
*
* For version 3, T has to be implicitly convertible to the first parameter type of
* all functions, and this is checked at the point of the using statement. For versions 1 and
* 2, this check is only done when a function is called.
*/
class UsingForDirective: public ASTNode
{
@@ -640,23 +648,28 @@ public:
UsingForDirective(
int64_t _id,
SourceLocation const& _location,
ASTPointer<IdentifierPath> _libraryName,
std::vector<ASTPointer<IdentifierPath>> _functions,
bool _usesBraces,
ASTPointer<TypeName> _typeName
):
ASTNode(_id, _location), m_libraryName(std::move(_libraryName)), m_typeName(std::move(_typeName))
ASTNode(_id, _location), m_functions(_functions), m_usesBraces(_usesBraces), m_typeName(std::move(_typeName))
{
solAssert(m_libraryName != nullptr, "Name cannot be null.");
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
IdentifierPath const& libraryName() const { return *m_libraryName; }
/// @returns the type name the library is attached to, null for `*`.
TypeName const* typeName() const { return m_typeName.get(); }
/// @returns a list of functions or the single library.
std::vector<ASTPointer<IdentifierPath>> const& functionsOrLibrary() const { return m_functions; }
bool usesBraces() const { return m_usesBraces; }
private:
ASTPointer<IdentifierPath> m_libraryName;
/// Either the single library or a list of functions.
std::vector<ASTPointer<IdentifierPath>> m_functions;
bool m_usesBraces;
ASTPointer<TypeName> m_typeName;
};
+19 -3
View File
@@ -32,6 +32,7 @@
#include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/Keccak256.h>
#include <boost/algorithm/string/join.hpp>
@@ -311,10 +312,25 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
bool ASTJsonConverter::visit(UsingForDirective const& _node)
{
setJsonNode(_node, "UsingForDirective", {
make_pair("libraryName", toJson(_node.libraryName())),
vector<pair<string, Json::Value>> attributes = {
make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue)
});
};
if (_node.usesBraces())
{
Json::Value functionList;
for (auto const& function: _node.functionsOrLibrary())
{
Json::Value functionNode;
functionNode["function"] = toJson(*function);
functionList.append(move(functionNode));
}
attributes.emplace_back("functionList", move(functionList));
}
else
attributes.emplace_back("libraryName", toJson(*_node.functionsOrLibrary().front()));
setJsonNode(_node, "UsingForDirective", move(attributes));
return false;
}
+9 -1
View File
@@ -348,9 +348,17 @@ ASTPointer<InheritanceSpecifier> ASTJsonImporter::createInheritanceSpecifier(Jso
ASTPointer<UsingForDirective> ASTJsonImporter::createUsingForDirective(Json::Value const& _node)
{
vector<ASTPointer<IdentifierPath>> functions;
if (_node.isMember("libraryName"))
functions.emplace_back(createIdentifierPath(_node["libraryName"]));
else if (_node.isMember("functionList"))
for (Json::Value const& function: _node["functionList"])
functions.emplace_back(createIdentifierPath(function["function"]));
return createASTNode<UsingForDirective>(
_node,
createIdentifierPath(member(_node, "libraryName")),
move(functions),
!_node.isMember("libraryName"),
_node["typeName"].isNull() ? nullptr : convertJsonToASTNode<TypeName>(_node["typeName"])
);
}
+2 -2
View File
@@ -194,7 +194,7 @@ void UsingForDirective::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_libraryName->accept(_visitor);
listAccept(functionsOrLibrary(), _visitor);
if (m_typeName)
m_typeName->accept(_visitor);
}
@@ -205,7 +205,7 @@ void UsingForDirective::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_libraryName->accept(_visitor);
listAccept(functionsOrLibrary(), _visitor);
if (m_typeName)
m_typeName->accept(_visitor);
}
+49 -22
View File
@@ -35,6 +35,7 @@
#include <libsolutil/Keccak256.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/UTF8.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
@@ -331,30 +332,48 @@ Type const* Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _scope)
{
vector<UsingForDirective const*> usingForDirectives;
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope))
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());
else if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope))
usingForDirectives +=
contract->usingForDirectives() +
ASTNode::filteredNodes<UsingForDirective>(contract->sourceUnit().nodes());
SourceUnit const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope);
if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope))
{
sourceUnit = &contract->sourceUnit();
usingForDirectives += contract->usingForDirectives();
}
else
solAssert(false, "");
solAssert(sourceUnit, "");
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());
// Normalise data location of type.
DataLocation typeLocation = DataLocation::Storage;
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
typeLocation = refType->location();
set<Declaration const*> seenFunctions;
MemberList::MemberMap members;
set<pair<string, Declaration const*>> seenFunctions;
auto addFunction = [&](FunctionDefinition const& _function, optional<string> _name = {})
{
if (!_name)
_name = _function.name();
Type const* functionType =
_function.libraryFunction() ? _function.typeViaContractName() : _function.type();
solAssert(functionType, "");
FunctionType const* asBoundFunction =
dynamic_cast<FunctionType const&>(*functionType).asBoundFunction();
solAssert(asBoundFunction, "");
if (_type.isImplicitlyConvertibleTo(*asBoundFunction->selfType()))
if (seenFunctions.insert(make_pair(*_name, &_function)).second)
members.emplace_back(&_function, asBoundFunction, *_name);
};
for (UsingForDirective const* ufd: usingForDirectives)
{
// Convert both types to pointers for comparison to see if the `using for`
// directive applies.
// Further down, we check more detailed for each function if `_type` is
// convertible to the function parameter type.
if (ufd->typeName() &&
if (
ufd->typeName() &&
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
*TypeProvider::withLocationIfReference(
typeLocation,
@@ -363,20 +382,28 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc
)
)
continue;
auto const& library = dynamic_cast<ContractDefinition const&>(
*ufd->libraryName().annotation().referencedDeclaration
);
for (FunctionDefinition const* function: library.definedFunctions())
for (auto const& pathPointer: ufd->functionsOrLibrary())
{
if (!function->isOrdinary() || !function->isVisibleAsLibraryMember() || seenFunctions.count(function))
continue;
seenFunctions.insert(function);
if (function->parameters().empty())
continue;
FunctionTypePointer fun =
dynamic_cast<FunctionType const&>(*function->typeViaContractName()).asBoundFunction();
if (_type.isImplicitlyConvertibleTo(*fun->selfType()))
members.emplace_back(function, fun);
solAssert(pathPointer);
Declaration const* declaration = pathPointer->annotation().referencedDeclaration;
solAssert(declaration);
if (ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(declaration))
{
solAssert(library->isLibrary());
for (FunctionDefinition const* function: library->definedFunctions())
{
if (!function->isOrdinary() || !function->isVisibleAsLibraryMember() || function->parameters().empty())
continue;
addFunction(*function);
}
}
else
addFunction(
dynamic_cast<FunctionDefinition const&>(*declaration),
pathPointer->path().back()
);
}
}
+20 -2
View File
@@ -117,6 +117,9 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
case Token::Type:
nodes.push_back(parseUserDefinedValueTypeDefinition());
break;
case Token::Using:
nodes.push_back(parseUsingDirective());
break;
case Token::Function:
nodes.push_back(parseFunctionDefinition(true));
break;
@@ -962,7 +965,22 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Using);
ASTPointer<IdentifierPath> library(parseIdentifierPath());
vector<ASTPointer<IdentifierPath>> functions;
bool const usesBraces = m_scanner->currentToken() == Token::LBrace;
if (usesBraces)
{
do
{
advance();
functions.emplace_back(parseIdentifierPath());
}
while (m_scanner->currentToken() == Token::Comma);
expectToken(Token::RBrace);
}
else
functions.emplace_back(parseIdentifierPath());
ASTPointer<TypeName> typeName;
expectToken(Token::For);
if (m_scanner->currentToken() == Token::Mul)
@@ -971,7 +989,7 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
typeName = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UsingForDirective>(library, typeName);
return nodeFactory.createNode<UsingForDirective>(move(functions), usesBraces, typeName);
}
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()