Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow NatSpec comments for state variables #8532

Merged
merged 3 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Compiler Features:
* Build system: Update the soljson.js build to emscripten 1.39.15 and boost 1.73.0 and include Z3 for integrated SMTChecker support without the callback mechanism.
* SMTChecker: Support array ``length``.
* SMTChecker: Support array ``push`` and ``pop``.

* Add support for natspec comments on state variables.

Bugfixes:
* Optimizer: Fixed a bug in BlockDeDuplicator.
Expand Down
6 changes: 3 additions & 3 deletions docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,9 @@ which only need to be created if there is a dispute.

contract C {
function createDSalted(bytes32 salt, uint arg) public {
/// This complicated expression just tells you how the address
/// can be pre-computed. It is just there for illustration.
/// You actually only need ``new D{salt: salt}(arg)``.
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff),
address(this),
Expand Down
9 changes: 6 additions & 3 deletions docs/natspec-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Documentation Example
Documentation is inserted above each ``class``, ``interface`` and
``function`` using the doxygen notation format.

Note: a ``public`` state variable is equivalent to a ``function``
for the purposes of NatSpec.

- For Solidity you may choose ``///`` for single or multi-line
comments, or ``/**`` and ending with ``*/``.

Expand Down Expand Up @@ -82,10 +85,10 @@ Tag
=========== =============================================================================== =============================
``@title`` A title that should describe the contract/interface contract, interface
``@author`` The name of the author contract, interface, function
``@notice`` Explain to an end user what this does contract, interface, function
``@dev`` Explain to a developer any extra details contract, interface, function
``@notice`` Explain to an end user what this does contract, interface, function, public state variable
``@dev`` Explain to a developer any extra details contract, interface, function, state variable
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
``@return`` Documents the return variables of a contract's function function
``@return`` Documents the return variables of a contract's function function, public state variable
=========== =============================================================================== =============================

If your function returns multiple values, like ``(int quotient, int remainder)``
Expand Down
6 changes: 3 additions & 3 deletions docs/security-considerations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ complete contract):

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not public, so @dev will not be useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will not be visible in the user-doc, but for the dev-doc a new json node stateVariables will be created that will take the @dev comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@notice is only for the external interface and @dev is valid for everything - so do you propose to default to @dev for non-public items?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly, for non-public state variables only @dev will be valid.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@axic @chriseth Is the basic mechanism implemented here ok? For me it looks like that there is still confusion around @dev on non-public state variables. We could also default /// <..> to @dev on non-public state variables. From my point of view it would be better to be more explicit here, so that there is a need to explicitly use @dev on non-public state variables.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@axic waiting for your answer to my question.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to keep the default at @notice for everything.

/// Withdraw your share.
function withdraw() public {
Expand All @@ -87,7 +87,7 @@ as it uses ``call`` which forwards all remaining gas by default:

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
Expand All @@ -106,7 +106,7 @@ outlined further below:
pragma solidity >=0.4.11 <0.7.0;

contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
Expand Down
2 changes: 1 addition & 1 deletion docs/types/reference-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
pragma solidity >=0.6.0 <0.7.0;

contract Proxy {
/// Address of the client contract managed by proxy i.e., this contract
/// @dev Address of the client contract managed by proxy i.e., this contract
address client;

constructor(address _client) public {
Expand Down
40 changes: 39 additions & 1 deletion libsolidity/analysis/DocStringAnalyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
return true;
}

bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
{
if (_variable.isStateVariable())
{
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author"};
if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
else
{
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "non-public state variables");
if (_variable.annotation().docTags.count("notice") > 0)
m_errorReporter.warning(
9098_error, _variable.documentation()->location(),
"Documentation tag on non-public state variables will be disallowed in 0.7.0. You will need to use the @dev tag explicitly."
);
}
if (_variable.annotation().docTags.count("title") > 0 || _variable.annotation().docTags.count("author") > 0)
m_errorReporter.warning(
4822_error, _variable.documentation()->location(),
"Documentation tag @title and @author is only allowed on contract definitions. It will be disallowed in 0.7.0."
);
}
return false;
}

bool DocStringAnalyser::visit(ModifierDefinition const& _modifier)
{
handleCallable(_modifier, _modifier, _modifier.annotation());
Expand Down Expand Up @@ -144,7 +169,20 @@ void DocStringAnalyser::parseDocStrings(
if (docTag.first == "return")
{
returnTagsVisited++;
if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
if (auto* varDecl = dynamic_cast<VariableDeclaration const*>(&_node))
{
if (!varDecl->isPublic())
appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed on public state-variables."
);
if (returnTagsVisited > 1)
appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed once on state-variables."
);
}
else if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
{
string content = docTag.second.content;
string firstWord = content.substr(0, content.find_first_of(" \t"));
Expand Down
7 changes: 7 additions & 0 deletions libsolidity/analysis/DocStringAnalyser.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class DocStringAnalyser: private ASTConstVisitor
private:
bool visit(ContractDefinition const& _contract) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(VariableDeclaration const& _variable) override;
bool visit(ModifierDefinition const& _modifier) override;
bool visit(EventDefinition const& _event) override;

Expand All @@ -67,6 +68,12 @@ class DocStringAnalyser: private ASTConstVisitor
StructurallyDocumentedAnnotation& _annotation
);

void handleDeclaration(
Declaration const& _declaration,
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
);

void parseDocStrings(
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation,
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
* Declaration of a variable. This can be used in various places, e.g. in function parameter
* lists, struct definitions and even function bodies.
*/
class VariableDeclaration: public Declaration
class VariableDeclaration: public Declaration, public StructurallyDocumented
{
public:
enum Location { Unspecified, Storage, Memory, CallData };
Expand All @@ -882,13 +882,15 @@ class VariableDeclaration: public Declaration
ASTPointer<ASTString> const& _name,
ASTPointer<Expression> _value,
Visibility _visibility,
ASTPointer<StructuredDocumentation> const _documentation = nullptr,
bool _isStateVar = false,
bool _isIndexed = false,
Mutability _mutability = Mutability::Mutable,
ASTPointer<OverrideSpecifier> _overrides = nullptr,
Location _referenceLocation = Location::Unspecified
):
Declaration(_id, _location, _name, _visibility),
StructurallyDocumented(std::move(_documentation)),
m_typeName(std::move(_type)),
m_value(std::move(_value)),
m_isStateVariable(_isStateVar),
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/ASTAnnotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, Structurally
{
};

struct VariableDeclarationAnnotation: DeclarationAnnotation
struct VariableDeclarationAnnotation: DeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// Type of variable (type of identifier referencing this variable).
TypePointer type = nullptr;
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
};
if (_node.isStateVariable() && _node.isPublic())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (_node.isStateVariable() && _node.documentation())
attributes.emplace_back("documentation", toJson(*_node.documentation()));
if (m_inEvent)
attributes.emplace_back("indexed", _node.isIndexed());
if (!_node.annotation().baseFunctions.empty())
Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
make_shared<ASTString>(member(_node, "name").asString()),
nullOrCast<Expression>(member(_node, "value")),
visibility(_node),
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
mutability,
Expand Down
29 changes: 28 additions & 1 deletion libsolidity/interface/Natspec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
auto constructorDefinition(_contractDef.constructor());
if (constructorDefinition)
{
string value = extractDoc(constructorDefinition->annotation().docTags, "notice");
string const value = extractDoc(constructorDefinition->annotation().docTags, "notice");
if (!value.empty())
// add the constructor, only if we have any documentation to add
methods["constructor"] = Json::Value(value);
Expand All @@ -52,6 +52,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)

for (auto const& it: _contractDef.interfaceFunctions())
if (it.second->hasDeclaration())
{
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
{
string value = extractDoc(f->annotation().docTags, "notice");
Expand All @@ -63,6 +64,19 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
methods[it.second->externalSignature()] = user;
}
}
else if (auto var = dynamic_cast<VariableDeclaration const*>(&it.second->declaration()))
{
solAssert(var->isStateVariable() && var->isPublic(), "");
string value = extractDoc(var->annotation().docTags, "notice");
aarlt marked this conversation as resolved.
Show resolved Hide resolved
if (!value.empty())
{
Json::Value user;
// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(value);
methods[it.second->externalSignature()] = user;
}
}
}
doc["methods"] = methods;

return doc;
Expand Down Expand Up @@ -110,7 +124,20 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
}
}

Json::Value stateVariables(Json::objectValue);
for (VariableDeclaration const* varDecl: _contractDef.stateVariables())
{
if (auto devDoc = devDocumentation(varDecl->annotation().docTags); !devDoc.empty())
stateVariables[varDecl->name()] = devDoc;

solAssert(varDecl->annotation().docTags.count("return") <= 1, "");
if (varDecl->annotation().docTags.count("return") == 1)
stateVariables[varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return");
}

doc["methods"] = methods;
if (!stateVariables.empty())
doc["stateVariables"] = stateVariables;

return doc;
}
Expand Down
5 changes: 5 additions & 0 deletions libsolidity/parsing/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTNodeFactory nodeFactory = _lookAheadArrayType ?
ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
ASTPointer<TypeName> type;
ASTPointer<StructuredDocumentation> const documentation = parseStructuredDocumentation();
if (_lookAheadArrayType)
type = _lookAheadArrayType;
else
Expand All @@ -695,6 +696,9 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
nodeFactory.setEndPositionFromNode(type);
}

if (!_options.isStateVariable && documentation != nullptr)
parserWarning(2837_error, "Only state variables can have a docstring. This will be disallowed in 0.7.0.");

if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace)
fatalParserError(
2915_error,
Expand Down Expand Up @@ -809,6 +813,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
identifier,
value,
visibility,
documentation,
_options.isStateVariable,
isIndexed,
mutability,
Expand Down
Loading