Skip to content

Commit 0fff4e6

Browse files
authored
Merge pull request #10908 from a3d4/fix-9231-struct-member-names-shadow-type-names
Fix shadowing struct types by struct member names
2 parents edee67b + 362fc66 commit 0fff4e6

24 files changed

+289
-20
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Bugfixes:
2121
* Control Flow Graph: Take internal calls to functions that always revert into account for reporting unused or unassigned variables.
2222
* Control Flow Graph: Assume unimplemented modifiers use a placeholder.
2323
* Function Call Graph: Fix internal error connected with circular constant references.
24+
* Name Resolver: Do not issue shadowing warning if the shadowing name is not directly accessible.
2425
* Natspec: Allow multiple ``@return`` tags on public state variable documentation.
2526
* SMTChecker: Fix internal error on struct constructor with fixed bytes member initialized with string literal.
2627
* SMTChecker: Fix internal error on external calls from the constructor.

libsolidity/analysis/DeclarationContainer.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
#include <libsolidity/ast/AST.h>
2727
#include <libsolutil/StringUtils.h>
2828

29+
#include <range/v3/view/filter.hpp>
30+
#include <range/v3/range/conversion.hpp>
31+
2932
using namespace std;
3033
using namespace solidity;
3134
using namespace solidity::frontend;
@@ -120,11 +123,7 @@ bool DeclarationContainer::registerDeclaration(
120123
if (conflictingDeclaration(_declaration, _name))
121124
return false;
122125

123-
// Do not warn about shadowing for structs and enums because their members are
124-
// not accessible without prefixes. Also do not warn about event parameters
125-
// because they do not participate in any proper scope.
126-
bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventOrErrorParameter());
127-
if (m_enclosingContainer && !special)
126+
if (m_enclosingContainer && _declaration.isVisibleAsUnqualifiedName())
128127
m_homonymCandidates.emplace_back(*_name, _location ? _location : &_declaration.location());
129128
}
130129

@@ -143,16 +142,35 @@ bool DeclarationContainer::registerDeclaration(
143142
return registerDeclaration(_declaration, nullptr, nullptr, _invisible, _update);
144143
}
145144

146-
vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive, bool _alsoInvisible) const
145+
vector<Declaration const*> DeclarationContainer::resolveName(
146+
ASTString const& _name,
147+
bool _recursive,
148+
bool _alsoInvisible,
149+
bool _onlyVisibleAsUnqualifiedNames
150+
) const
147151
{
148152
solAssert(!_name.empty(), "Attempt to resolve empty name.");
149153
vector<Declaration const*> result;
154+
150155
if (m_declarations.count(_name))
151-
result = m_declarations.at(_name);
156+
{
157+
if (_onlyVisibleAsUnqualifiedNames)
158+
result += m_declarations.at(_name) | ranges::views::filter(&Declaration::isVisibleAsUnqualifiedName) | ranges::to_vector;
159+
else
160+
result += m_declarations.at(_name);
161+
}
162+
152163
if (_alsoInvisible && m_invisibleDeclarations.count(_name))
153-
result += m_invisibleDeclarations.at(_name);
164+
{
165+
if (_onlyVisibleAsUnqualifiedNames)
166+
result += m_invisibleDeclarations.at(_name) | ranges::views::filter(&Declaration::isVisibleAsUnqualifiedName) | ranges::to_vector;
167+
else
168+
result += m_invisibleDeclarations.at(_name);
169+
}
170+
154171
if (result.empty() && _recursive && m_enclosingContainer)
155-
result = m_enclosingContainer->resolveName(_name, true, _alsoInvisible);
172+
result = m_enclosingContainer->resolveName(_name, true, _alsoInvisible, _onlyVisibleAsUnqualifiedNames);
173+
156174
return result;
157175
}
158176

libsolidity/analysis/DeclarationContainer.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,10 @@ class DeclarationContainer
3939
public:
4040
using Homonyms = std::vector<std::pair<langutil::SourceLocation const*, std::vector<Declaration const*>>>;
4141

42-
explicit DeclarationContainer(
43-
ASTNode const* _enclosingNode = nullptr,
44-
DeclarationContainer* _enclosingContainer = nullptr
45-
):
46-
m_enclosingNode(_enclosingNode), m_enclosingContainer(_enclosingContainer)
42+
DeclarationContainer() = default;
43+
explicit DeclarationContainer(ASTNode const* _enclosingNode, DeclarationContainer* _enclosingContainer):
44+
m_enclosingNode(_enclosingNode),
45+
m_enclosingContainer(_enclosingContainer)
4746
{
4847
if (_enclosingContainer)
4948
_enclosingContainer->m_innerContainers.emplace_back(this);
@@ -57,7 +56,20 @@ class DeclarationContainer
5756
bool registerDeclaration(Declaration const& _declaration, ASTString const* _name, langutil::SourceLocation const* _location, bool _invisible, bool _update);
5857
bool registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update);
5958

60-
std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false, bool _alsoInvisible = false) const;
59+
/// Finds all declarations that in the current scope can be referred to using specified name.
60+
/// @param _name the name to look for.
61+
/// @param _recursive if true and there are no matching declarations in the current container,
62+
/// recursively searches the enclosing containers as well.
63+
/// @param _alsoInvisible if true, include invisible declaration in the results.
64+
/// @param _onlyVisibleAsUnqualifiedNames if true, do not include declarations which can never
65+
/// actually be referenced using their name alone (without being qualified with the name
66+
/// of scope in which they are declared).
67+
std::vector<Declaration const*> resolveName(
68+
ASTString const& _name,
69+
bool _recursive = false,
70+
bool _alsoInvisible = false,
71+
bool _onlyVisibleAsUnqualifiedNames = false
72+
) const;
6173
ASTNode const* enclosingNode() const { return m_enclosingNode; }
6274
DeclarationContainer const* enclosingContainer() const { return m_enclosingContainer; }
6375
std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; }
@@ -80,8 +92,8 @@ class DeclarationContainer
8092
void populateHomonyms(std::back_insert_iterator<Homonyms> _it) const;
8193

8294
private:
83-
ASTNode const* m_enclosingNode;
84-
DeclarationContainer const* m_enclosingContainer;
95+
ASTNode const* m_enclosingNode = nullptr;
96+
DeclarationContainer const* m_enclosingContainer = nullptr;
8597
std::vector<DeclarationContainer const*> m_innerContainers;
8698
std::map<ASTString, std::vector<Declaration const*>> m_declarations;
8799
std::map<ASTString, std::vector<Declaration const*>> m_invisibleDeclarations;

libsolidity/analysis/NameAndTypeResolver.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,13 @@ vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString c
182182
Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path) const
183183
{
184184
solAssert(!_path.empty(), "");
185-
vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), true);
185+
vector<Declaration const*> candidates = m_currentScope->resolveName(
186+
_path.front(),
187+
/* _recursive */ true,
188+
/* _alsoInvisible */ false,
189+
/* _onlyVisibleAsUnqualifiedNames */ true
190+
);
191+
186192
for (size_t i = 1; i < _path.size() && candidates.size() == 1; i++)
187193
{
188194
if (!m_scopes.count(candidates.front()))
@@ -627,7 +633,10 @@ void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope)
627633
solAssert(dynamic_cast<SourceUnit const*>(&_subScope), "Unexpected scope type.");
628634
else
629635
{
630-
bool newlyAdded = m_scopes.emplace(&_subScope, make_shared<DeclarationContainer>(m_currentScope, m_scopes[m_currentScope].get())).second;
636+
bool newlyAdded = m_scopes.emplace(
637+
&_subScope,
638+
make_shared<DeclarationContainer>(m_currentScope, m_scopes[m_currentScope].get())
639+
).second;
631640
solAssert(newlyAdded, "Unable to add new scope.");
632641
}
633642
m_currentScope = &_subScope;

libsolidity/ast/AST.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,18 @@ bool Declaration::isEventOrErrorParameter() const
606606
return dynamic_cast<EventDefinition const*>(scope()) || dynamic_cast<ErrorDefinition const*>(scope());
607607
}
608608

609+
bool Declaration::isVisibleAsUnqualifiedName() const
610+
{
611+
if (!scope())
612+
return true;
613+
if (isStructMember() || isEnumValue() || isEventOrErrorParameter())
614+
return false;
615+
if (auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(scope()))
616+
if (!functionDefinition->isImplemented())
617+
return false; // parameter of a function without body
618+
return true;
619+
}
620+
609621
DeclarationAnnotation& Declaration::annotation() const
610622
{
611623
return initAnnotation<DeclarationAnnotation>();

libsolidity/ast/AST.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ class Declaration: public ASTNode, public Scopable
265265
/// @returns true if this is a declaration of a parameter of an event.
266266
bool isEventOrErrorParameter() const;
267267

268+
/// @returns false if the declaration can never be referenced without being qualified with a scope.
269+
/// Usually the name alone can be used to refer to the corresponding entity.
270+
/// But, for example, struct member names or enum member names always require a prefix.
271+
/// Another example is event parameter names, which do not participate in any proper scope.
272+
bool isVisibleAsUnqualifiedName() const;
273+
268274
/// @returns the type of expressions referencing this declaration.
269275
/// This can only be called once types of variable declarations have already been resolved.
270276
virtual Type const* type() const = 0;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
pragma abicoder v2;
2+
3+
contract C {
4+
enum EnumType {A, B, C}
5+
6+
struct StructType {
7+
uint x;
8+
}
9+
10+
error E1(StructType StructType);
11+
error E2(EnumType StructType, StructType EnumType);
12+
13+
function f() public {
14+
revert E1({StructType: StructType(42)});
15+
}
16+
17+
function g() public {
18+
revert E2({EnumType: StructType(42), StructType: EnumType.B});
19+
}
20+
}
21+
// ====
22+
// compileToEwasm: also
23+
// compileViaYul: also
24+
// ----
25+
// f() -> FAILURE, hex"33a54193", hex"000000000000000000000000000000000000000000000000000000000000002a"
26+
// g() -> FAILURE, hex"374b9387", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"000000000000000000000000000000000000000000000000000000000000002a"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
contract C {
2+
struct S {
3+
uint x;
4+
}
5+
6+
enum E {E, S, C, a, f}
7+
8+
uint a;
9+
10+
function f() public pure {}
11+
}
12+
// ----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
contract C {
2+
error E(int bytes, bytes x);
3+
}
4+
// ----
5+
// ParserError 2314: (29-34): Expected ',' but got 'bytes'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
contract C {
2+
enum EnumType {A, B, C}
3+
4+
struct StructType {
5+
uint x;
6+
}
7+
8+
error E1(StructType StructType);
9+
error E2(EnumType EnumType);
10+
error E3(EnumType StructType, StructType EnumType);
11+
}
12+
// ----

0 commit comments

Comments
 (0)