Description
Justification
Right now when the resolver encounters a member it can't find a Declaration
for, it logs a binding failure and attempts to resolve known identifiers in the rest of the expression. This makes late-bound member calls unresolvable:
Dim foo As Range
Set foo = ActiveWorkbook.Worksheets("Sheet1").Range(cellAddress)
Here Range
is a late-bound member call made against Object
, but assuming cellAddress
is some local variable, that's all Rubberduck will be seeing to the right of the Worksheets
member call.
Note: this is a discussion, not a spec.
Description
Similar to how we create on-the-fly declarations for undeclared variables, we could be creating on-the-fly declarations for late-bound or otherwise unknown members. Rather than adding a new flag to the already-overcrowded Declaration
class, let's broaden the usage of the IsUndeclared
property, which we already use for undeclared variables.
I suggest we create such a declaration whenever we encounter a member we can't resolve:
Dim foo As Class1 ' a known class module
Set foo = New Class1
foo.Bar = 42 ' member "Bar" isn't found in module "Class1" (won't compile)
So here we would be adding an "undeclared" member Bar
under Class1
, and as a first iteration its data type could be Variant
, and then that can be enhanced later to infer a data type from usage.
Another one:
Dim foo As SomeClass ' non-existing class module
Set foo = New SomeClass ' won't compile
foo.DoSomething 42
Here we would be adding an "undeclared" SomeClass
class, a DoSomething
procedure (Function
would be the easiest for a first iteration), and one Variant
(or as inferred from usage) parameter declaration under it.
And in the case of late-bound code:
Dim outlookApp As Object
Set outlookApp = CreateObject("Outlook.Application")
outlookApp.Quit
Here we would create a class declaration with a Quit
method. The name of the bogus class could be outlookAppClass
(based on the identifier for the object of that type), and the parent project would have to be the containing project.
When the late-bound member is against a known member, the parent project needs to be whatever project/library declares the deepest known member:
Dim foo As Range
Set foo = ActiveWorkbook.Worksheets("Sheet1").Range(cellAddress)
So here we would create an undeclared WorksheetsClass
declaration under the Excel
library, and an undeclared Range
function under it, taking a single parameter.
In a dream world, we would have the resolver take note of how an Object
-returning member is used, e.g.:
Dim ws As Worksheet
Set ws = ActiveWorkbook.Worksheets(sheetName)
Dim wss As Sheets
Set wss = ActiveWorkbook.Worksheets(Array(sheetName1, sheetName2))
And then annotate the WorksheetsClass
undeclared type with a resolver hint that says "this type can return a Worksheet
or Sheets
object" -- and then pipe late-bound resolution through these hints; and then in the case of code like this:
Worksheets("Sheet1").Range("A1") = 42
...we could successfully resolve the Range
member call to the Excel.Worksheet.Range
declaration, with a new IdentifierReference.IsLateBound
flag turned on.
One significant enhancement this would enable in the future, would be intellisense for late-bound member calls and not-yet-created types, a bit like R# does: we could then have a quickfix (?) that says "Generate class" and/or "Generate member".
This is very much a gigantic can of worms with tons of possibly unintended consequences, both good and bad. Thoughts? Other ideas? Let's think of what the consequences are, and decide whether we can/should go down this path.