The array is initialized:
Private pSectors(1 To 3) As clsNewSector
Its individual elements aren't assigned, but the array itself is ready for whatever you want to throw at it: it's reserving 3 spots for clsNewSector instances, at compile-time. Being an object type, the array is merely holding pointers. Under the hood, VBA arrays point to a SAFEARRAY structure that look something like this:
typedef struct tagSAFEARRAY {
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY;
The type metadata for any given element is only accessible via a series of successive pointer reads; the array structure itself is abstracting away the concept of a type of element, such that it's capable of supporting any type. This content (not mine) goes into further details.
Whether a programming language might let a Foo into an array of Bar objects is all about type safety, which . In .NET (and other techs), type safety is very extensively explored, with generics and other advanced mechanisms. In VBA, we're 25 years ago, with a compiler that defers a lot of type safety to last-minute runtime checks - this code compiles perfectly fine:
Public Sub Test()
Dim things(1 To 10) As Class1
Set things(1) = New Class2 '<~ *run-time* error 13 "type mismatch"
End Sub
Now, if there's some level of run-time type safety, surely that means the type metadata exists somewhere! And it does - but VBA does not have an extensive type system surfaced to the language, and much of its internal mechanics aren't [easily] programmatically accessible (call stack comes to mind).
In any function call, the arguments are evaluated first - so when we do TypeName(a(1)), the a(1) subscript resolves to a NULL object pointer (no reference was Set yet), so Nothing is passed to the TypeName function, which returns the string "Nothing" since VBA has pretty weird typing semantics in this area - but if we leave the type of individual elements alone for a minute and look at the data type of the array itself...
Public Sub Test()
Dim things(1 To 10) As Class1
Debug.Print TypeName(things) ' prints "Object()"
End Sub
The fact that the reported data type of our explicitly-typed array is Object() tells us everything we need to know: the real type metadata of the array itself is being abstracted away, so there is no [simple?] VBA code that can be given an array and an object variable, and determine whether the object is of the correct type for the array, unless we hard-code the expected data type and don't care for null references.
The problem we're facing here, is that the reason we need to determine a specific data type is to spawn a new object of that type - and since object references initialize to Nothing, that's the only "data type" we'll only get.
Unless we initialize the array differently.
You could make clsNewSector have a default instance (set its VB_PredeclaredId attribute value to True, or just use and synchronize Rubberduck's @PredeclaredId annotation, and then expose a property like these:
Public Property Get IsDefault() As Boolean
IsDefault = Me Is clsNewSector
End Property
And then you could initialize pSectors with pointers to the class' default instance as soon as possible:
For i = LBound(pSectors) To UBound(pSectors)
Set pSectors(i) = clsNewSector
Next
That way no subscript would default to Nothing when you pass a(1) to TypeName, and then the rest of the code is cursed with having to verify whether the instance they're looking at has IsDefault = True, otherwise we run the risk of making that global default instance stateful, and we wouldn't want that to happen (shared global state at various array indices - what could possibly go wrong?!).
Overall pretty frail, definitely not ideal.
An idiomatic solution would be to create a custom collection class per type, that gets to enforce type safety on its own terms. Could be a NewSectors class (consider dropping that cls prefix) whose Item indexed Property Set member will throw an error given Nothing or any object reference that isn't a NewSector instance.
The root of the problem is the too many apparent responsibilities of the NewObject function, which seemingly can create anything given a string with a type name.
Let's think outside the box for a minute.
Function InitializeObjectArray(a As Variant) As Variant
We use the array a itself to get the name of a type, and then we pass that name to a function that gets us the object of the type we expect.
So the function is inherently coupled with NewObject, whose name isn't telling us much about the breadth of its applicability.
This looks like a job for...
What if we moved that responsibility to a formalized interface?
@Interface IArrayItemFactory
Option Explicit
Public Function Create() As Object
End Function
Then we could have a NewSectorFactory class that implements it as follows:
Implements IArrayItemFactory
Option Explicit
Private Function IArrayItemFactory_Create() As Object
Set IArrayItemFactory_Create = New clsNewSector
End Function
Now InitializeObjectArray might look like this:
Public Function InitializeObjectArray(ByVal a As Variant, ByVal factory As IArrayItemFactory) As Variant
For i = LBound(a) To UBound(a)
Set a(i) = factory.Create
Next i
End Function
By stripping the function of the responsibility of working out the type of object to create, and moving it into its own object/dependency, the function is now easily able to initialize any array of any object type that has an implementation for the IArrayItemFactory interface, and the calling code is now responsible for telling the function what to initialize the array with, by simply being responsible for providing the implementation for the factory.