3
3
using Rubberduck . Parsing . Symbols ;
4
4
using Rubberduck . Parsing . VBA ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
6
7
using System . Linq ;
8
+ using System . Windows . Forms ;
7
9
8
10
namespace Rubberduck . Inspections
9
11
{
@@ -21,28 +23,110 @@ public static IEnumerable<Declaration> GetDeclarationsPotentiallyRequiringSetAss
21
23
22
24
public static bool RequiresSetAssignment ( IdentifierReference reference , RubberduckParserState state )
23
25
{
24
- //Not an assignment...definitely does not require a 'Set' assignment
25
26
if ( ! reference . IsAssignment )
26
27
{
28
+ // reference isn't assigning its declaration; not interesting
27
29
return false ;
28
30
}
29
-
30
- //We know for sure it DOES NOT use 'Set'
31
- if ( ! MayRequireAssignmentUsingSet ( reference . Declaration ) )
31
+
32
+ var setStmtContext = reference . Context . GetAncestor < VBAParser . SetStmtContext > ( ) ;
33
+ if ( setStmtContext != null )
34
+ {
35
+ // assignment already has a Set keyword
36
+ // (but is it misplaced? ...hmmm... beyond the scope of *this* inspection though)
37
+ // if we're only ever assigning to 'Nothing', might as well flag it though
38
+ if ( reference . Declaration . References . Where ( r => r . IsAssignment ) . All ( r =>
39
+ r . Context . GetAncestor < VBAParser . SetStmtContext > ( ) . expression ( ) . GetText ( ) == Tokens . Nothing ) )
40
+ {
41
+ return true ;
42
+ }
43
+ }
44
+
45
+ var letStmtContext = reference . Context . GetAncestor < VBAParser . LetStmtContext > ( ) ;
46
+ if ( letStmtContext == null )
47
+ {
48
+ // we're probably in a For Each loop
49
+ return false ;
50
+ }
51
+
52
+ var declaration = reference . Declaration ;
53
+ if ( declaration . IsArray )
32
54
{
55
+ // arrays don't need a Set statement... todo figure out if array items are objects
33
56
return false ;
34
57
}
35
58
36
- //We know for sure that it DOES use 'Set'
37
- if ( RequiresAssignmentUsingSet ( reference . Declaration ) )
59
+ var isObjectVariable = declaration . IsObject ( ) ;
60
+ var isVariant = declaration . IsUndeclared || declaration . AsTypeName == Tokens . Variant ;
61
+ if ( ! isObjectVariable && ! isVariant )
62
+ {
63
+ return false ;
64
+ }
65
+
66
+ if ( isObjectVariable )
67
+ {
68
+ // get the members of the returning type, a default member could make us lie otherwise
69
+ var classModule = declaration . AsTypeDeclaration as ClassModuleDeclaration ;
70
+ if ( classModule ? . DefaultMember != null )
71
+ {
72
+ var parameters = ( classModule . DefaultMember as IParameterizedDeclaration ) ? . Parameters . ToArray ( ) ?? Enumerable . Empty < ParameterDeclaration > ( ) . ToArray ( ) ;
73
+ if ( ! parameters . Any ( ) || parameters . All ( p => p . IsOptional ) )
74
+ {
75
+ // assigned declaration has a default parameterless member, which is legally being assigned here.
76
+ // might be a good idea to flag that default member assignment though...
77
+ return false ;
78
+ }
79
+ }
80
+
81
+ // assign declaration is an object without a default parameterless (or with all parameters optional) member - LHS needs a 'Set' keyword.
82
+ return true ;
83
+ }
84
+
85
+ // assigned declaration is a variant. we need to know about the RHS of the assignment.
86
+
87
+ var expression = letStmtContext . expression ( ) ;
88
+ if ( expression == null )
89
+ {
90
+ Debug . Assert ( false , "RHS expression is empty? What's going on here?" ) ;
91
+ }
92
+
93
+ if ( expression is VBAParser . NewExprContext )
94
+ {
95
+ // RHS expression is newing up an object reference - LHS needs a 'Set' keyword:
96
+ return true ;
97
+ }
98
+
99
+ var literalExpression = expression as VBAParser . LiteralExprContext ;
100
+ if ( literalExpression ? . literalExpression ( ) ? . literalIdentifier ( ) ? . objectLiteralIdentifier ( ) != null )
38
101
{
102
+ // RHS is a 'Nothing' token - LHS needs a 'Set' keyword:
39
103
return true ;
40
104
}
41
105
42
- //We need to look everything to understand the RHS - the assigned reference is probably a Variant
43
- var allInterestingDeclarations = GetDeclarationsPotentiallyRequiringSetAssignment ( state . AllUserDeclarations ) ;
106
+ // todo resolve expression return type
44
107
45
- return ObjectOrVariantRequiresSetAssignment ( reference , allInterestingDeclarations ) ;
108
+ var memberRefs = state . DeclarationFinder . IdentifierReferences ( reference . ParentScoping . QualifiedName ) ;
109
+ var lastRef = memberRefs . LastOrDefault ( r => ! Equals ( r , reference ) && r . Context . GetAncestor < VBAParser . LetStmtContext > ( ) == letStmtContext ) ;
110
+ if ( lastRef ? . Declaration . AsTypeDeclaration ? . DeclarationType . HasFlag ( DeclarationType . ClassModule ) ?? false )
111
+ {
112
+ // the last reference in the expression is referring to an object type
113
+ return true ;
114
+ }
115
+ if ( lastRef ? . Declaration . AsTypeName == Tokens . Object )
116
+ {
117
+ return true ;
118
+ }
119
+
120
+ var accessibleDeclarations = state . DeclarationFinder . GetAccessibleDeclarations ( reference . ParentScoping ) ;
121
+ foreach ( var accessibleDeclaration in accessibleDeclarations . Where ( d => d . IdentifierName == expression . GetText ( ) ) )
122
+ {
123
+ if ( accessibleDeclaration . DeclarationType . HasFlag ( DeclarationType . ClassModule ) || accessibleDeclaration . AsTypeName == Tokens . Object )
124
+ {
125
+ return true ;
126
+ }
127
+ }
128
+
129
+ return false ;
46
130
}
47
131
48
132
private static bool MayRequireAssignmentUsingSet ( Declaration declaration )
0 commit comments