Merge pull request #4306 from ethereum/remove_050_workaround_scoping

[BREAKING] C99 scoping rules by default (remove 050 workaround)
This commit is contained in:
chriseth 2018-06-21 13:57:59 +02:00 committed by GitHub
commit 0d1047181d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 85 additions and 219 deletions

View File

@ -16,6 +16,7 @@ Breaking Changes:
``implements``, ``macro``, ``mutable``, ``override``, ``partial``, ``promise``, ``reference``, ``sealed``,
``sizeof``, ``supports``, ``typedef`` and ``unchecked``.
* General: Remove assembly instruction aliases ``sha3`` and ``suicide``
* General: C99-style scoping rules are enforced now. This was already the case in the experimental 0.5.0 mode.
* Optimizer: Remove the no-op ``PUSH1 0 NOT AND`` sequence.
* Parser: Disallow trailing dots that are not followed by a number.
* Type Checker: Disallow arithmetic operations for boolean variables.
@ -26,6 +27,7 @@ Breaking Changes:
Language Features:
* General: Allow appending ``calldata`` keyword to types, to explicitly specify data location for arguments of external functions.
* General: Support ``pop()`` for storage arrays.
* General: Scoping rules now follow the C99-style.
Compiler Features:
* Type Checker: Show named argument in case of error.

View File

@ -1323,6 +1323,7 @@ custom types without the overhead of external function calls:
if (carry > 0) {
// too bad, we have to add a limb
uint[] memory newLimbs = new uint[](r.limbs.length + 1);
uint i;
for (i = 0; i < r.limbs.length; ++i)
newLimbs[i] = r.limbs[i];
newLimbs[i] = carry;

View File

@ -325,74 +325,7 @@ is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For
element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string.
A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared
(this will change soon, see below).
This happens because Solidity inherits its scoping rules from JavaScript.
This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block.
As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``:
::
// This will not compile
pragma solidity ^0.4.16;
contract ScopingErrors {
function scoping() public {
uint i = 0;
while (i++ < 1) {
uint same1 = 0;
}
while (i++ < 2) {
uint same1 = 0;// Illegal, second declaration of same1
}
}
function minimalScoping() public {
{
uint same2 = 0;
}
{
uint same2 = 0;// Illegal, second declaration of same2
}
}
function forLoopScoping() public {
for (uint same3 = 0; same3 < 1; same3++) {
}
for (uint same3 = 0; same3 < 1; same3++) {// Illegal, second declaration of same3
}
}
}
In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value.
As a result, the following code is legal, despite being poorly written:
::
pragma solidity ^0.4.0;
contract C {
function foo() public pure returns (uint) {
// baz is implicitly initialized as 0
uint bar = 5;
if (true) {
bar += baz;
} else {
uint baz = 10;// never executes
}
return bar;// returns 5
}
}
Scoping starting from Version 0.5.0
-----------------------------------
Starting from version 0.5.0, Solidity will change to the more widespread scoping rules of C99
Scoping in Solidity follows the widespread scoping rules of C99
(and many other languages): Variables are visible from the point right after their declaration
until the end of a ``{ }``-block. As an exception to this rule, variables declared in the
initialization part of a for-loop are only visible until the end of the for-loop.
@ -401,17 +334,12 @@ Variables and other items declared outside of a code block, for example function
user-defined types, etc., do not change their scoping behaviour. This means you can
use state variables before they are declared and call functions recursively.
These rules are already introduced now as an experimental feature.
As a consequence, the following examples will compile without warnings, since
the two variables have the same name but disjoint scopes. In non-0.5.0-mode,
they have the same scope (the function ``minimalScoping``) and thus it does
not compile there.
the two variables have the same name but disjoint scopes.
::
pragma solidity ^0.4.0;
pragma experimental "v0.5.0";
pragma solidity >0.4.24;
contract C {
function minimalScoping() pure public {
{
@ -430,8 +358,7 @@ In any case, you will get a warning about the outer variable being shadowed.
::
pragma solidity ^0.4.0;
pragma experimental "v0.5.0";
pragma solidity >0.4.24;
contract C {
function f() pure public returns (uint) {
uint x = 1;
@ -443,6 +370,24 @@ In any case, you will get a warning about the outer variable being shadowed.
}
}
.. warning::
Before version 0.5.0 Solidity followed the same scoping rules as JavaScript, that is, a variable declared anywhere within a function would be in scope
for the entire function, regardless where it was declared. Note that this is a breaking change. The following example shows a code snippet that used
to compile but leads to an error starting from version 0.5.0.
::
// This will not compile
pragma solidity >0.4.24;
contract C {
function f() pure public returns (uint) {
x = 2;
uint x;
return x;
}
}
.. index:: ! exception, ! throw, ! assert, ! require, ! revert
Error handling: Assert, Require, Revert and Exceptions

View File

@ -58,7 +58,7 @@ public:
/// @returns whether declaration is valid, and if not also returns previous declaration.
Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const;
/// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
/// Activates a previously inactive (invisible) variable. To be used in C99 scoping for
/// VariableDeclarationStatements.
void activateVariable(ASTString const& _name);

View File

@ -54,11 +54,10 @@ NameAndTypeResolver::NameAndTypeResolver(
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
{
bool useC99Scoping = _sourceUnit.annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, useC99Scoping, m_errorReporter, _currentScope);
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
}
catch (FatalError const&)
{
@ -449,11 +448,9 @@ string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope
):
m_useC99Scoping(_useC99Scoping),
m_scopes(_scopes),
m_currentScope(_currentScope),
m_errorReporter(_errorReporter)
@ -629,29 +626,25 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&)
bool DeclarationRegistrationHelper::visit(Block& _block)
{
_block.setScope(m_currentScope);
if (m_useC99Scoping)
enterNewSubScope(_block);
enterNewSubScope(_block);
return true;
}
void DeclarationRegistrationHelper::endVisit(Block&)
{
if (m_useC99Scoping)
closeCurrentScope();
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ForStatement& _for)
{
_for.setScope(m_currentScope);
if (m_useC99Scoping)
enterNewSubScope(_for);
enterNewSubScope(_for);
return true;
}
void DeclarationRegistrationHelper::endVisit(ForStatement&)
{
if (m_useC99Scoping)
closeCurrentScope();
closeCurrentScope();
}
void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement)
@ -716,9 +709,8 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
if (fun->isConstructor())
warnAboutShadowing = false;
// Register declaration as inactive if we are in block scope and C99 mode.
// Register declaration as inactive if we are in block scope.
bool inactive =
m_useC99Scoping &&
(dynamic_cast<Block const*>(m_currentScope) || dynamic_cast<ForStatement const*>(m_currentScope));
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);

View File

@ -69,7 +69,7 @@ public:
/// that create their own scope.
/// @returns false in case of error.
bool updateDeclaration(Declaration const& _declaration);
/// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
/// Activates a previously inactive (invisible) variable. To be used in C99 scoping for
/// VariableDeclarationStatements.
void activateVariable(std::string const& _name);
@ -142,7 +142,6 @@ public:
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope = nullptr
);
@ -190,7 +189,6 @@ private:
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
bool m_useC99Scoping = false;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;

View File

@ -48,9 +48,7 @@ bool ReferencesResolver::visit(Block const& _block)
if (!m_resolveInsideCode)
return false;
m_experimental050Mode = _block.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(&_block);
m_resolver.setScope(&_block);
return true;
}
@ -59,9 +57,7 @@ void ReferencesResolver::endVisit(Block const& _block)
if (!m_resolveInsideCode)
return;
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(_block.scope());
m_resolver.setScope(_block.scope());
}
bool ReferencesResolver::visit(ForStatement const& _for)
@ -69,9 +65,7 @@ bool ReferencesResolver::visit(ForStatement const& _for)
if (!m_resolveInsideCode)
return false;
m_experimental050Mode = _for.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(&_for);
m_resolver.setScope(&_for);
return true;
}
@ -79,18 +73,16 @@ void ReferencesResolver::endVisit(ForStatement const& _for)
{
if (!m_resolveInsideCode)
return;
if (m_experimental050Mode)
m_resolver.setScope(_for.scope());
m_resolver.setScope(_for.scope());
}
void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (!m_resolveInsideCode)
return;
if (m_experimental050Mode)
for (auto const& var: _varDeclStatement.declarations())
if (var)
m_resolver.activateVariable(var->name());
for (auto const& var: _varDeclStatement.declarations())
if (var)
m_resolver.activateVariable(var->name());
}
bool ReferencesResolver::visit(Identifier const& _identifier)
@ -99,9 +91,14 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
if (declarations.empty())
{
string suggestions = m_resolver.similarNameSuggestions(_identifier.name());
string errorMessage =
"Undeclared identifier." +
(suggestions.empty()? "": " Did you mean " + std::move(suggestions) + "?");
string errorMessage = "Undeclared identifier.";
if (!suggestions.empty())
{
if ("\"" + _identifier.name() + "\"" == suggestions)
errorMessage += " " + std::move(suggestions) + " is not (or not yet) visible at this point.";
else
errorMessage += " Did you mean " + std::move(suggestions) + "?";
}
declarationError(_identifier.location(), errorMessage);
}
else if (declarations.size() == 1)

View File

@ -536,8 +536,9 @@ contract provider is module, safeMath, announcementTypes {
address provAddr;
uint256 provHeight;
bool interest = false;
uint256 a;
var rate = clients[msg.sender].lastRate;
for ( uint256 a = (clients[msg.sender].paidUpTo + 1) ; a <= currentSchellingRound ; a++ ) {
for ( a = (clients[msg.sender].paidUpTo + 1) ; a <= currentSchellingRound ; a++ ) {
if (globalFunds[a].reward > 0 && globalFunds[a].supply > 0) {
provAddr = clients[msg.sender].providerAddress;
provHeight = clients[msg.sender].providerHeight;
@ -585,8 +586,9 @@ contract provider is module, safeMath, announcementTypes {
uint256 steps;
uint256 currHeight = providers[addr].currentHeight;
uint256 LTSID = providers[addr].data[currHeight].lastSupplyID;
uint256 a;
var rate = providers[addr].data[currHeight].lastPaidRate;
for ( uint256 a = (providers[addr].data[currHeight].paidUpTo + 1) ; a <= currentSchellingRound ; a++ ) {
for ( a = (providers[addr].data[currHeight].paidUpTo + 1) ; a <= currentSchellingRound ; a++ ) {
if (globalFunds[a].reward > 0 && globalFunds[a].supply > 0) {
if ( providers[addr].data[currHeight].rateHistory[a].valid ) {
rate = providers[addr].data[currHeight].rateHistory[a].value;

View File

@ -155,7 +155,8 @@ library strings {
// Starting at ptr-31 means the LSB will be the byte we care about
var ptr = self._ptr - 31;
var end = ptr + self._len;
for (uint len = 0; ptr < end; len++) {
uint len;
for (len = 0; ptr < end; len++) {
uint8 b;
assembly { b := and(mload(ptr), 0xFF) }
if (b < 0x80) {
@ -695,7 +696,7 @@ library strings {
uint retptr;
assembly { retptr := add(ret, 32) }
for(i = 0; i < parts.length; i++) {
for(uint i = 0; i < parts.length; i++) {
memcpy(retptr, parts[i]._ptr, parts[i]._len);
retptr += parts[i]._len;
if (i < parts.length - 1) {

View File

@ -133,22 +133,6 @@ BOOST_AUTO_TEST_CASE(assignment_in_declaration)
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(use_before_declaration)
{
string text = R"(
contract C {
function f() public pure { a = 3; uint a = 2; assert(a == 2); }
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
function f() public pure { assert(a == 0); uint a = 2; assert(a == 2); }
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(function_call_does_not_clear_local_vars)
{
string text = R"(

View File

@ -5011,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(byte_array_push_transition)
if (data.length != i) return 0x1000 + i;
if (data[data.length - 1] != byte(i)) return i;
}
for (i = 1; i < 40; i++)
for (uint8 i = 1; i < 40; i++)
if (data[i - 1] != byte(i)) return 0x1000000 + i;
return 0;
}

View File

@ -1,8 +1,9 @@
contract test {
function f() pure public {
uint256 x;
if (true) { uint256 x; }
x = 1;
if (true) { uint256 x; x = 2; }
}
}
// ----
// DeclarationError: (71-80): Identifier already declared.
// Warning: (80-89): This declaration shadows an existing declaration.

View File

@ -1,11 +0,0 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
uint256 x;
if (true) { uint256 x; }
}
}
// ----
// Warning: (101-110): This declaration shadows an existing declaration.
// Warning: (76-85): Unused local variable.
// Warning: (101-110): Unused local variable.

View File

@ -2,3 +2,5 @@ contract B {
function f() mod(x) pure public { uint x = 7; }
modifier mod(uint a) { if (a > 0) _; }
}
// ----
// DeclarationError: (34-35): Undeclared identifier.

View File

@ -1,7 +0,0 @@
pragma experimental "v0.5.0";
contract B {
function f() mod(x) pure public { uint x = 7; }
modifier mod(uint a) { if (a > 0) _; }
}
// ----
// DeclarationError: (64-65): Undeclared identifier.

View File

@ -1,5 +1,3 @@
pragma experimental "v0.5.0";
contract C {
function f() internal {
{
@ -9,4 +7,4 @@ contract C {
}
}
// ----
// DeclarationError: (130-131): Undeclared identifier.
// DeclarationError: (99-100): Undeclared identifier.

View File

@ -1,5 +1,3 @@
pragma experimental "v0.5.0";
contract C {
function f() internal {
{
@ -8,6 +6,6 @@ contract C {
}
}
// ----
// DeclarationError: (110-111): Undeclared identifier. Did you mean "a"?
// DeclarationError: (113-114): Undeclared identifier. Did you mean "b"?
// DeclarationError: (116-117): Undeclared identifier. Did you mean "c"?
// DeclarationError: (79-80): Undeclared identifier. "a" is not (or not yet) visible at this point.
// DeclarationError: (82-83): Undeclared identifier. "b" is not (or not yet) visible at this point.
// DeclarationError: (85-86): Undeclared identifier. "c" is not (or not yet) visible at this point.

View File

@ -3,4 +3,4 @@ contract c {
function g() public { f(); }
}
// ----
// DeclarationError: (68-69): Undeclared identifier. Did you mean "f"?
// DeclarationError: (68-69): Undeclared identifier. "f" is not (or not yet) visible at this point.

View File

@ -1,6 +0,0 @@
contract C {
function f() pure public {
a = 7;
uint a;
}
}

View File

@ -1,11 +1,8 @@
contract A {
function f() {
function f() public pure {
uint y = 1;
uint x = 3 < 0 ? x = 3 : 6;
uint x = 3 < 0 ? y = 3 : 6;
true ? x = 3 : 4;
}
}
// ----
// Warning: (17-119): No visibility specified. Defaulting to "public".
// Warning: (40-46): Unused local variable.
// Warning: (17-119): Function state mutability can be restricted to pure

View File

@ -1,9 +1,7 @@
contract test {
function fun(uint256 a) {
while (true) { uint256 x = 1; break; continue; } x = 9;
function fun() public pure {
uint256 x;
while (true) { x = 1; break; continue; } x = 9;
}
}
// ----
// Warning: (20-115): No visibility specified. Defaulting to "public".
// Warning: (33-42): Unused function parameter. Remove or comment out the variable name to silence this warning.
// Warning: (20-115): Function state mutability can be restricted to pure

View File

@ -5,4 +5,5 @@ contract test {
}
}
// ----
// DeclarationError: (77-83): Identifier already declared.
// Warning: (57-63): Unused local variable.
// Warning: (77-83): Unused local variable.

View File

@ -1,10 +0,0 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
{ uint x; }
{ uint x; }
}
}
// ----
// Warning: (87-93): Unused local variable.
// Warning: (107-113): Unused local variable.

View File

@ -5,4 +5,5 @@ contract test {
}
}
// ----
// DeclarationError: (75-81): Identifier already declared.
// Warning: (57-63): Unused local variable.
// Warning: (75-81): Unused local variable.

View File

@ -1,10 +0,0 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
{ uint x; }
uint x;
}
}
// ----
// Warning: (87-93): Unused local variable.
// Warning: (105-111): Unused local variable.

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() public {
{
@ -8,4 +7,4 @@ contract test {
}
}
// ----
// DeclarationError: (123-124): Undeclared identifier.
// DeclarationError: (93-94): Undeclared identifier.

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
x = 3;
@ -6,4 +5,4 @@ contract test {
}
}
// ----
// DeclarationError: (85-86): Undeclared identifier. Did you mean "x"?
// DeclarationError: (55-56): Undeclared identifier. "x" is not (or not yet) visible at this point.

View File

@ -4,3 +4,5 @@ contract test {
uint x;
}
}
// ----
// DeclarationError: (55-56): Undeclared identifier. "x" is not (or not yet) visible at this point.

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++){

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++)

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++){
@ -8,4 +7,4 @@ contract test {
}
}
// ----
// DeclarationError: (154-155): Undeclared identifier.
// DeclarationError: (124-125): Undeclared identifier.

View File

@ -1,4 +1,3 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (;; y++){
@ -7,4 +6,4 @@ contract test {
}
}
// ----
// DeclarationError: (93-94): Undeclared identifier.
// DeclarationError: (63-64): Undeclared identifier.

View File

@ -4,3 +4,5 @@ contract test {
uint256 x = 2;
}
}
// ----
// DeclarationError: (55-56): Undeclared identifier. "x" is not (or not yet) visible at this point.

View File

@ -3,3 +3,5 @@ contract test {
uint a = a;
}
}
// ----
// DeclarationError: (64-65): Undeclared identifier. "a" is not (or not yet) visible at this point.

View File

@ -1,8 +0,0 @@
pragma experimental "v0.5.0";
contract test {
function f() pure public {
uint a = a;
}
}
// ----
// DeclarationError: (94-95): Undeclared identifier. Did you mean "a"?