Merge pull request #9218 from ethereum/issue-8911

Natspec: Implement inheritance and @inheritdoc
This commit is contained in:
chriseth
2020-07-20 18:44:13 +02:00
committed by GitHub
18 changed files with 1082 additions and 219 deletions
+553 -13
View File
@@ -77,7 +77,7 @@ public:
BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError));
}
private:
protected:
CompilerStack m_compilerStack;
};
@@ -1277,13 +1277,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable)
)";
char const *natspec = R"ABCDEF({
"methods":
{
"x()":
{
"details": "test"
}
}
"methods": { "x()": { "details": "test" } }
})ABCDEF";
char const *natspec1 = R"ABCDEF({
@@ -1318,13 +1312,80 @@ BOOST_AUTO_TEST_CASE(user_default_inherit_variable)
)";
char const *natspec = R"ABCDEF({
"methods":
{
"x()":
{
"notice": "Hello world"
"methods": { "x()": { "notice": "Hello world" } }
})ABCDEF";
checkNatspec(sourceCode, "C", natspec, true);
checkNatspec(sourceCode, "D", natspec, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_variable)
{
char const *sourceCode = R"(
contract B {
function x() virtual external returns (uint) {
return 1;
}
}
contract C {
/// @notice Hello world
/// @dev test
function x() virtual external returns (uint) {
return 1;
}
}
contract D is C, B {
/// @inheritdoc C
uint public override(C, B) x;
}
)";
char const *natspec = R"ABCDEF({
"methods": { "x()": { "details": "test" } }
})ABCDEF";
char const *natspec1 = R"ABCDEF({
"methods" : {},
"stateVariables" :
{
"x" :
{
"details" : "test"
}
}
})ABCDEF";
checkNatspec(sourceCode, "C", natspec, false);
checkNatspec(sourceCode, "D", natspec1, false);
}
BOOST_AUTO_TEST_CASE(user_explicit_inherit_variable)
{
char const *sourceCode = R"(
contract B {
function x() virtual external returns (uint) {
return 1;
}
}
contract C {
/// @notice Hello world
/// @dev test
function x() virtual external returns (uint) {
return 1;
}
}
contract D is C, B {
/// @inheritdoc C
uint public override(C, B) x;
}
)";
char const *natspec = R"ABCDEF({
"methods": { "x()": { "notice": "Hello world" } }
})ABCDEF";
checkNatspec(sourceCode, "C", natspec, true);
@@ -1423,6 +1484,411 @@ BOOST_AUTO_TEST_CASE(user_default_inherit)
checkNatspec(sourceCode, "Token", natspec, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inherit)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 {
function transfer(address to, uint amount) virtual external returns (bool) {
return false;
}
}
contract Token is ERC21, ERC20 {
/// @inheritdoc ERC20
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "test",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, false);
checkNatspec(sourceCode, "Token", natspec, false);
}
BOOST_AUTO_TEST_CASE(user_explicit_inherit)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 {
function transfer(address to, uint amount) virtual external returns (bool) {
return false;
}
}
contract Token is ERC21, ERC20 {
/// @inheritdoc ERC20
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, true);
checkNatspec(sourceCode, "Token", natspec, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inherit2)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 is ERC20 {
function transfer(address to, uint amount) virtual override external returns (bool) {
return false;
}
}
contract Token is ERC20 {
/// @inheritdoc ERC20
function transfer(address to, uint amount) override external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "test",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, false);
checkNatspec(sourceCode, "ERC21", natspec, false);
checkNatspec(sourceCode, "Token", natspec, false);
}
BOOST_AUTO_TEST_CASE(user_explicit_inherit2)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 is ERC20 {
function transfer(address to, uint amount) virtual override external returns (bool) {
return false;
}
}
contract Token is ERC20 {
/// @inheritdoc ERC20
function transfer(address to, uint amount) override external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, true);
checkNatspec(sourceCode, "ERC21", natspec, true);
checkNatspec(sourceCode, "Token", natspec, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_partial2)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 is ERC20 {
/// @inheritdoc ERC20
/// @dev override dev comment
/// @notice override notice
function transfer(address to, uint amount) virtual override external returns (bool) {
return false;
}
}
contract Token is ERC21 {
function transfer(address to, uint amount) override external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "test",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
char const *natspec2 = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "override dev comment",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, false);
checkNatspec(sourceCode, "Token", natspec2, false);
}
BOOST_AUTO_TEST_CASE(user_explicit_inherit_partial2)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 is ERC20 {
/// @inheritdoc ERC20
/// @dev override dev comment
/// @notice override notice
function transfer(address to, uint amount) virtual override external returns (bool) {
return false;
}
}
contract Token is ERC21 {
function transfer(address to, uint amount) override external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
}
}
})ABCDEF";
char const *natspec2 = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "override notice"
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, true);
checkNatspec(sourceCode, "Token", natspec2, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_partial)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 {
function transfer(address to, uint amount) virtual external returns (bool) {
return false;
}
}
contract Token is ERC21, ERC20 {
/// @inheritdoc ERC20
/// @dev override dev comment
/// @notice override notice
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "test",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
char const *natspec2 = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "override dev comment",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, false);
checkNatspec(sourceCode, "Token", natspec2, false);
}
BOOST_AUTO_TEST_CASE(user_explicit_inherit_partial)
{
char const *sourceCode = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
contract ERC21 {
function transfer(address to, uint amount) virtual external returns (bool) {
return false;
}
}
contract Token is ERC21, ERC20 {
/// @inheritdoc ERC20
/// @dev override dev comment
/// @notice override notice
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
}
}
})ABCDEF";
char const *natspec2 = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"notice": "override notice"
}
}
})ABCDEF";
checkNatspec(sourceCode, "ERC20", natspec, true);
checkNatspec(sourceCode, "Token", natspec2, true);
}
BOOST_AUTO_TEST_CASE(dev_inherit_parameter_mismatch)
{
char const *sourceCode = R"(
@@ -1517,6 +1983,80 @@ BOOST_AUTO_TEST_CASE(user_inherit_parameter_mismatch)
checkNatspec(sourceCode, "Token", natspec2, true);
}
BOOST_AUTO_TEST_CASE(dev_explicit_inehrit_complex)
{
char const *sourceCode1 = R"(
interface ERC20 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer
/// @dev test
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
interface ERC21 {
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
/// @author Programmer2
/// @dev test2
/// @param to address to transfer to
/// @param amount amount to transfer
function transfer(address to, uint amount) external returns (bool);
}
)";
char const *sourceCode2 = R"(
import "Interfaces.sol" as myInterfaces;
contract Token is myInterfaces.ERC20, myInterfaces.ERC21 {
/// @inheritdoc myInterfaces.ERC20
function transfer(address too, uint amount)
override(myInterfaces.ERC20, myInterfaces.ERC21) external returns (bool) {
return false;
}
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"transfer(address,uint256)":
{
"author": "Programmer",
"details": "test",
"params":
{
"amount": "amount to transfer",
"to": "address to transfer to"
}
}
}
})ABCDEF";
m_compilerStack.reset();
m_compilerStack.setSources({
{"Interfaces.sol", "pragma solidity >=0.0;\n" + std::string(sourceCode1)},
{"Testfile.sol", "pragma solidity >=0.0;\n" + std::string(sourceCode2)}
});
m_compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion());
BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed");
Json::Value generatedDocumentation = m_compilerStack.natspecDev("Token");
Json::Value expectedDocumentation;
util::jsonParseStrict(natspec, expectedDocumentation);
expectedDocumentation["version"] = Json::Value(Natspec::c_natspecVersion);
expectedDocumentation["kind"] = Json::Value("dev");
BOOST_CHECK_MESSAGE(
expectedDocumentation == generatedDocumentation,
"Expected:\n" << expectedDocumentation.toStyledString() <<
"\n but got:\n" << generatedDocumentation.toStyledString()
);
}
}
BOOST_AUTO_TEST_SUITE_END()
@@ -0,0 +1,7 @@
contract C {
/// @inheritdoc X
function f() internal {
}
}
// ----
// DocstringParsingError 9397: (17-34): Documentation tag @inheritdoc references inexistent contract "X".
@@ -0,0 +1,10 @@
contract D {
}
contract C is D {
/// @inheritdoc D
function f() internal {
}
}
// ----
// DocstringParsingError 4682: (38-55): Documentation tag @inheritdoc references contract "D", but the contract does not contain a function that is overridden by this function.
@@ -0,0 +1,11 @@
contract D {
struct S { uint a; }
}
contract C is D {
/// @inheritdoc D.S
function f() internal {
}
}
// ----
// DocstringParsingError 1430: (63-82): Documentation tag @inheritdoc reference "D.S" is not a contract.
@@ -0,0 +1,8 @@
contract C {
struct S { uint a; }
/// @inheritdoc S
function f() internal {
}
}
// ----
// DocstringParsingError 1430: (42-59): Documentation tag @inheritdoc reference "S" is not a contract.
@@ -0,0 +1,17 @@
contract ERC20 {
/// @notice This event is emitted when a transfer occurs.
/// @param from The source account.
/// @param to The destination account.
/// @param amount The amount.
/// @dev A test case!
event Transfer(address indexed from, address indexed to, uint amount);
}
contract A is ERC20 {
/// @inheritdoc ERC20
event Transfer();
}
// ----
// DocstringParsingError 6546: (305-326): Documentation tag @inheritdoc not valid for events.