Skip to content

Commit b080934

Browse files
authored
Merge pull request #5117 from MDoerner/ArgumentWithIncompatibleObjectTypeInspection
ArgumentWithIncompatibleObjectTypeInspection
2 parents 89f9298 + 6c70467 commit b080934

30 files changed

+765
-43
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Rubberduck.Inspections.Abstract;
4+
using Rubberduck.Inspections.Inspections.Extensions;
5+
using Rubberduck.Inspections.Results;
6+
using Rubberduck.Parsing.Grammar;
7+
using Rubberduck.Parsing.Inspections;
8+
using Rubberduck.Parsing.Inspections.Abstract;
9+
using Rubberduck.Parsing.Symbols;
10+
using Rubberduck.Parsing.TypeResolvers;
11+
using Rubberduck.Parsing.VBA;
12+
using Rubberduck.Parsing.VBA.DeclarationCaching;
13+
using Rubberduck.Resources.Inspections;
14+
using Rubberduck.VBEditor;
15+
16+
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
17+
{
18+
public class ArgumentWithIncompatibleObjectTypeInspection : InspectionBase
19+
{
20+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
21+
private readonly ISetTypeResolver _setTypeResolver;
22+
23+
/// <summary>
24+
/// Locates arguments passed to functions or procedures for object parameters which the do not have a compatible declared type.
25+
/// </summary>
26+
/// <why>
27+
/// The VBA compiler does not check whether different object types are compatible. Instead there is a runtime error whenever the types are incompatible.
28+
/// </why>
29+
/// <example hasResult="true">
30+
/// <![CDATA[
31+
/// IInterface:
32+
///
33+
/// Public Sub DoSomething()
34+
/// End Sub
35+
///
36+
/// ------------------------------
37+
/// Class1:
38+
///
39+
///'No Implements IInterface
40+
///
41+
/// Public Sub DoSomething()
42+
/// End Sub
43+
///
44+
/// ------------------------------
45+
/// Module1:
46+
///
47+
/// Public Sub DoIt()
48+
/// Dim cls As Class1
49+
/// Set cls = New Class1
50+
/// Foo cls
51+
/// End Sub
52+
///
53+
/// Public Sub Foo(cls As IInterface)
54+
/// End Sub
55+
/// ]]>
56+
/// </example>
57+
/// <example hasResult="false">
58+
/// <![CDATA[
59+
/// IInterface:
60+
///
61+
/// Public Sub DoSomething()
62+
/// End Sub
63+
///
64+
/// ------------------------------
65+
/// Class1:
66+
///
67+
/// Implements IInterface
68+
///
69+
/// Private Sub IInterface_DoSomething()
70+
/// End Sub
71+
///
72+
/// ------------------------------
73+
/// Module1:
74+
///
75+
/// Public Sub DoIt()
76+
/// Dim cls As Class1
77+
/// Set cls = New Class1
78+
/// Foo cls
79+
/// End Sub
80+
///
81+
/// Public Sub Foo(cls As IInterface)
82+
/// End Sub
83+
/// ]]>
84+
/// </example>
85+
public ArgumentWithIncompatibleObjectTypeInspection(RubberduckParserState state, ISetTypeResolver setTypeResolver)
86+
: base(state)
87+
{
88+
_declarationFinderProvider = state;
89+
_setTypeResolver = setTypeResolver;
90+
91+
//This will most likely cause a runtime error. The exceptions are rare and should be refactored or made explicit with an @Ignore annotation.
92+
Severity = CodeInspectionSeverity.Error;
93+
}
94+
95+
protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
96+
{
97+
var finder = _declarationFinderProvider.DeclarationFinder;
98+
99+
var strictlyTypedObjectParameters = finder.DeclarationsWithType(DeclarationType.Parameter)
100+
.Where(ToBeConsidered)
101+
.OfType<ParameterDeclaration>();
102+
103+
var offendingArguments = strictlyTypedObjectParameters
104+
.SelectMany(param => param.ArgumentReferences)
105+
.Select(argumentReference => ArgumentReferenceWithArgumentTypeName(argumentReference, finder))
106+
.Where(argumentReferenceWithTypeName => argumentReferenceWithTypeName.argumentTypeName != null
107+
&& !ArgumentPossiblyLegal(
108+
argumentReferenceWithTypeName.argumentReference.Declaration,
109+
argumentReferenceWithTypeName.argumentTypeName));
110+
111+
return offendingArguments
112+
.Where(argumentReferenceWithTypeName => !IsIgnored(argumentReferenceWithTypeName.Item1))
113+
.Select(argumentReference => InspectionResult(argumentReference, _declarationFinderProvider));
114+
}
115+
116+
private static bool ToBeConsidered(Declaration declaration)
117+
{
118+
return declaration != null
119+
&& declaration.AsTypeDeclaration != null
120+
&& declaration.IsObject;
121+
}
122+
123+
private (IdentifierReference argumentReference, string argumentTypeName) ArgumentReferenceWithArgumentTypeName(IdentifierReference argumentReference, DeclarationFinder finder)
124+
{
125+
return (argumentReference, ArgumentSetTypeName(argumentReference, finder));
126+
}
127+
128+
private string ArgumentSetTypeName(IdentifierReference argumentReference, DeclarationFinder finder)
129+
{
130+
var argumentExpression = argumentReference.Context as VBAParser.ExpressionContext;
131+
return SetTypeNameOfExpression(argumentExpression, argumentReference.QualifiedModuleName, finder);
132+
}
133+
134+
private string SetTypeNameOfExpression(VBAParser.ExpressionContext expression, QualifiedModuleName containingModule, DeclarationFinder finder)
135+
{
136+
return _setTypeResolver.SetTypeName(expression, containingModule);
137+
}
138+
139+
private bool ArgumentPossiblyLegal(Declaration parameterDeclaration , string assignedTypeName)
140+
{
141+
return assignedTypeName == parameterDeclaration.FullAsTypeName
142+
|| assignedTypeName == Tokens.Variant
143+
|| assignedTypeName == Tokens.Object
144+
|| HasBaseType(parameterDeclaration, assignedTypeName)
145+
|| HasSubType(parameterDeclaration, assignedTypeName);
146+
}
147+
148+
private bool HasBaseType(Declaration declaration, string typeName)
149+
{
150+
var ownType = declaration.AsTypeDeclaration;
151+
if (ownType == null || !(ownType is ClassModuleDeclaration classType))
152+
{
153+
return false;
154+
}
155+
156+
return classType.Subtypes.Select(subtype => subtype.QualifiedModuleName.ToString()).Contains(typeName);
157+
}
158+
159+
private bool HasSubType(Declaration declaration, string typeName)
160+
{
161+
var ownType = declaration.AsTypeDeclaration;
162+
if (ownType == null || !(ownType is ClassModuleDeclaration classType))
163+
{
164+
return false;
165+
}
166+
167+
return classType.Supertypes.Select(supertype => supertype.QualifiedModuleName.ToString()).Contains(typeName);
168+
}
169+
170+
private bool IsIgnored(IdentifierReference assignment)
171+
{
172+
return assignment.IsIgnoringInspectionResultFor(AnnotationName)
173+
// Ignoring the Declaration disqualifies all assignments
174+
|| assignment.Declaration.IsIgnoringInspectionResultFor(AnnotationName);
175+
}
176+
177+
private IInspectionResult InspectionResult((IdentifierReference argumentReference, string argumentTypeName) argumentReferenceWithTypeName, IDeclarationFinderProvider declarationFinderProvider)
178+
{
179+
var (argumentReference, argumentTypeName) = argumentReferenceWithTypeName;
180+
return new IdentifierReferenceInspectionResult(this,
181+
ResultDescription(argumentReference, argumentTypeName),
182+
declarationFinderProvider,
183+
argumentReference);
184+
}
185+
186+
private string ResultDescription(IdentifierReference argumentReference, string argumentTypeName)
187+
{
188+
var parameterName = argumentReference.Declaration.IdentifierName;
189+
var parameterTypeName = argumentReference.Declaration.FullAsTypeName;
190+
var argumentExpression = argumentReference.Context.GetText();
191+
return string.Format(InspectionResults.SetAssignmentWithIncompatibleObjectTypeInspection, parameterName, parameterTypeName, argumentExpression, argumentTypeName);
192+
}
193+
}
194+
}

Rubberduck.CodeAnalysis/Inspections/Concrete/ImplementedInterfaceMemberInspection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
4343
&& !member.IsIgnoringInspectionResultFor(AnnotationName)))
4444
.Select(result => new DeclarationInspectionResult(this,
4545
string.Format(InspectionResults.ImplementedInterfaceMemberInspection,
46+
result.QualifiedModuleName.ToString(),
4647
Resources.RubberduckUI.ResourceManager
4748
.GetString("DeclarationType_" + result.DeclarationType)
4849
.Capitalize(),

Rubberduck.Parsing/Binding/ArgumentListArgument.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace Rubberduck.Parsing.Binding
99
public sealed class ArgumentListArgument
1010
{
1111
private readonly IExpressionBinding _binding;
12-
private readonly ParserRuleContext _context;
1312
private readonly Func<Declaration, IBoundExpression> _namedArgumentExpressionCreator;
1413
private readonly bool _isAddressOfArgument;
1514

@@ -21,54 +20,59 @@ public ArgumentListArgument(IExpressionBinding binding, ParserRuleContext contex
2120
public ArgumentListArgument(IExpressionBinding binding, ParserRuleContext context, ArgumentListArgumentType argumentType, Func<Declaration, IBoundExpression> namedArgumentExpressionCreator, bool isAddressOfArgument = false)
2221
{
2322
_binding = binding;
24-
_context = context;
23+
Context = context;
2524
ArgumentType = argumentType;
2625
_namedArgumentExpressionCreator = namedArgumentExpressionCreator;
2726
_isAddressOfArgument = isAddressOfArgument;
27+
ReferencedParameter = null;
2828
}
2929

3030
public ArgumentListArgumentType ArgumentType { get; }
3131
public IBoundExpression NamedArgumentExpression { get; private set; }
3232
public IBoundExpression Expression { get; private set; }
33+
public ParameterDeclaration ReferencedParameter { get; private set; }
34+
public ParserRuleContext Context { get; }
3335

34-
public void Resolve(Declaration calledProcedure, int parameterIndex)
36+
public void Resolve(Declaration calledProcedure, int parameterIndex, bool isArrayAccess = false)
3537
{
3638
var binding = _binding;
3739
if (calledProcedure != null)
3840
{
3941
NamedArgumentExpression = _namedArgumentExpressionCreator(calledProcedure);
42+
ReferencedParameter = ResolveReferencedParameter(calledProcedure, parameterIndex);
4043

41-
if (!_isAddressOfArgument && !CanBeObject(calledProcedure, parameterIndex))
44+
if (!_isAddressOfArgument
45+
&& !(Context is VBAParser.MissingArgumentContext)
46+
&& (isArrayAccess
47+
|| ReferencedParameter != null
48+
&& !CanBeObject(ReferencedParameter)))
4249
{
43-
binding = new LetCoercionDefaultBinding(_context, binding);
50+
binding = new LetCoercionDefaultBinding(Context, binding);
4451
}
4552
}
4653

4754
Expression = binding.Resolve();
4855
}
4956

50-
private bool CanBeObject(Declaration calledProcedure, int parameterIndex)
57+
private ParameterDeclaration ResolveReferencedParameter(Declaration calledProcedure, int parameterIndex)
5158
{
5259
if (NamedArgumentExpression != null)
5360
{
54-
var correspondingParameter = NamedArgumentExpression.ReferencedDeclaration as ParameterDeclaration;
55-
return CanBeObject(correspondingParameter);
61+
return NamedArgumentExpression.ReferencedDeclaration as ParameterDeclaration;
5662
}
5763

5864
if (parameterIndex >= 0 && calledProcedure is IParameterizedDeclaration parameterizedDeclaration)
5965
{
6066
var parameters = parameterizedDeclaration.Parameters.ToList();
6167
if (parameterIndex >= parameters.Count)
6268
{
63-
return parameters.Any(param => param.IsParamArray);
69+
return parameters.FirstOrDefault(param => param.IsParamArray);
6470
}
6571

66-
var correspondingParameter = parameters[parameterIndex];
67-
return CanBeObject(correspondingParameter);
68-
72+
return parameters[parameterIndex];
6973
}
7074

71-
return true;
75+
return null;
7276
}
7377

7478
private bool CanBeObject(ParameterDeclaration parameter)

Rubberduck.Parsing/Binding/ArgumentListArgumentType.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public enum ArgumentListArgumentType
44
{
55
Positional,
6-
Named
6+
Named,
7+
Missing
78
}
89
}

Rubberduck.Parsing/Binding/Bindings/BinaryOpDefaultBinding.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public IBoundExpression Resolve()
2626
if (leftExpr.Classification == ExpressionClassification.ResolutionFailed)
2727
{
2828
var failedExpr = (ResolutionFailedExpression) leftExpr;
29-
return failedExpr.Join(rightExpr);
29+
return failedExpr.Join(_context, rightExpr);
3030
}
3131

3232
if (rightExpr.Classification == ExpressionClassification.ResolutionFailed)
3333
{
3434
var failedExpr = (ResolutionFailedExpression)rightExpr;
35-
return failedExpr.Join(leftExpr);
35+
return failedExpr.Join(_context, leftExpr);
3636
}
3737

3838
return new BinaryOpExpression(null, _context, leftExpr, rightExpr);

Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentLi
6767

6868
ResolveArgumentList(null, argumentList);
6969
var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression);
70-
return failedExpression.Join(argumentExpressions);
70+
return failedExpression.Join(expression, argumentExpressions);
7171
}
7272

7373
if (!(expression is VBAParser.LExpressionContext lExpressionContext))
@@ -107,7 +107,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress
107107
failedExpr.AddSuccessfullyResolvedExpression(lExpression);
108108

109109
var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression);
110-
return failedExpr.Join(argumentExpressions);
110+
return failedExpr.Join(context, argumentExpressions);
111111
}
112112

113113
private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, ParserRuleContext defaultMemberContext, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null)

Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ public IndexDefaultBinding(
4444
_argumentList = argumentList;
4545
}
4646

47-
private static void ResolveArgumentList(Declaration calledProcedure, ArgumentList argumentList)
47+
private static void ResolveArgumentList(Declaration calledProcedure, ArgumentList argumentList, bool isArrayAccess = false)
4848
{
4949
var arguments = argumentList.Arguments;
5050
for (var index = 0; index < arguments.Count; index++)
5151
{
52-
arguments[index].Resolve(calledProcedure, index);
52+
arguments[index].Resolve(calledProcedure, index, isArrayAccess);
5353
}
5454
}
5555

@@ -71,7 +71,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu
7171

7272
ResolveArgumentList(null, argumentList);
7373
var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression);
74-
return failedExpression.Join(argumentExpressions);
74+
return failedExpression.Join(expression, argumentExpressions);
7575
}
7676

7777
if (lExpression.Classification == ExpressionClassification.Unbound)
@@ -130,7 +130,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress
130130
var failedExpr = new ResolutionFailedExpression(context, isDefaultMemberResolution);
131131
failedExpr.AddSuccessfullyResolvedExpression(lExpression);
132132
var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression);
133-
return failedExpr.Join(argumentExpressions);
133+
return failedExpr.Join(context, argumentExpressions);
134134
}
135135

136136
private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression)
@@ -359,7 +359,7 @@ declared type of the array’s element type.
359359
TODO: Implement compatibility checking
360360
*/
361361

362-
ResolveArgumentList(indexedDeclaration.AsTypeDeclaration, argumentList);
362+
ResolveArgumentList(indexedDeclaration, argumentList, true);
363363
return new IndexExpression(indexedDeclaration, ExpressionClassification.Variable, expression, _lExpression, argumentList, isArrayAccess: true, defaultMemberRecursionDepth: defaultMemberRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression);
364364
}
365365

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Antlr4.Runtime;
2+
using Rubberduck.Parsing.Symbols;
3+
4+
namespace Rubberduck.Parsing.Binding
5+
{
6+
public sealed class MissingArgumentBinding : IExpressionBinding
7+
{
8+
private readonly Declaration _parent;
9+
private readonly ParserRuleContext _missingArgumentContext;
10+
11+
public MissingArgumentBinding(ParserRuleContext missingArgumentContext)
12+
{
13+
_missingArgumentContext = missingArgumentContext;
14+
}
15+
16+
public IBoundExpression Resolve()
17+
{
18+
return new MissingArgumentExpression(ExpressionClassification.Variable, _missingArgumentContext);
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)