diff options
-rw-r--r-- | Changelog.md | 3 | ||||
-rw-r--r-- | docs/contracts.rst | 29 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 35 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.h | 1 | ||||
-rw-r--r-- | libsolidity/ast/AST.h | 12 | ||||
-rw-r--r-- | libsolidity/parsing/Parser.cpp | 26 | ||||
-rw-r--r-- | libsolidity/parsing/Parser.h | 3 | ||||
-rw-r--r-- | libsolidity/parsing/Token.h | 2 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 35 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 145 | ||||
-rw-r--r-- | test/libsolidity/SolidityParser.cpp | 9 | ||||
-rw-r--r-- | test/libsolidity/SolidityTypes.cpp | 2 |
12 files changed, 288 insertions, 14 deletions
diff --git a/Changelog.md b/Changelog.md index 99089b46..2a267fd7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.4.11 (unreleased) +Features: + * Support ``interface`` contracts. + ### 0.4.10 (2017-03-15) Features: diff --git a/docs/contracts.rst b/docs/contracts.rst index 2ee04675..28c003bd 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -922,6 +922,35 @@ Such contracts cannot be compiled (even if they contain implemented functions al If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. +.. index:: ! contract;interface, ! interface contract + +********** +Interfaces +********** + +Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: + +#. Cannot inherit other contracts or interfaces. +#. Cannot define constructor. +#. Cannot define variables. +#. Cannot define structs. +#. Cannot define enums. + +Some of these restrictions might be lifted in the future. + +Interfaces are basically limited to what the Contract ABI can represent and the conversion between the ABI and +an Interface should be possible without any information loss. + +Interfaces are denoted by their own keyword: + +:: + + interface Token { + function transfer(address recipient, uint amount); + } + +Contracts can inherit interfaces as they would inherit other contracts. + .. index:: ! library, callcode, delegatecall .. _libraries: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 34ed8129..30e84f11 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -64,8 +64,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { m_scope = &_contract; - // We force our own visiting order here. - //@TODO structs will be visited again below, but it is probably fine. + // We force our own visiting order here. The structs have to be excluded below. + set<ASTNode const*> visited; + for (auto const& s: _contract.definedStructs()) + visited.insert(s); ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.baseContracts(), *this); @@ -113,7 +115,9 @@ bool TypeChecker::visit(ContractDefinition const& _contract) _contract.annotation().isFullyImplemented = false; } - ASTNode::listAccept(_contract.subNodes(), *this); + for (auto const& n: _contract.subNodes()) + if (!visited.count(n.get())) + n->accept(*this); checkContractExternalTypeClashes(_contract); // check for hash collisions in function signatures @@ -354,6 +358,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_inheritance.location(), "Interfaces cannot inherit."); + if (base->isLibrary()) typeError(_inheritance.location(), "Libraries cannot be inherited from."); @@ -396,6 +403,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) bool TypeChecker::visit(StructDefinition const& _struct) { + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_struct.location(), "Structs cannot be defined in interfaces."); + for (ASTPointer<VariableDeclaration> const& member: _struct.members()) if (!type(*member)->canBeStored()) typeError(member->location(), "Type cannot be used in struct."); @@ -451,6 +461,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function) dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : vector<ContractDefinition const*>() ); + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + { + if (_function.isImplemented()) + typeError(_function.location(), "Functions in interfaces cannot have an implementation."); + if (_function.visibility() < FunctionDefinition::Visibility::Public) + typeError(_function.location(), "Functions in interfaces cannot be internal or private."); + if (_function.isConstructor()) + typeError(_function.location(), "Constructor cannot be defined in interfaces."); + } if (_function.isImplemented()) _function.body().accept(*this); return false; @@ -458,6 +477,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_variable.location(), "Variables cannot be declared in interfaces."); + // Variables can be declared without type (with "var"), in which case the first assignment // sets the type. // Note that assignments before the first declaration are legal because of the special scoping @@ -504,6 +526,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) return false; } +bool TypeChecker::visit(EnumDefinition const& _enum) +{ + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_enum.location(), "Enumerable cannot be declared in interfaces."); + return false; +} + void TypeChecker::visitManually( ModifierInvocation const& _modifier, vector<ContractDefinition const*> const& _bases diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 46d8230a..88559f44 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -83,6 +83,7 @@ private: virtual bool visit(StructDefinition const& _struct) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(VariableDeclaration const& _variable) override; + virtual bool visit(EnumDefinition const& _enum) override; /// We need to do this manually because we want to pass the bases of the current contract in /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 8031760d..02234ffc 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -316,19 +316,21 @@ protected: class ContractDefinition: public Declaration, public Documented { public: + enum class ContractKind { Interface, Contract, Library }; + ContractDefinition( SourceLocation const& _location, ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _documentation, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<ASTNode>> const& _subNodes, - bool _isLibrary + ContractKind _contractKind = ContractKind::Contract ): Declaration(_location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), - m_isLibrary(_isLibrary) + m_contractKind(_contractKind) {} virtual void accept(ASTVisitor& _visitor) override; @@ -344,7 +346,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; - bool isLibrary() const { return m_isLibrary; } + bool isLibrary() const { return m_contractKind == ContractKind::Library; } /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. @@ -371,10 +373,12 @@ public: virtual ContractDefinitionAnnotation& annotation() const override; + ContractKind contractKind() const { return m_contractKind; } + private: std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<ASTNode>> m_subNodes; - bool m_isLibrary; + ContractKind m_contractKind; // parsed Natspec documentation of the contract. Json::Value m_userDocumentation; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e26e2908..b5130c8a 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -82,9 +82,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) case Token::Import: nodes.push_back(parseImportDirective()); break; + case Token::Interface: case Token::Contract: case Token::Library: - nodes.push_back(parseContractDefinition(token == Token::Library)); + nodes.push_back(parseContractDefinition(token)); break; default: fatalParserError(string("Expected import directive or contract definition.")); @@ -193,13 +194,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective() return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases)); } -ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) +ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token) +{ + switch(_token) + { + case Token::Interface: + return ContractDefinition::ContractKind::Interface; + case Token::Contract: + return ContractDefinition::ContractKind::Contract; + case Token::Library: + return ContractDefinition::ContractKind::Library; + default: + fatalParserError("Unsupported contract type."); + } + // FIXME: fatalParserError is not considered as throwing here + return ContractDefinition::ContractKind::Contract; +} + +ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind) { ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docString; if (m_scanner->currentCommentLiteral() != "") docString = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - expectToken(_isLibrary ? Token::Library : Token::Contract); + expectToken(_expectedKind); ASTPointer<ASTString> name = expectIdentifierToken(); vector<ASTPointer<InheritanceSpecifier>> baseContracts; if (m_scanner->currentToken() == Token::Is) @@ -252,7 +270,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) docString, baseContracts, subNodes, - _isLibrary + tokenToContractKind(_expectedKind) ); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 79d1d1d4..282617ab 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -69,7 +69,8 @@ private: ///@name Parsing functions for the AST nodes ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<ImportDirective> parseImportDirective(); - ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); + ContractDefinition::ContractKind tokenToContractKind(Token::Value _token); + ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index c6d050bb..9a557ebd 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -157,6 +157,7 @@ namespace solidity K(Hex, "hex", 0) \ K(If, "if", 0) \ K(Indexed, "indexed", 0) \ + K(Interface, "interface", 0) \ K(Internal, "internal", 0) \ K(Import, "import", 0) \ K(Is, "is", 0) \ @@ -225,7 +226,6 @@ namespace solidity K(Final, "final", 0) \ K(In, "in", 0) \ K(Inline, "inline", 0) \ - K(Interface, "interface", 0) \ K(Let, "let", 0) \ K(Match, "match", 0) \ K(NullLiteral, "null", 0) \ diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7ef34383..8dd5042a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9266,6 +9266,41 @@ BOOST_AUTO_TEST_CASE(scientific_notation) BOOST_CHECK(callContractFunction("k()") == encodeArgs(u256(-25))); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* sourceCode = R"( + interface I { + event A(); + function f() returns (bool); + function() payable; + } + + contract A is I { + function f() returns (bool) { + return g(); + } + + function g() returns (bool) { + return true; + } + + function() payable { + } + } + + contract C { + function f(address _interfaceAddress) returns (bool) { + I i = I(_interfaceAddress); + return i.f(); + } + } + )"; + compileAndRun(sourceCode, 0, "A"); + u160 const recipient = m_contractAddress; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(address)", recipient) == encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index fa310434..c002fd3e 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -5327,6 +5327,151 @@ BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* text = R"( + interface I { + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_constructor) +{ + char const* text = R"( + interface I { + function I(); + } + )"; + CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_functions) +{ + char const* text = R"( + interface I { + function(); + function f(); + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_function_bodies) +{ + char const* text = R"( + interface I { + function f() { + } + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot have an implementation"); +} + +BOOST_AUTO_TEST_CASE(interface_function_internal) +{ + char const* text = R"( + interface I { + function f() internal; + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); +} + +BOOST_AUTO_TEST_CASE(interface_function_private) +{ + char const* text = R"( + interface I { + function f() private; + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); +} + +BOOST_AUTO_TEST_CASE(interface_events) +{ + char const* text = R"( + interface I { + event E(); + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_inheritance) +{ + char const* text = R"( + interface A { + } + interface I is A { + } + )"; + CHECK_ERROR(text, TypeError, "Interfaces cannot inherit"); +} + + +BOOST_AUTO_TEST_CASE(interface_structs) +{ + char const* text = R"( + interface I { + struct A { + } + } + )"; + CHECK_ERROR(text, TypeError, "Structs cannot be defined in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_variables) +{ + char const* text = R"( + interface I { + uint a; + } + )"; + CHECK_ERROR(text, TypeError, "Variables cannot be declared in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_enums) +{ + char const* text = R"( + interface I { + enum A { B, C } + } + )"; + CHECK_ERROR(text, TypeError, "Enumerable cannot be declared in interfaces"); +} + +BOOST_AUTO_TEST_CASE(using_interface) +{ + char const* text = R"( + interface I { + function f(); + } + contract C is I { + function f() { + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(using_interface_complex) +{ + char const* text = R"( + interface I { + event A(); + function f(); + function g(); + function(); + } + contract C is I { + function f() { + } + } + )"; + success(text); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index ffb4b6f2..6e33aba5 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1493,6 +1493,15 @@ BOOST_AUTO_TEST_CASE(scientific_notation) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* text = R"( + interface Interface { + function f(); + } + )"; + BOOST_CHECK(successParse(text)); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 5362239d..0b5ab516 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TypePointer multiArray = make_shared<ArrayType>(DataLocation::Storage, stringArray); BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); - ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, false); + ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, ContractDefinition::ContractKind::Contract); BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$"); BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); |