Why are missing `Set` for certain types a runtime error rather than compile error? - vba

Given the following VBA code, assuming that a Something is just a VBA class module....
Public Type Foo
SomeThing As Something
End Type
Public Sub TestFoo()
Dim x As Foo
With x
'Correct way to do it
Set .someThing = New Something
End With
With x
'This is wrong but realized only as a RTE
'438: Object doesn't support this property or method
.SomeThing = New Something
End With
End Sub
In contrast, if you change the type to something like VBA.Collection as below:
Public Type Foo
SomeThing As VBA.Collection
End Type
Public Sub TestFoo()
Dim x As Foo
With x
.SomeThing = New VBA.Collection
End With
End Sub
This is now a compile error, with Argument Not Optional. This is obviously wrong but why is it a compile-time error only with VBA.Collection?

This is explained in the VBA Language Specification. The semantics of the assignment inside the With block is driven by whether or not the statement is a Set statement or a Let statement. In this context, it is a Let statement:
With x
.SomeThing = New Something
End With
Note that in the VBA grammar, the keyword Let is optional (obsolete):
let-statement = ["Let"] l-expression "=" expression
In a Set statement, the Set keyword is required:
set-statement = "Set" l-expression "=" expression
Inside of a With block, the l-expression is basically the UDT member, although the exact same behavior applies if x is used directly.
When evaluating a Let expression, the semantics are described in section 5.4.3.8:
Static Semantics.
This statement is invalid if any of the following is true:
<expression> cannot be evaluated to a simple data value (section 5.6.2.2 ).
Following that to 5.6.2.2 (Evaluation to a simple data value), the following runtime semantics apply (applicable rule only):
Runtime semantics.
At runtime, the simple data value’s value and value type are
determined based on the classification of the expression, as follows:
If the expression’s value type is a specific class:
If the source object has a public default Property Get or a public
default function, and this default member’s parameter list is
compatible with an argument list containing 0 parameters, the simple
data value’s value is the result of evaluating this default member as
a simple data value.
Otherwise, if the source object does not have a public default
Property Get or a public default function, runtime error 438 (Object
doesn’t support this property or method) is raised.
Thus the runtime error 438 for SomeThing As Something.
With the Collection, the Let static semantics still apply, but it fails the static semantics of 5.6.2.2 (which gives the compile error). Again, the preceding inapplicable semantics are omitted:
Static semantics. The following types of expressions can be evaluated to produce a simple data value:
An expression classified as a value expression may be evaluated as a simple data value based on the following rules:
If the declared type of the expression is a specific class:
If this class has a public default Property Get or function and this default member’s parameter list is compatible with an argument
list containing 0 parameters, simple data value evaluation restarts as
if this default member was the expression.
The default member of Collection is a function (.Item) that takes a single parameter Index. In this code, the parameter is not provided, so the argument list is incompatible:
With x
.SomeThing = New VBA.Collection
End With
Thus the Argument Not Optional compile error.

In typing up the question, it struck me that the VBA.Collection had a default member. Thus, the compiler was interpreting the .Something = New VBA.Collection as an assignment to the default member... except that the Item is an indexed property. That explains why we get Argument not optional which would be quite a strange error to get with a Set statement.
In contrast, a VBA class module might not have a default member at all, so that there is no indexed property + default member to trigger a compile-time error. However, it also means that the bad syntax won't be caught until runtime.

Related

Using statement, As vs =

Is there a difference between using 'As' keyword and the '=' operator in vb.net?
Example:
Using aThing As New Thing()
...
End Using
' OR
Using aThing = New Thing()
...
End Using
There will be no effective difference if you have Option Infer On. If you have Option Infer Off then the first snippet will always result in a variable of type Thing while the second snippet will fail to compile with Option Strict On and result in a variable of type Object with Option Strict Off.
The first code snippet is explicit in its typing of the variable so it will be the type you specify regardless of what settings you have for Option Strict and Option Infer. The second code snippet is not explicit about the type so that type must be determined implicitly by the compiler. With Option Infer On, the type Thing can be inferred from the initialising statement. With Option Infer Off, the type will default to Object and late-binding must be used, which is not allowed with Option Strict On.
It's worth noting that your original question isn't really valid because it's actually not a case of using As or =. This:
Using aThing As New Thing()
is actually just a shorthand for this:
Using aThing As Thing = New Thing()
so you're actually using = either way and the choice is just whether or not to provide an As clause. An As clause is required with Option Strict On unless you also have Option Infer On and the type can be inferred from the initialising statement. If there is no initialising statement or the type of that statement is different to the type you want the variable to be then an As clause is required to tell the compiler the type of the variable that it cannot infer for itself.

A Roslyn bug? On non-shared member, I'm getting error that I'm using 'shared member initializer'

Have the following trivial code:
Class A
Private value As Integer = 1
Sub Action(Optional param1 As Integer = value)
End Sub
End Class
Visual Studio complains about default value (value) with error BC30369:
Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class.
Is this really the right error for this case? The method is not shared.
In Visual Studio 2012 or 2013, the error in the same case is
Constant expression is required.
what absolutely makes sense.
After additional research I think that there is a problem in order of checks made by compiler.
If I change the code, making value member Shared, I get correct result: Error BC30059
Constant expression is required.
Since nothing except constants can be placed into default value of Optional clause, check for the above BC30059 ("Constant expression is required.") should be obviously made "earlier" than the check for BC30369 (shown in question).
I created bug report at Microsoft Connect.

Why doesn't VB.Net type inference work in class fields?

If I were to type the following into a method body:
Dim myInt = 1
the Visual Studio IDE (and therefore, I am guessing, the compiler) infers the type of myInt to be Integer.
EDIT
Apparently using a literal was a bad choice here, since I've become embroiled in a lengthy debate that has nothing to do with the question. If you take issue with the fact that the expression 1 might be interpreted as an instance of different numeric types, pretend I had written:
Dim myInstance = New MyClass()
END EDIT
However, when I put a field declaration with the exact same code at the top of a class, the type of myList is not inferred:
Public Class Foo
Dim myInt = 1
End Class
On mouseover, it mentions the absence of an As clause, and says a type of Object has been assumed. I cannot pass myInt as an argument to a function or sub that expects an Integer argument, without explicitly adding an As clause or casting to Integer.
Is there a discrepancy between how the IDE and compiler deal with type inference? If, on the other hand, the compiler can't infer type in this situation either, why the discrepancy between method variables and class fields?
What you've found is that way on purpose. here is the MSDN expalanation.
Local type inference applies at procedure level. It cannot be used to
declare variables at module level (within a class, structure, module,
or interface but not within a procedure or block). If num2 in the
previous example were a field of a class instead of a local variable
in a procedure, the declaration would cause an error with Option
Strict on, and would classify num2 as an Object with Option Strict
off. Similarly, local type inference does not apply to procedure level
variables declared as Static.

Setting a variable to null value in inline if statement

I'm trying to use the following code to check for a DBNull and set the variable to nothing if it is, or a short if it isn't. The problem is it is failing to set the variable to Nothing and sets it to a 0 instead. Anybody know why?
variable = If(currentRow.Item("variable") Is DBNull.Value,
Nothing, CShort(currentRow.Item("variable")))
If variable is declared As Short? then the code works with a slight tweak: you need to cast either operand of If to the target type first:
variable = If(condition, CType(Nothing, Short?), CShort(…))
(You could also have cast the third operand instead, or both.)
This cast is necessary because of how If deduces types: if the two result types mismatch, a common type is deduced which is the closest parent type, i.e. a type from which both inherit. However, with Nothing, new rules come into play because as far as VB is concerned, Nothing is already a valid Short – a default-initialised one (see old answer below for explanation). So VB doesn’t try any type coercion, it simply uses Short as the return value.
Old answer below, assuming that OP had declared variable As Short:
You cannot set value types to Nothing. If you assign Nothing to a value type then it will be set to its type’s default value instead – which is 0 for Short.
You can test this easily:
Dim s as Short = Nothing
Console.WriteLine(s)
Setting a value type to Nothing is the same as invoking its default constructor (New Short()) or declaring a new variable of that type without initialising it. The corresponding operation in C# would be to assign default(T) (short s = default(short)).
If you want to represent null value types, you have to use nullable types:
Dim s as Short? = Nothing
Now s is of type Nullable<Short> (Short? is a shortcut of that) and can be assigned a proper Nothing.

How do I use Thread.VolatileWrite with reference types with Option Strict On?

Wrapping the argument in CObj or DirectCast shuts the compiler up, but the value is still not written.
Option Strict On
Imports System.Threading
Module Module1
Dim str As String
Sub Main()
Thread.VolatileWrite(str, "HELLO") ' Compiler error.
Thread.VolatileWrite(CObj(str), "HELLO") ' Fails silently.
Thread.VolatileWrite(DirectCast(str), "HELLO") ' Fails silently.
Console.WriteLine(str)
End Sub
End Module
There is no overload of Thread.VolatileWrite which takes a String argument. The only reference type supported is Object.
Because VolatileWrite is updating the variable str and Option Strict is On the compiler complains because in theory VolatileWrite could attempt to write a value to that variable which is not of type String (the compiler only sees that it might write any Object). In fact, as the VolatileWrite method also only takes a String you could write code which would attempt to do this. It would fail for reasons beyond the scope of this question.
When you wrap the expression in a COjb/CType/DirectCast expression (really anything with parenthesis) then the variable is no longer considered a variable but a value - it's treated the same way as if you'd just type a string literal there. Since values don't have storage locations the ByRefness of VolatileWrite is ignored which means it no longer writes which means it can no longer write a bad value which means the compiler doesn't need to warn anymore.
To get the behavior you want with a string type variable use the System.Threading.Thread.MemoryBarrier method before your writes and after your reads. See this thread for additional information: How do I specify the equivalent of volatile in VB.net?