Search code examples
vbacomscoping

Is scoping broken in VBA?


Say you have this code in a module called Module1:

Option Explicit

Private Type TSomething
    Foo As Integer
    Bar As Integer
End Type

Public Something As TSomething

In equivalent C# code if you made the Something field public, the code would no longer compile, because of inconsistent accessibility - the type of the field being less accessible than the field itself. Which makes sense.

However in VBA you could have this code in Module2:

Sub DoSomething()
    Module1.Something.Bar = 42
    Debug.Print Module1.Something.Bar
End Sub

And you get IntelliSense while typing it, and it compiles, and it runs, and it outputs 42.

Why? How does it even work, from a COM standpoint? Is it part of the language specs?


Solution

  • As per my comment, VBA exposes a private Type, just like it exposes a Private Enum.

    VBA assumes you can make use of the TypeInfo in the consuming context, but it won't allow you to declare or create instances of those types or enums.

    This C++ answer is partly informative:

    Access Control is applied to names

    The access specifier for the name has nothing to do with it's type

    But it's perhaps useful to think of a Private Type in a standard module, as something like a "PublicNotCreatable" class. If you provide a public wrapper, then the type is accessible outside the host module.

    But VBA handles things differently when the Type is in a Public Class Module!

    Here's your Module1 expanded:

    Option Explicit
    
    Private Type TSomething
        Foo As Integer
        Bar As Integer
    End Type
    
    Public Type TOtherThing
        Foo As Integer
        Bar As Integer
    End Type
    
    Public Type TWrapperThing
      Something As TSomething
    End Type
    
    Public Something As TSomething
    Public Otherthing As TOtherThing
    Public Wrapperthing As TWrapperThing
    
    Public Function GetSomething() As TSomething
      GetSomething.Foo = 1
    End Function
    
    Public Function GetOtherthing() As TOtherThing
      GetOtherthing.Foo = 1
    End Function
    

    And Module2 expanded:

    Option Explicit
    
    Sub DoThings()
    
    'Compile Error: User-defined type not defined
      'Dim oSomething As TSomething
      Dim vSomething As Variant
      
      Dim oOtherthing As Module1.TOtherThing
      Dim vOtherthing As Variant
      Dim oWrapperthing As Module1.TWrapperThing
      
      Module1.Something.Foo = 42
      Module1.Otherthing.Foo = 42
      Module1.Wrapperthing.Something.Foo = 42
    
      'Compile Error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
      'vSomething = Module1.Something
      'vOtherthing = Module1.Otherthing
      
      oOtherthing = Module1.Otherthing
      oOtherthing.Foo = 43
      
      'Is 43 > 42?
      Debug.Assert oOtherthing.Foo > Module1.Otherthing.Foo
      
    'Compile Errors: "GetSomething" User-defined type not defined
      'Module1.GetSomething.Foo = 42
      'Module1.GetSomething().Foo = 42
      
      Module1.GetOtherthing.Foo = 42
      Module1.GetOtherthing().Foo = 42
    
    End Sub