Skip to content

Commit 02cad44

Browse files
committed
Fix resolution of default member and array accesses on dictionary access expressions
1 parent 39402e8 commit 02cad44

File tree

2 files changed

+319
-14
lines changed

2 files changed

+319
-14
lines changed

Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,38 @@ private IBoundExpression Resolve(IBoundExpression lExpression, int defaultMember
7575
return ResolveLExpressionIsUnbound(lExpression);
7676
}
7777

78-
if (lExpression is IndexExpression indexExpression
79-
&& _argumentList.HasArguments
80-
&& lExpression.ReferencedDeclaration != null)
78+
if(lExpression.ReferencedDeclaration != null)
8179
{
82-
var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, defaultMemberResolutionRecursionDepth);
83-
if (doubleIndexExpression != null)
80+
if (_argumentList.HasArguments)
8481
{
85-
return doubleIndexExpression;
82+
switch (lExpression)
83+
{
84+
case IndexExpression indexExpression:
85+
var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, defaultMemberResolutionRecursionDepth);
86+
if (doubleIndexExpression != null)
87+
{
88+
return doubleIndexExpression;
89+
}
90+
91+
break;
92+
case DictionaryAccessExpression dictionaryAccessExpression:
93+
var indexOnBangExpression = ResolveLExpressionIsDictionaryAccessExpression(dictionaryAccessExpression, defaultMemberResolutionRecursionDepth);
94+
if (indexOnBangExpression != null)
95+
{
96+
return indexOnBangExpression;
97+
}
98+
99+
break;
100+
}
86101
}
87-
}
88102

89-
if (IsVariablePropertyFunctionWithoutParameters(lExpression)
90-
&& lExpression.ReferencedDeclaration != null)
91-
{
92-
var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, defaultMemberResolutionRecursionDepth);
93-
if (parameterlessLExpressionAccess != null)
103+
if (IsVariablePropertyFunctionWithoutParameters(lExpression))
94104
{
95-
return parameterlessLExpressionAccess;
105+
var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, defaultMemberResolutionRecursionDepth);
106+
if (parameterlessLExpressionAccess != null)
107+
{
108+
return parameterlessLExpressionAccess;
109+
}
96110
}
97111
}
98112

@@ -185,6 +199,27 @@ private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression ind
185199
return ResolveDefaultMember(indexExpression, asTypeName, asTypeDeclaration, defaultMemberResolutionRecursionDepth);
186200
}
187201

202+
private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, int defaultMemberResolutionRecursionDepth = 0)
203+
{
204+
//This is equivalent to the case in which the lExpression is an IndexExpression with the difference that it cannot be an array access.
205+
206+
var indexedDeclaration = dictionaryAccessExpression.ReferencedDeclaration;
207+
if (indexedDeclaration == null)
208+
{
209+
return null;
210+
}
211+
212+
if (indexedDeclaration.IsArray)
213+
{
214+
return ResolveLExpressionDeclaredTypeIsArray(dictionaryAccessExpression);
215+
}
216+
217+
var asTypeName = indexedDeclaration.AsTypeName;
218+
var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration;
219+
220+
return ResolveDefaultMember(dictionaryAccessExpression, asTypeName, asTypeDeclaration, defaultMemberResolutionRecursionDepth);
221+
}
222+
188223
private IBoundExpression ResolveDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, int defaultMemberResolutionRecursionDepth)
189224
{
190225
/*

RubberduckTests/Grammar/ResolverTests.cs

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3056,7 +3056,7 @@ End Function
30563056
[Category("Grammar")]
30573057
[Category("Resolver")]
30583058
[Test]
3059-
public void ChainedDictionaryAccessExpressionHasReferenceToDefaultMemberAtExclamationMark()
3059+
public void ChainedSameMemberDictionaryAccessExpressionHasReferenceToDefaultMemberAtExclamationMark()
30603060
{
30613061
var classCode = @"
30623062
Public Function Foo(bar As String) As Class1
@@ -3093,6 +3093,276 @@ End Function
30933093
}
30943094
}
30953095

3096+
[Category("Grammar")]
3097+
[Category("Resolver")]
3098+
[Test]
3099+
public void ChainedDictionaryAccessExpressionHasReferenceToDefaultMemberAtSecondExclamationMark()
3100+
{
3101+
var class1Code = @"
3102+
Public Function Foo(bar As String) As Class2
3103+
Attribute Foo.VB_UserMemId = 0
3104+
Set Foo = New Class2
3105+
End Function
3106+
";
3107+
3108+
var class2Code = @"
3109+
Public Function Baz(bar As String) As Class2
3110+
Attribute Baz.VB_UserMemId = 0
3111+
Set Baz = New Class2
3112+
End Function
3113+
";
3114+
3115+
var moduleCode = @"
3116+
Private Function Foo() As Class1
3117+
Dim cls As new Class1
3118+
Set Foo = cls!newClassObject!whatever
3119+
End Function
3120+
";
3121+
3122+
var vbe = MockVbeBuilder.BuildFromModules(
3123+
("Class1", class1Code, ComponentType.ClassModule),
3124+
("Class2", class2Code, ComponentType.ClassModule),
3125+
("Module1", moduleCode, ComponentType.StandardModule));
3126+
3127+
var selection = new Selection(4, 18, 4, 19);
3128+
3129+
using (var state = Resolve(vbe.Object))
3130+
{
3131+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3132+
var qualifiedSelection = new QualifiedSelection(module, selection);
3133+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3134+
var referencedDeclaration = reference.Declaration;
3135+
3136+
var expectedReferencedDeclarationName = "Class1.Foo";
3137+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3138+
3139+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3140+
Assert.IsTrue(reference.IsDefaultMemberAccess);
3141+
}
3142+
}
3143+
3144+
[Category("Grammar")]
3145+
[Category("Resolver")]
3146+
[Test]
3147+
public void ChainedDictionaryAccessExpressionHasReferenceToDefaultMemberAtFirstExclamationMark()
3148+
{
3149+
var class1Code = @"
3150+
Public Function Foo(bar As String) As Class2
3151+
Attribute Foo.VB_UserMemId = 0
3152+
Set Foo = New Class2
3153+
End Function
3154+
";
3155+
3156+
var class2Code = @"
3157+
Public Function Baz(bar As String) As Class2
3158+
Attribute Baz.VB_UserMemId = 0
3159+
Set Baz = New Class2
3160+
End Function
3161+
";
3162+
3163+
var moduleCode = @"
3164+
Private Function Foo() As Class1
3165+
Dim cls As new Class1
3166+
Set Foo = cls!newClassObject!whatever
3167+
End Function
3168+
";
3169+
3170+
var vbe = MockVbeBuilder.BuildFromModules(
3171+
("Class1", class1Code, ComponentType.ClassModule),
3172+
("Class2", class2Code, ComponentType.ClassModule),
3173+
("Module1", moduleCode, ComponentType.StandardModule));
3174+
3175+
var selection = new Selection(4, 33, 4, 34);
3176+
3177+
using (var state = Resolve(vbe.Object))
3178+
{
3179+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3180+
var qualifiedSelection = new QualifiedSelection(module, selection);
3181+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3182+
var referencedDeclaration = reference.Declaration;
3183+
3184+
var expectedReferencedDeclarationName = "Class2.Baz";
3185+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3186+
3187+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3188+
Assert.IsTrue(reference.IsDefaultMemberAccess);
3189+
}
3190+
}
3191+
3192+
[Category("Grammar")]
3193+
[Category("Resolver")]
3194+
[Test]
3195+
public void DictionaryAccessExpressionWithIndexedDefaultMemberAccessHasReferenceToDefaultMemberAtExclamationMark()
3196+
{
3197+
var class1Code = @"
3198+
Public Function Foo(bar As String) As Class2
3199+
Attribute Foo.VB_UserMemId = 0
3200+
Set Foo = New Class2
3201+
End Function
3202+
";
3203+
3204+
var class2Code = @"
3205+
Public Function Baz(bar As String) As Class2
3206+
Attribute Baz.VB_UserMemId = 0
3207+
Set Baz = New Class2
3208+
End Function
3209+
";
3210+
3211+
var moduleCode = @"
3212+
Private Function Foo() As Class1
3213+
Dim cls As new Class1
3214+
Set Foo = cls!newClassObject(""whatever"")
3215+
End Function
3216+
";
3217+
3218+
var vbe = MockVbeBuilder.BuildFromModules(
3219+
("Class1", class1Code, ComponentType.ClassModule),
3220+
("Class2", class2Code, ComponentType.ClassModule),
3221+
("Module1", moduleCode, ComponentType.StandardModule));
3222+
3223+
var selection = new Selection(4, 18, 4, 19);
3224+
3225+
using (var state = Resolve(vbe.Object))
3226+
{
3227+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3228+
var qualifiedSelection = new QualifiedSelection(module, selection);
3229+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3230+
var referencedDeclaration = reference.Declaration;
3231+
3232+
var expectedReferencedDeclarationName = "Class1.Foo";
3233+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3234+
3235+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3236+
Assert.IsTrue(reference.IsDefaultMemberAccess);
3237+
}
3238+
}
3239+
3240+
[Category("Grammar")]
3241+
[Category("Resolver")]
3242+
[Test]
3243+
public void DictionaryAccessExpressionWithIndexedDefaultMemberAccessHasReferenceToDefaultMemberOnEntireContextExcludingFinalArguments()
3244+
{
3245+
var class1Code = @"
3246+
Public Function Foo(bar As String) As Class2
3247+
Attribute Foo.VB_UserMemId = 0
3248+
Set Foo = New Class2
3249+
End Function
3250+
";
3251+
3252+
var class2Code = @"
3253+
Public Function Baz(bar As String) As Class2
3254+
Attribute Baz.VB_UserMemId = 0
3255+
Set Baz = New Class2
3256+
End Function
3257+
";
3258+
3259+
var moduleCode = @"
3260+
Private Function Foo() As Class1
3261+
Dim cls As new Class1
3262+
Set Foo = cls!newClassObject(""whatever"")
3263+
End Function
3264+
";
3265+
3266+
var vbe = MockVbeBuilder.BuildFromModules(
3267+
("Class1", class1Code, ComponentType.ClassModule),
3268+
("Class2", class2Code, ComponentType.ClassModule),
3269+
("Module1", moduleCode, ComponentType.StandardModule));
3270+
3271+
var selection = new Selection(4, 15, 4, 33);
3272+
3273+
using (var state = Resolve(vbe.Object))
3274+
{
3275+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3276+
var qualifiedSelection = new QualifiedSelection(module, selection);
3277+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3278+
var referencedDeclaration = reference.Declaration;
3279+
3280+
var expectedReferencedDeclarationName = "Class2.Baz";
3281+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3282+
3283+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3284+
Assert.IsTrue(reference.IsDefaultMemberAccess);
3285+
}
3286+
}
3287+
3288+
[Category("Grammar")]
3289+
[Category("Resolver")]
3290+
[Test]
3291+
public void DictionaryAccessExpressionWithArrayAccessHasReferenceToDefaultMemberAtExclamationMark()
3292+
{
3293+
var class1Code = @"
3294+
Public Function Foo(bar As String) As Class1()
3295+
Attribute Foo.VB_UserMemId = 0
3296+
End Function
3297+
";
3298+
3299+
var moduleCode = @"
3300+
Private Function Foo() As Class1
3301+
Dim cls As new Class1
3302+
Set Foo = cls!newClassObject(""whatever"")
3303+
End Function
3304+
";
3305+
3306+
var vbe = MockVbeBuilder.BuildFromModules(
3307+
("Class1", class1Code, ComponentType.ClassModule),
3308+
("Module1", moduleCode, ComponentType.StandardModule));
3309+
3310+
var selection = new Selection(4, 18, 4, 19);
3311+
3312+
using (var state = Resolve(vbe.Object))
3313+
{
3314+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3315+
var qualifiedSelection = new QualifiedSelection(module, selection);
3316+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3317+
var referencedDeclaration = reference.Declaration;
3318+
3319+
var expectedReferencedDeclarationName = "Class1.Foo";
3320+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3321+
3322+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3323+
Assert.IsTrue(reference.IsDefaultMemberAccess);
3324+
}
3325+
}
3326+
3327+
[Category("Grammar")]
3328+
[Category("Resolver")]
3329+
[Test]
3330+
public void DictionaryAccessExpressionWithArrayAccessHasReferenceToDefaultMemberOnEntireContext()
3331+
{
3332+
var class1Code = @"
3333+
Public Function Foo(bar As String) As Class1()
3334+
Attribute Foo.VB_UserMemId = 0
3335+
End Function
3336+
";
3337+
3338+
var moduleCode = @"
3339+
Private Function Foo() As Class1
3340+
Dim cls As new Class1
3341+
Set Foo = cls!newClassObject(0)
3342+
End Function
3343+
";
3344+
3345+
var vbe = MockVbeBuilder.BuildFromModules(
3346+
("Class1", class1Code, ComponentType.ClassModule),
3347+
("Module1", moduleCode, ComponentType.StandardModule));
3348+
3349+
var selection = new Selection(4, 15, 4, 36);
3350+
3351+
using (var state = Resolve(vbe.Object))
3352+
{
3353+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1");
3354+
var qualifiedSelection = new QualifiedSelection(module, selection);
3355+
var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First();
3356+
var referencedDeclaration = reference.Declaration;
3357+
3358+
var expectedReferencedDeclarationName = "Class1.Foo";
3359+
var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}";
3360+
3361+
Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName);
3362+
Assert.IsTrue(reference.IsArrayAccess);
3363+
}
3364+
}
3365+
30963366
[Category("Grammar")]
30973367
[Category("Resolver")]
30983368
[Test]

0 commit comments

Comments
 (0)