mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Constants at file-level.
This commit is contained in:
parent
21dee1c8ba
commit
346fe1c6c5
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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, "");
|
||||||
|
@ -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()))
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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; }
|
||||||
|
@ -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{
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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"
|
Loading…
Reference in New Issue
Block a user