Skip to content

Commit

Permalink
Merge pull request #8532 from aarlt/structured-docs-variables-aarlt
Browse files Browse the repository at this point in the history
Allow NatSpec comments for state variables
  • Loading branch information
chriseth authored May 19, 2020
2 parents d151222 + af8bb5f commit 22d5caa
Show file tree
Hide file tree
Showing 23 changed files with 368 additions and 83 deletions.
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;
/// 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");
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

0 comments on commit 22d5caa

Please sign in to comment.