/
VariableRequiresSetAssignmentEvaluator.cs
175 lines (149 loc) · 6.94 KB
/
VariableRequiresSetAssignmentEvaluator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using Rubberduck.Parsing;
using Rubberduck.Parsing.Grammar;
using Rubberduck.Parsing.Symbols;
using Rubberduck.Parsing.VBA;
using System.Collections.Generic;
using System.Linq;
namespace Rubberduck.Inspections
{
public static class VariableRequiresSetAssignmentEvaluator
{
public static IEnumerable<Declaration> GetDeclarationsPotentiallyRequiringSetAssignment(IEnumerable<Declaration> declarations)
{
var relevantDeclarations = declarations.Where(dec => dec.AsTypeName != Tokens.Variant
&& !SymbolList.ValueTypes.Contains(dec.AsTypeName)
&&(MayRequireAssignmentUsingSet(dec) || RequiresAssignmentUsingSet(dec)));
return relevantDeclarations;
}
public static bool RequiresSetAssignment(IdentifierReference reference, RubberduckParserState state)
{
if (!reference.IsAssignment || !MayRequireAssignmentUsingSet(reference.Declaration))
{
return false;
}
if (RequiresAssignmentUsingSet(reference.Declaration))
{
return true;
}
//We need to look everything to understand the RHS - the assigned reference is probably a Variant
var allInterestingDeclarations = GetDeclarationsPotentiallyRequiringSetAssignment(state.AllUserDeclarations);
return ObjectOrVariantRequiresSetAssignment(reference, allInterestingDeclarations);
}
private static bool MayRequireAssignmentUsingSet(Declaration declaration)
{
if (declaration.DeclarationType == DeclarationType.PropertyLet)
{
return false;
}
if (declaration.AsTypeName == Tokens.Variant)
{
return true;
}
if (declaration.IsArray)
{
return false;
}
if (declaration.AsTypeDeclaration != null)
{
if ((ClassModuleDeclaration.HasDefaultMember(declaration.AsTypeDeclaration)
|| declaration.AsTypeDeclaration.DeclarationType == DeclarationType.Enumeration))
{
return false;
}
}
if (SymbolList.ValueTypes.Contains(declaration.AsTypeName))
{
return false;
}
return true;
}
private static bool RequiresAssignmentUsingSet(Declaration declaration)
{
if (declaration.AsTypeDeclaration != null)
{
return declaration.AsTypeDeclaration.DeclarationType == DeclarationType.UserDefinedType
&& (((IsVariableOrParameter(declaration)
&& !declaration.IsSelfAssigned)
|| (IsMemberWithReturnType(declaration)
&& declaration.IsTypeSpecified)));
}
return false;
}
private static bool IsMemberWithReturnType(Declaration item)
{
return (item.DeclarationType == DeclarationType.Function
|| item.DeclarationType == DeclarationType.PropertyGet);
}
private static bool IsVariableOrParameter(Declaration item)
{
return item.DeclarationType == DeclarationType.Variable
|| item.DeclarationType == DeclarationType.Parameter;
}
private static bool ObjectOrVariantRequiresSetAssignment(IdentifierReference objectOrVariantRef, IEnumerable<Declaration> variantAndObjectDeclarations)
{
//Not an assignment...nothing to evaluate
if (!objectOrVariantRef.IsAssignment)
{
return false;
}
if (IsAlreadyAssignedUsingSet(objectOrVariantRef)
|| objectOrVariantRef.Declaration.AsTypeName != Tokens.Variant)
{
return true;
}
//Variants can be assigned with or without 'Set' depending...
var letStmtContext = ParserRuleContextHelper.GetParent<VBAParser.LetStmtContext>(objectOrVariantRef.Context);
//A potential error is only possible for let statements: rset, lset and other type specific assignments are always let assignments;
//assignemts in for each loop statements are do not require the set keyword.
if(letStmtContext == null)
{
return false;
}
//You can only new up objects.
if (RHSUsesNew(letStmtContext)) { return true; }
if (RHSIsLiteral(letStmtContext))
{
if(RHSIsObjectLiteral(letStmtContext))
{
return true;
}
//All literals but the object literal potentially do not need a set assignment.
//We cannot get more information from the RHS and do not want false positives.
return false;
}
//If the RHS is the identifierName of one of the 'interesting' declarations, we need to use 'Set'
//unless the 'interesting' declaration is also a Variant
var rhsIdentifier = GetRHSIdentifierExpressionText(letStmtContext);
return variantAndObjectDeclarations.Any(dec => dec.IdentifierName == rhsIdentifier && dec.AsTypeName != Tokens.Variant);
}
private static bool IsLetAssignment(IdentifierReference reference)
{
var letStmtContext = ParserRuleContextHelper.GetParent<VBAParser.LetStmtContext>(reference.Context);
return (reference.IsAssignment && letStmtContext != null);
}
private static bool IsAlreadyAssignedUsingSet(IdentifierReference reference)
{
var setStmtContext = ParserRuleContextHelper.GetParent<VBAParser.SetStmtContext>(reference.Context);
return (reference.IsAssignment && setStmtContext?.SET() != null);
}
private static string GetRHSIdentifierExpressionText(VBAParser.LetStmtContext letStmtContext)
{
var expression = letStmtContext.expression();
return expression is VBAParser.LExprContext ? expression.GetText() : string.Empty;
}
private static bool RHSUsesNew(VBAParser.LetStmtContext letStmtContext)
{
var expression = letStmtContext.expression();
return (expression is VBAParser.NewExprContext);
}
private static bool RHSIsLiteral(VBAParser.LetStmtContext letStmtContext)
{
return letStmtContext.expression() is VBAParser.LiteralExprContext;
}
private static bool RHSIsObjectLiteral(VBAParser.LetStmtContext letStmtContext)
{
var rhsAsLiteralExpr = letStmtContext.expression() as VBAParser.LiteralExprContext;
return rhsAsLiteralExpr?.literalExpression()?.literalIdentifier()?.objectLiteralIdentifier() != null;
}
}
}