Skip to content

Commit f913d7a

Browse files
committed
Add let coercion default member access resolution (part 1)
This commit contains the general setup for resolving default member accesses due to let coercion and covers all cases in which the coercion does not happen on a subexpression level.
1 parent ea7d8d6 commit f913d7a

13 files changed

+574
-182
lines changed

Rubberduck.Parsing/Binding/BindingService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public Declaration ResolveGoTo(Declaration procedure, string label)
3434
return _declarationFinder.FindLabel(procedure, label);
3535
}
3636

37-
public IBoundExpression ResolveDefault(Declaration module, Declaration parent, ParserRuleContext expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
37+
public IBoundExpression ResolveDefault(Declaration module, Declaration parent, ParserRuleContext expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion, bool isLetAssignment = false)
3838
{
39-
return _defaultBindingContext.Resolve(module, parent, expression, withBlockVariable, statementContext);
39+
return _defaultBindingContext.Resolve(module, parent, expression, withBlockVariable, statementContext, requiresLetCoercion, isLetAssignment);
4040
}
4141

4242
public IBoundExpression ResolveType(Declaration module, Declaration parent, ParserRuleContext expression)
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Rubberduck.Parsing.Symbols;
4+
using System.Linq;
5+
using Antlr4.Runtime;
6+
using Rubberduck.Parsing.Grammar;
7+
8+
namespace Rubberduck.Parsing.Binding
9+
{
10+
public sealed class LetCoercionDefaultBinding : IExpressionBinding
11+
{
12+
private readonly ParserRuleContext _expression;
13+
private readonly IExpressionBinding _wrappedExpressionBinding;
14+
private IBoundExpression _wrappedExpression;
15+
private readonly bool _isAssignment;
16+
17+
private const int DEFAULT_MEMBER_RECURSION_LIMIT = 32;
18+
19+
//This is a wrapper used to model Let coercion for object types.
20+
21+
public LetCoercionDefaultBinding(
22+
ParserRuleContext expression,
23+
IExpressionBinding wrappedExpressionBinding,
24+
bool isAssignment = false)
25+
: this(
26+
expression,
27+
(IBoundExpression)null,
28+
isAssignment)
29+
{
30+
_wrappedExpressionBinding = wrappedExpressionBinding;
31+
}
32+
33+
public LetCoercionDefaultBinding(
34+
ParserRuleContext expression,
35+
IBoundExpression wrappedExpression,
36+
bool isAssignment = false)
37+
{
38+
_expression = expression;
39+
_wrappedExpression = wrappedExpression;
40+
_isAssignment = isAssignment;
41+
}
42+
43+
public IBoundExpression Resolve()
44+
{
45+
if (_wrappedExpressionBinding != null)
46+
{
47+
_wrappedExpression = _wrappedExpressionBinding.Resolve();
48+
}
49+
50+
return Resolve(_wrappedExpression, _expression, _isAssignment);
51+
}
52+
53+
private static IBoundExpression Resolve(IBoundExpression wrappedExpression, ParserRuleContext expression, bool isAssignment)
54+
{
55+
if (wrappedExpression.Classification == ExpressionClassification.ResolutionFailed)
56+
{
57+
return wrappedExpression;
58+
}
59+
60+
var wrappedDeclaration = wrappedExpression.ReferencedDeclaration;
61+
62+
if (wrappedDeclaration == null
63+
|| !wrappedDeclaration.IsObject
64+
&& !(wrappedDeclaration.IsObjectArray
65+
&& wrappedExpression is IndexExpression indexExpression
66+
&& indexExpression.IsArrayAccess))
67+
{
68+
return wrappedExpression;
69+
}
70+
71+
//The wrapped declaration is of a specific class type or Object.
72+
73+
if (wrappedExpression.Classification == ExpressionClassification.Unbound)
74+
{
75+
//This should actually not be possible since an unbound expression cannot have a referenced declaration.
76+
//Apart from this, we can only deal with the type Object.
77+
return new LetCoercionDefaultMemberAccessExpression(null, ExpressionClassification.Unbound, expression, wrappedExpression, 1, null);
78+
}
79+
80+
var asTypeName = wrappedDeclaration.AsTypeName;
81+
var asTypeDeclaration = wrappedDeclaration.AsTypeDeclaration;
82+
83+
return ResolveViaDefaultMember(wrappedExpression, asTypeName, asTypeDeclaration, expression, isAssignment);
84+
}
85+
86+
private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression)
87+
{
88+
var failedExpr = new ResolutionFailedExpression();
89+
failedExpr.AddSuccessfullyResolvedExpression(lExpression);
90+
return failedExpr;
91+
}
92+
93+
private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrappedExpression, string asTypeName, Declaration asTypeDeclaration, ParserRuleContext expression, bool isAssignment, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null)
94+
{
95+
if (Tokens.Variant.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase)
96+
|| Tokens.Object.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase))
97+
{
98+
// We cannot know the the default member in this case, so return an unbound member call.
99+
return new LetCoercionDefaultMemberAccessExpression(null, ExpressionClassification.Unbound, expression, wrappedExpression, recursionDepth, containedExpression);
100+
}
101+
102+
var defaultMember = (asTypeDeclaration as ClassModuleDeclaration)?.DefaultMember;
103+
if (defaultMember == null
104+
|| !IsPropertyGetLetFunctionProcedure(defaultMember)
105+
|| !IsPublic(defaultMember))
106+
{
107+
return CreateFailedExpression(wrappedExpression);
108+
}
109+
110+
var defaultMemberClassification = DefaultMemberClassification(defaultMember);
111+
112+
var parameters = ((IParameterizedDeclaration)defaultMember).Parameters.ToList();
113+
if (isAssignment
114+
&& defaultMember.DeclarationType == DeclarationType.PropertyLet
115+
&& IsCompatibleWithOneNonObjectParameter(parameters))
116+
{
117+
//This is a Let assignment. So, finding a Property Let with one non object paramter means we are done.
118+
return new LetCoercionDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression, recursionDepth, containedExpression);
119+
}
120+
121+
if (parameters.All(parameter => parameter.IsOptional))
122+
{
123+
if (!defaultMember.IsObject)
124+
{
125+
//We found a property Get of Function default member returning a value type.
126+
//This might also be applicable in case of an assignment, because only the Get will be assigned as default member if both Get and Let exist.
127+
return new LetCoercionDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression, recursionDepth, containedExpression);
128+
}
129+
130+
if (DEFAULT_MEMBER_RECURSION_LIMIT >= recursionDepth)
131+
{
132+
//The default member returns an object type. So, we have to recurse.
133+
return ResolveRecursiveDefaultMember(wrappedExpression, defaultMember, defaultMemberClassification, expression, isAssignment, recursionDepth, containedExpression);
134+
}
135+
}
136+
137+
return CreateFailedExpression(wrappedExpression);
138+
}
139+
140+
private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression wrappedExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ParserRuleContext expression, bool isAssignment, int recursionDepth, RecursiveDefaultMemberAccessExpression containedExpression)
141+
{
142+
var defaultMemberAsTypeName = defaultMember.AsTypeName;
143+
var defaultMemberAsTypeDeclaration = defaultMember.AsTypeDeclaration;
144+
145+
var defaultMemberExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, recursionDepth, containedExpression);
146+
147+
return ResolveViaDefaultMember(
148+
wrappedExpression,
149+
defaultMemberAsTypeName,
150+
defaultMemberAsTypeDeclaration,
151+
expression,
152+
isAssignment,
153+
recursionDepth + 1,
154+
defaultMemberExpression);
155+
}
156+
157+
private static bool IsCompatibleWithOneNonObjectParameter(IReadOnlyCollection<ParameterDeclaration> parameters)
158+
{
159+
return parameters.Count(parameter => !parameter.IsObject) == 1
160+
&& parameters.Any(parameter => !parameter.IsOptional && !parameter.IsObject)
161+
|| parameters.All(parameter => parameter.IsOptional)
162+
&& parameters.Any(parameter => !parameter.IsObject);
163+
}
164+
165+
private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration)
166+
{
167+
var declarationType = declaration.DeclarationType;
168+
return declarationType == DeclarationType.PropertyGet
169+
|| declarationType == DeclarationType.PropertyLet
170+
|| declarationType == DeclarationType.Function
171+
|| declarationType == DeclarationType.Procedure;
172+
}
173+
174+
private static bool IsPublic(Declaration declaration)
175+
{
176+
var accessibility = declaration.Accessibility;
177+
return accessibility == Accessibility.Global
178+
|| accessibility == Accessibility.Implicit
179+
|| accessibility == Accessibility.Public;
180+
}
181+
182+
private static ExpressionClassification DefaultMemberClassification(Declaration defaultMember)
183+
{
184+
if (defaultMember.DeclarationType.HasFlag(DeclarationType.Property))
185+
{
186+
return ExpressionClassification.Property;
187+
}
188+
189+
if (defaultMember.DeclarationType == DeclarationType.Procedure)
190+
{
191+
return ExpressionClassification.Subroutine;
192+
}
193+
194+
return ExpressionClassification.Function;
195+
}
196+
}
197+
}

Rubberduck.Parsing/Binding/DefaultBindingContext.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ public DefaultBindingContext(
2323
_procedurePointerBindingContext = procedurePointerBindingContext;
2424
}
2525

26-
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
26+
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion, bool isLetAssignment)
2727
{
28-
var bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext);
28+
var bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext, requiresLetCoercion, isLetAssignment);
2929
return bindingTree?.Resolve();
3030
}
3131

32-
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
32+
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false)
3333
{
34+
if (requiresLetCoercion && expression is ParserRuleContext context)
35+
{
36+
var innerExpressionBinding = BuildTree(module, parent, expression, withBlockVariable, statementContext);
37+
return new LetCoercionDefaultBinding(context, innerExpressionBinding, isLetAssignment);
38+
}
39+
3440
switch (expression)
3541
{
3642
case VBAParser.ExpressionContext expressionContext:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Antlr4.Runtime;
2+
using Rubberduck.Parsing.Symbols;
3+
4+
namespace Rubberduck.Parsing.Binding
5+
{
6+
public class LetCoercionDefaultMemberAccessExpression : BoundExpression
7+
{
8+
public LetCoercionDefaultMemberAccessExpression(
9+
Declaration referencedDeclaration,
10+
ExpressionClassification classification,
11+
ParserRuleContext context,
12+
IBoundExpression wrappedExpression,
13+
int defaultMemberRecursionDepth = 0,
14+
RecursiveDefaultMemberAccessExpression containedDefaultMemberRecursionExpression = null)
15+
: base(referencedDeclaration, classification, context)
16+
{
17+
WrappedExpression = wrappedExpression;
18+
DefaultMemberRecursionDepth = defaultMemberRecursionDepth;
19+
ContainedDefaultMemberRecursionExpression = containedDefaultMemberRecursionExpression;
20+
}
21+
22+
public IBoundExpression WrappedExpression { get; }
23+
public int DefaultMemberRecursionDepth { get; }
24+
public RecursiveDefaultMemberAccessExpression ContainedDefaultMemberRecursionExpression { get; }
25+
}
26+
}

Rubberduck.Parsing/Binding/IBindingContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Rubberduck.Parsing.Binding
55
{
66
public interface IBindingContext
77
{
8-
IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext);
9-
IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext);
8+
IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false);
9+
IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false);
1010
}
1111
}

Rubberduck.Parsing/Binding/ProcedurePointerBindingContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public ProcedurePointerBindingContext(DeclarationFinder declarationFinder)
1515
_declarationFinder = declarationFinder;
1616
}
1717

18-
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
18+
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false)
1919
{
2020
IExpressionBinding bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext);
2121
if (bindingTree != null)
@@ -25,7 +25,7 @@ public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTr
2525
return null;
2626
}
2727

28-
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
28+
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false)
2929
{
3030
switch (expression)
3131
{

Rubberduck.Parsing/Binding/TypeBindingContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ public TypeBindingContext(DeclarationFinder declarationFinder)
1515
_declarationFinder = declarationFinder;
1616
}
1717

18-
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
18+
public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false)
1919
{
2020
IExpressionBinding bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext);
2121
return bindingTree?.Resolve();
2222
}
2323

24-
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext)
24+
public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false)
2525
{
2626
switch (expression)
2727
{

Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ public override void EnterEraseStmt(VBAParser.EraseStmtContext context)
176176
_resolver.Resolve(context);
177177
}
178178

179+
public override void EnterMidStatement(VBAParser.MidStatementContext context)
180+
{
181+
_resolver.Resolve(context);
182+
}
183+
179184
public override void EnterLsetStmt(VBAParser.LsetStmtContext context)
180185
{
181186
_resolver.Resolve(context);

0 commit comments

Comments
 (0)