Constants at file-level.

This commit is contained in:
chriseth 2020-09-25 12:15:31 +02:00
parent 21dee1c8ba
commit 346fe1c6c5
11 changed files with 104 additions and 25 deletions

View File

@ -7,7 +7,7 @@ options { tokenVocab=SolidityLexer; }
/** /**
* On top level, Solidity allows pragmas, import directives, and * On top level, Solidity allows pragmas, import directives, and
* definitions of contracts, interfaces, libraries, structs and enums. * definitions of contracts, interfaces, libraries, structs, enums and constants.
*/ */
sourceUnit: ( sourceUnit: (
pragmaDirective pragmaDirective
@ -16,6 +16,7 @@ sourceUnit: (
| interfaceDefinition | interfaceDefinition
| libraryDefinition | libraryDefinition
| functionDefinition | functionDefinition
| constantVariableDeclaration
| structDefinition | structDefinition
| enumDefinition | enumDefinition
)* EOF; )* EOF;
@ -240,6 +241,17 @@ locals [boolean constantnessSet = false, boolean visibilitySet = false, boolean
(Assign initialValue=expression)? (Assign initialValue=expression)?
Semicolon; Semicolon;
/**
* The declaration of a constant variable.
*/
constantVariableDeclaration
:
type=typeName
Constant
name=identifier
Assign initialValue=expression
Semicolon;
/** /**
* Parameter of an event. * Parameter of an event.
*/ */

View File

@ -276,11 +276,17 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
if (_variable.annotation().type) if (_variable.annotation().type)
return; return;
if (_variable.isConstant() && !_variable.isStateVariable()) if (_variable.isFileLevelVariable() && !_variable.isConstant())
m_errorReporter.declarationError(
8342_error,
_variable.location(),
"Only constant variables are allowed at file level."
);
if (_variable.isConstant() && (!_variable.isStateVariable() && !_variable.isFileLevelVariable()))
m_errorReporter.declarationError( m_errorReporter.declarationError(
1788_error, 1788_error,
_variable.location(), _variable.location(),
"The \"constant\" keyword can only be used for state variables." "The \"constant\" keyword can only be used for state variables or variables at file level."
); );
if (_variable.immutable() && !_variable.isStateVariable()) if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError( m_errorReporter.declarationError(
@ -344,6 +350,11 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
solAssert(varLoc == Location::Unspecified, ""); solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory; typeLoc = DataLocation::Memory;
} }
else if (_variable.isFileLevelVariable())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
}
else if (_variable.isStateVariable()) else if (_variable.isStateVariable())
{ {
solAssert(varLoc == Location::Unspecified, ""); solAssert(varLoc == Location::Unspecified, "");

View File

@ -87,7 +87,7 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
bool DocStringAnalyser::visit(VariableDeclaration const& _variable) bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
{ {
if (!_variable.isStateVariable()) if (!_variable.isStateVariable() && !_variable.isFileLevelVariable())
return false; return false;
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation())) if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))

View File

@ -61,13 +61,13 @@ bool DocStringTagParser::visit(VariableDeclaration const& _variable)
{ {
if (_variable.isStateVariable()) if (_variable.isStateVariable())
{ {
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "inheritdoc"};
static set<string> const validNonPublicTags = set<string>{"dev", "inheritdoc"};
if (_variable.isPublic()) if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables"); parseDocStrings(_variable, _variable.annotation(), {"dev", "notice", "return", "inheritdoc"}, "public state variables");
else else
parseDocStrings(_variable, _variable.annotation(), validNonPublicTags, "non-public state variables"); parseDocStrings(_variable, _variable.annotation(), {"dev", "inheritdoc"}, "non-public state variables");
} }
else if (_variable.isFileLevelVariable())
parseDocStrings(_variable, _variable.annotation(), {"dev"}, "file-level variables");
return false; return false;
} }

View File

@ -35,7 +35,7 @@ namespace solidity::frontend
/** /**
* This module performs analyses on the AST that are done after type checking and assignments of types: * This module performs analyses on the AST that are done after type checking and assignments of types:
* - whether there are circular references in constant state variables * - whether there are circular references in constant variables
* - whether override specifiers are actually contracts * - whether override specifiers are actually contracts
* - whether a modifier is in a function header * - whether a modifier is in a function header
* - whether an event is used outside of an emit statement * - whether an event is used outside of an emit statement

View File

@ -647,6 +647,11 @@ bool VariableDeclaration::isStateVariable() const
return dynamic_cast<ContractDefinition const*>(scope()); return dynamic_cast<ContractDefinition const*>(scope());
} }
bool VariableDeclaration::isFileLevelVariable() const
{
return dynamic_cast<SourceUnit const*>(scope());
}
set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
{ {
using Location = VariableDeclaration::Location; using Location = VariableDeclaration::Location;

View File

@ -967,6 +967,7 @@ public:
/// Can only be called after reference resolution. /// Can only be called after reference resolution.
bool hasReferenceOrMappingType() const; bool hasReferenceOrMappingType() const;
bool isStateVariable() const; bool isStateVariable() const;
bool isFileLevelVariable() const;
bool isIndexed() const { return m_isIndexed; } bool isIndexed() const { return m_isIndexed; }
Mutability mutability() const { return m_mutability; } Mutability mutability() const { return m_mutability; }
bool isConstant() const { return m_mutability == Mutability::Constant; } bool isConstant() const { return m_mutability == Mutability::Constant; }

View File

@ -2162,7 +2162,7 @@ void IRGeneratorForStatements::handleVariableReference(
) )
{ {
setLocation(_referencingExpression); setLocation(_referencingExpression);
if (_variable.isStateVariable() && _variable.isConstant()) if ((_variable.isStateVariable() || _variable.isFileLevelVariable()) && _variable.isConstant())
define(_referencingExpression) << constantValueFunction(_variable) << "()\n"; define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
else if (_variable.isStateVariable() && _variable.immutable()) else if (_variable.isStateVariable() && _variable.immutable())
setLValue(_referencingExpression, IRLValue{ setLValue(_referencingExpression, IRLValue{

View File

@ -111,7 +111,17 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
nodes.push_back(parseFunctionDefinition(true)); nodes.push_back(parseFunctionDefinition(true));
break; break;
default: default:
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/function definition."); // Constant variable.
if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS)
{
VarDeclParserOptions options;
options.kind = VarDeclKind::FileLevel;
options.allowInitialValue = true;
nodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon);
}
else
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition.");
} }
} }
solAssert(m_recursionDepth == 0, ""); solAssert(m_recursionDepth == 0, "");
@ -332,15 +342,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
subNodes.push_back(parseStructDefinition()); subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum) else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition()); subNodes.push_back(parseEnumDefinition());
else if ( else if (variableDeclarationStart())
currentTokenValue == Token::Identifier ||
currentTokenValue == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentTokenValue) ||
(currentTokenValue == Token::Function && m_scanner->peekNextToken() == Token::LParen)
)
{ {
VarDeclParserOptions options; VarDeclParserOptions options;
options.isStateVariable = true; options.kind = VarDeclKind::State;
options.allowInitialValue = true; options.allowInitialValue = true;
subNodes.push_back(parseVariableDeclaration(options)); subNodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon); expectToken(Token::Semicolon);
@ -687,10 +692,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName(); ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName();
nodeFactory.setEndPositionFromNode(type); nodeFactory.setEndPositionFromNode(type);
if (!_options.isStateVariable && documentation != nullptr) if (_options.kind == VarDeclKind::Other && documentation != nullptr)
parserError(2837_error, "Only state variables can have a docstring."); parserError(2837_error, "Only state variables or file-level variables can have a docstring.");
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace) if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.kind == VarDeclKind::State && m_scanner->currentToken() == Token::LBrace)
fatalParserError( fatalParserError(
2915_error, 2915_error,
"Expected a state variable declaration. If you intended this as a fallback function " "Expected a state variable declaration. If you intended this as a fallback function "
@ -708,7 +713,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
while (true) while (true)
{ {
Token token = m_scanner->currentToken(); Token token = m_scanner->currentToken();
if (_options.isStateVariable && TokenTraits::isVariableVisibilitySpecifier(token)) if (_options.kind == VarDeclKind::State && TokenTraits::isVariableVisibilitySpecifier(token))
{ {
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
if (visibility != Visibility::Default) if (visibility != Visibility::Default)
@ -724,7 +729,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
else else
visibility = parseVisibilitySpecifier(); visibility = parseVisibilitySpecifier();
} }
else if (_options.isStateVariable && token == Token::Override) else if (_options.kind == VarDeclKind::State && token == Token::Override)
{ {
if (overrides) if (overrides)
parserError(9125_error, "Override already specified."); parserError(9125_error, "Override already specified.");
@ -1928,6 +1933,16 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
return ret; return ret;
} }
bool Parser::variableDeclarationStart()
{
Token currentToken = m_scanner->currentToken();
return
currentToken == Token::Identifier ||
currentToken == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentToken) ||
(currentToken == Token::Function && m_scanner->peekNextToken() == Token::LParen);
}
optional<string> Parser::findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes) optional<string> Parser::findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes)
{ {
// We circumvent the scanner here, because it skips non-docstring comments. // We circumvent the scanner here, because it skips non-docstring comments.

View File

@ -52,13 +52,13 @@ public:
private: private:
class ASTNodeFactory; class ASTNodeFactory;
enum class VarDeclKind { FileLevel, State, Other };
struct VarDeclParserOptions struct VarDeclParserOptions
{ {
// This is actually not needed, but due to a defect in the C++ standard, we have to. // This is actually not needed, but due to a defect in the C++ standard, we have to.
// https://stackoverflow.com/questions/17430377 // https://stackoverflow.com/questions/17430377
VarDeclParserOptions() {} VarDeclParserOptions() {}
VarDeclKind kind = VarDeclKind::Other;
bool isStateVariable = false;
bool allowIndexed = false; bool allowIndexed = false;
bool allowEmptyName = false; bool allowEmptyName = false;
bool allowInitialValue = false; bool allowInitialValue = false;
@ -155,6 +155,9 @@ private:
///@{ ///@{
///@name Helper functions ///@name Helper functions
/// @return true if we are at the start of a variable declaration.
bool variableDeclarationStart();
/// Used as return value of @see peekStatementType. /// Used as return value of @see peekStatementType.
enum class LookAheadInfo enum class LookAheadInfo
{ {

View File

@ -0,0 +1,32 @@
bytes constant a = "\x03\x01\x02";
bytes constant b = hex"030102";
string constant c = "hello";
uint256 constant x = 56;
enum ActionChoices {GoLeft, GoRight, GoStraight, Sit}
ActionChoices constant choices = ActionChoices.GoLeft;
bytes32 constant st = "abc\x00\xff__";
contract C {
function f() public returns (bytes memory) {
return a;
}
function g() public returns (bytes memory) {
return b;
}
function h() public returns (bytes memory) {
return bytes(c);
}
function i() public returns (uint, ActionChoices, bytes32) {
return (x, choices, st);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x20, 3, "\x03\x01\x02"
// g() -> 0x20, 3, "\x03\x01\x02"
// h() -> 0x20, 5, "hello"