mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9892 from ethereum/constantsAtFileLevel
Constants at file-level.
This commit is contained in:
@@ -276,11 +276,17 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||
if (_variable.annotation().type)
|
||||
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(
|
||||
1788_error,
|
||||
_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())
|
||||
m_errorReporter.declarationError(
|
||||
@@ -344,6 +350,11 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = DataLocation::Memory;
|
||||
}
|
||||
else if (_variable.isFileLevelVariable())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = DataLocation::Memory;
|
||||
}
|
||||
else if (_variable.isStateVariable())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
|
||||
@@ -87,7 +87,7 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
|
||||
|
||||
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (!_variable.isStateVariable())
|
||||
if (!_variable.isStateVariable() && !_variable.isFileLevelVariable())
|
||||
return false;
|
||||
|
||||
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
|
||||
|
||||
@@ -61,13 +61,13 @@ bool DocStringTagParser::visit(VariableDeclaration const& _variable)
|
||||
{
|
||||
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())
|
||||
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
|
||||
parseDocStrings(_variable, _variable.annotation(), {"dev", "notice", "return", "inheritdoc"}, "public state variables");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,8 +130,11 @@ bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source)
|
||||
try
|
||||
{
|
||||
for (shared_ptr<ASTNode> const& node: _source.nodes())
|
||||
{
|
||||
setScope(&_source);
|
||||
if (!resolveNamesAndTypesInternal(*node, true))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (langutil::FatalError const&)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,13 @@ bool PostTypeChecker::check(ASTNode const& _astRoot)
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool PostTypeChecker::finalize()
|
||||
{
|
||||
for (auto& checker: m_checkers)
|
||||
checker->finalize();
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition)
|
||||
{
|
||||
return callVisit(_contractDefinition);
|
||||
@@ -83,6 +90,11 @@ bool PostTypeChecker::visit(Identifier const& _identifier)
|
||||
return callVisit(_identifier);
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
return callVisit(_memberAccess);
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(StructDefinition const& _struct)
|
||||
{
|
||||
return callVisit(_struct);
|
||||
@@ -110,14 +122,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
||||
ConstStateVarCircularReferenceChecker(ErrorReporter& _errorReporter):
|
||||
Checker(_errorReporter) {}
|
||||
|
||||
bool visit(ContractDefinition const&) override
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
solAssert(m_constVariableDependencies.empty(), "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void endVisit(ContractDefinition const&) override
|
||||
void finalize() override
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
for (auto declaration: m_constVariables)
|
||||
@@ -128,9 +133,12 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
||||
"The value of the constant " + declaration->name() +
|
||||
" has a cyclic dependency via " + identifier->name() + "."
|
||||
);
|
||||
}
|
||||
|
||||
m_constVariables.clear();
|
||||
m_constVariableDependencies.clear();
|
||||
bool visit(ContractDefinition const&) override
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(VariableDeclaration const& _variable) override
|
||||
@@ -162,6 +170,15 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(MemberAccess const& _memberAccess) override
|
||||
{
|
||||
if (m_currentConstVariable)
|
||||
if (auto var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
if (var->isConstant())
|
||||
m_constVariableDependencies[m_currentConstVariable].insert(var);
|
||||
return true;
|
||||
}
|
||||
|
||||
VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom)
|
||||
{
|
||||
auto visitor = [&](VariableDeclaration const& _variable, util::CycleDetector<VariableDeclaration>& _cycleDetector, size_t _depth)
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace solidity::frontend
|
||||
|
||||
/**
|
||||
* 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 a modifier is in a function header
|
||||
* - whether an event is used outside of an emit statement
|
||||
@@ -54,6 +54,9 @@ public:
|
||||
{
|
||||
Checker(langutil::ErrorReporter& _errorReporter):
|
||||
m_errorReporter(_errorReporter) {}
|
||||
|
||||
/// Called after all source units have been visited.
|
||||
virtual void finalize() {}
|
||||
protected:
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
};
|
||||
@@ -63,6 +66,9 @@ public:
|
||||
|
||||
bool check(ASTNode const& _astRoot);
|
||||
|
||||
/// Called after all source units have been visited.
|
||||
bool finalize();
|
||||
|
||||
private:
|
||||
bool visit(ContractDefinition const& _contract) override;
|
||||
void endVisit(ContractDefinition const& _contract) override;
|
||||
@@ -77,6 +83,7 @@ private:
|
||||
bool visit(FunctionCall const& _functionCall) override;
|
||||
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(MemberAccess const& _identifier) override;
|
||||
|
||||
bool visit(StructDefinition const& _struct) override;
|
||||
void endVisit(StructDefinition const& _struct) override;
|
||||
|
||||
@@ -647,6 +647,11 @@ bool VariableDeclaration::isStateVariable() const
|
||||
return dynamic_cast<ContractDefinition const*>(scope());
|
||||
}
|
||||
|
||||
bool VariableDeclaration::isFileLevelVariable() const
|
||||
{
|
||||
return dynamic_cast<SourceUnit const*>(scope());
|
||||
}
|
||||
|
||||
set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
|
||||
{
|
||||
using Location = VariableDeclaration::Location;
|
||||
|
||||
@@ -967,6 +967,7 @@ public:
|
||||
/// Can only be called after reference resolution.
|
||||
bool hasReferenceOrMappingType() const;
|
||||
bool isStateVariable() const;
|
||||
bool isFileLevelVariable() const;
|
||||
bool isIndexed() const { return m_isIndexed; }
|
||||
Mutability mutability() const { return m_mutability; }
|
||||
bool isConstant() const { return m_mutability == Mutability::Constant; }
|
||||
|
||||
@@ -1753,18 +1753,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
Type::Category category = _memberAccess.annotation().type->category();
|
||||
solAssert(
|
||||
dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) ||
|
||||
dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) ||
|
||||
category == Type::Category::TypeType ||
|
||||
category == Type::Category::Module ||
|
||||
category == Type::Category::Function,
|
||||
category == Type::Category::Module,
|
||||
""
|
||||
);
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
{
|
||||
auto const* funDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
|
||||
solAssert(funDef && funDef->isFree(), "");
|
||||
solAssert(variable->isConstant(), "");
|
||||
appendVariable(*variable, static_cast<Expression const&>(_memberAccess));
|
||||
}
|
||||
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
{
|
||||
auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
|
||||
solAssert(function && function->isFree(), "");
|
||||
solAssert(funType->kind() == FunctionType::Kind::Internal, "");
|
||||
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
|
||||
utils().pushCombinedFunctionEntryLabel(*funDef);
|
||||
utils().pushCombinedFunctionEntryLabel(*function);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1871,6 +1871,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
solAssert(false, "");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Module:
|
||||
{
|
||||
Type::Category category = _memberAccess.annotation().type->category();
|
||||
solAssert(
|
||||
dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) ||
|
||||
dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) ||
|
||||
category == Type::Category::TypeType ||
|
||||
category == Type::Category::Module,
|
||||
""
|
||||
);
|
||||
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
{
|
||||
solAssert(variable->isConstant(), "");
|
||||
handleVariableReference(*variable, static_cast<Expression const&>(_memberAccess));
|
||||
}
|
||||
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
{
|
||||
auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
|
||||
solAssert(function && function->isFree(), "");
|
||||
solAssert(function->functionType(true), "");
|
||||
solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal, "");
|
||||
solAssert(funType->kind() == FunctionType::Kind::Internal, "");
|
||||
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
|
||||
|
||||
define(_memberAccess) << to_string(function->id()) << "\n";
|
||||
m_context.internalFunctionAccessed(_memberAccess, *function);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "Member access to unknown type.");
|
||||
}
|
||||
@@ -2130,6 +2159,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else if (dynamic_cast<ImportDirective const*>(declaration))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(false, "Identifier type not expected in expression context.");
|
||||
@@ -2162,7 +2195,7 @@ void IRGeneratorForStatements::handleVariableReference(
|
||||
)
|
||||
{
|
||||
setLocation(_referencingExpression);
|
||||
if (_variable.isStateVariable() && _variable.isConstant())
|
||||
if ((_variable.isStateVariable() || _variable.isFileLevelVariable()) && _variable.isConstant())
|
||||
define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
|
||||
else if (_variable.isStateVariable() && _variable.immutable())
|
||||
setLValue(_referencingExpression, IRLValue{
|
||||
|
||||
@@ -387,6 +387,8 @@ bool CompilerStack::analyze()
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !postTypeChecker.check(*source->ast))
|
||||
noErrors = false;
|
||||
if (!postTypeChecker.finalize())
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
// Check that immutable variables are never read in c'tors and assigned
|
||||
|
||||
@@ -111,7 +111,17 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
|
||||
nodes.push_back(parseFunctionDefinition(true));
|
||||
break;
|
||||
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, "");
|
||||
@@ -332,15 +342,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
|
||||
subNodes.push_back(parseStructDefinition());
|
||||
else if (currentTokenValue == Token::Enum)
|
||||
subNodes.push_back(parseEnumDefinition());
|
||||
else if (
|
||||
currentTokenValue == Token::Identifier ||
|
||||
currentTokenValue == Token::Mapping ||
|
||||
TokenTraits::isElementaryTypeName(currentTokenValue) ||
|
||||
(currentTokenValue == Token::Function && m_scanner->peekNextToken() == Token::LParen)
|
||||
)
|
||||
else if (variableDeclarationStart())
|
||||
{
|
||||
VarDeclParserOptions options;
|
||||
options.isStateVariable = true;
|
||||
options.kind = VarDeclKind::State;
|
||||
options.allowInitialValue = true;
|
||||
subNodes.push_back(parseVariableDeclaration(options));
|
||||
expectToken(Token::Semicolon);
|
||||
@@ -687,10 +692,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName();
|
||||
nodeFactory.setEndPositionFromNode(type);
|
||||
|
||||
if (!_options.isStateVariable && documentation != nullptr)
|
||||
parserError(2837_error, "Only state variables can have a docstring.");
|
||||
if (_options.kind == VarDeclKind::Other && documentation != nullptr)
|
||||
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(
|
||||
2915_error,
|
||||
"Expected a state variable declaration. If you intended this as a fallback function "
|
||||
@@ -708,7 +713,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
while (true)
|
||||
{
|
||||
Token token = m_scanner->currentToken();
|
||||
if (_options.isStateVariable && TokenTraits::isVariableVisibilitySpecifier(token))
|
||||
if (_options.kind == VarDeclKind::State && TokenTraits::isVariableVisibilitySpecifier(token))
|
||||
{
|
||||
nodeFactory.markEndPosition();
|
||||
if (visibility != Visibility::Default)
|
||||
@@ -724,7 +729,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
else
|
||||
visibility = parseVisibilitySpecifier();
|
||||
}
|
||||
else if (_options.isStateVariable && token == Token::Override)
|
||||
else if (_options.kind == VarDeclKind::State && token == Token::Override)
|
||||
{
|
||||
if (overrides)
|
||||
parserError(9125_error, "Override already specified.");
|
||||
@@ -1928,6 +1933,16 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
|
||||
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)
|
||||
{
|
||||
// We circumvent the scanner here, because it skips non-docstring comments.
|
||||
|
||||
@@ -52,13 +52,13 @@ public:
|
||||
private:
|
||||
class ASTNodeFactory;
|
||||
|
||||
enum class VarDeclKind { FileLevel, State, Other };
|
||||
struct VarDeclParserOptions
|
||||
{
|
||||
// This is actually not needed, but due to a defect in the C++ standard, we have to.
|
||||
// https://stackoverflow.com/questions/17430377
|
||||
VarDeclParserOptions() {}
|
||||
|
||||
bool isStateVariable = false;
|
||||
VarDeclKind kind = VarDeclKind::Other;
|
||||
bool allowIndexed = false;
|
||||
bool allowEmptyName = false;
|
||||
bool allowInitialValue = false;
|
||||
@@ -155,6 +155,9 @@ private:
|
||||
///@{
|
||||
///@name Helper functions
|
||||
|
||||
/// @return true if we are at the start of a variable declaration.
|
||||
bool variableDeclarationStart();
|
||||
|
||||
/// Used as return value of @see peekStatementType.
|
||||
enum class LookAheadInfo
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user