rubberduck-vba / Rubberduck

Every programmer needs a rubberduck. COM add-in for the VBA & VB6 IDE (VBE).
https://rubberduckvba.com
GNU General Public License v3.0
1.92k stars 302 forks source link

Inspection for use of Global class without square-brackets #4164

Open ThunderFrame opened 6 years ago

ThunderFrame commented 6 years ago

The Global class is special, and despite being non-hidden in the VBA library, it is hidden in the Excel library. Despite not having a leading underscore, the VBE seems to treat any Global class as hidden, irrespective of the library attributes. Using a Global class without square-brackets in VBA code results in a lack of Intellisense, which can lead to issues around member discoverability and usage.

Instances such as: Set app = Excel.Global.Application

Should be rewritten as: Set app = Excel.[Global].Application

bclothier commented 6 years ago

I do not think the Global class is getting any special treatment. It is hidden. Here's the IDL for the Global coclass:

[
  uuid(00020812-0000-0000-C000-000000000046),
  helpcontext(0x0002c738),
  appobject,
  hidden
]
coclass Global {
    [default] interface _Global;
};

Its interface is likewise hidden:

[
  odl,
  uuid(000208D9-0000-0000-C000-000000000046),
  helpcontext(0x0002c738),
  hidden,
  dual,
  nonextensible,
  oleautomation
]
interface _Global : IDispatch {
...
}

Note that it's also nonextensible which means we can know that if an identifier doesn't exist in its list, it can't be called upon on the Global.

In contrast, VBA's Global has those following IDL definition:

[
  uuid(FCFB3D23-A0FA-1068-A738-08002B3371B5),
  helpcontext(0x0010fcce),
  appobject
]
coclass Global {
    [default] interface VBEGlobal;
};

with the interface:

[
  odl,
  uuid(2C3F47C0-7132-11CF-941E-00AA00A74CD0)
]
interface VBEGlobal : IUnknown {
    [helpcontext(0x0010cb88)]
    HRESULT _stdcall Load([in] IDispatch* Object);
    [helpcontext(0x0010cb8c)]
    HRESULT _stdcall Unload([in] IDispatch* Object);
    [propget, helpcontext(0x0010de55)]
    HRESULT _stdcall UserForms([out, retval] IDispatch** pdispRetVal);
};

So, no, I don't think Excel's Global is special. VBE is simply respecting the attributes that is defined on the class. Thus the behavior with the lack of intellisense would be general to any hidden coclasses.

Same behavior can be observed with DAO.PrivDBEngine which is similarly hidden but otherwise usable in VBA. Furthermore, you do get intellisense if you use variable:

Excel.Global.<No intellisense here>

Dim g As Excel.Global
g.<Intellisense works here>

NB: Note that there are 3 attributes that affects the visibility & usability. The hidden and control merely makes an object non-visible but does not prevent it from being used within VBA. The restricted, OTOH, actively blocks any use of it. (RD's Extension and DockableToolWindow classes both get decorated with the restricted attribute precisely because they are not safe for VBA's consumption)

hidden attribute control attribute restricted attribute

ThunderFrame commented 6 years ago

The Global in the VBA library is not marked as hidden, but you must still use [Global].UserForms instead of Global.UserForms. Whether it's VBA, or Excel or some other host, it seems the square brackets are necessary for Intellisense to work.

bclothier commented 6 years ago

I see what you mean. But I wonder if that's simply because of name collisions?

VBA.Global.<ctrl + space> or Global.<ctrl + space> gets me the intellisense for an union of all Global classes. Remember that Excel.Global is nonextensible, right? Userforms, Load and Unload are not member of the Excel.Global, yet it will appear in the list.

Add Word reference to the project, and the same intellisense now lists all members from VBA.Global, Excel.Global, and Word.Global.

But as noted before, if we declare a variable, VBE knows what to do for its intellisense member.

Dim e As Excel.Global
e.<intellisense works>

Yet, if we try:

Word.Global.<ctrl + space>

we still get the union. It's as if the VBE is not respecting the disambiguation, and the bracketing helps to override that.

A test would be to create two type libraries, each with Foo as an appobject and load them up and see if we get the same behavior w/ the intellisense for the Foo. If so, then it's a bug with VBE that we need to work around. If not, then I'll agree that Global is getting special treatment.