dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
288 stars 65 forks source link

Target type context does not flow into result operands of conditional 'If' expressions #97

Open AnthonyDGreen opened 7 years ago

AnthonyDGreen commented 7 years ago

Visual Basic has several "typeless" expressions whose final value depends on target type information:

Dim ns As String = Nothing
Dim i As Integer = Nothing
Dim ni As Integer? = Nothing
Dim s As Action(Of String) = AddressOf Console.WriteLine

AddressOf expressions don't have a type. Target-typing allows overload resolution to pick the correct method and instantiate a delegate referring to that method.

Dim f1 = Function() True
Dim f2 As Func(Of Boolean) = Function() True
Dim f3 As Expression(Of Func(Of Boolean)) = Function() True

In the code above the same text, Function() True, may have an anonymous delegate type <Function() As Integer>, named delegate type Func(Of Boolean), or may produce an expression tree based on target type context.

Dim arr As Byte() = {1, 2, 3, 4}

The code above does not first produce an Integer array then convert it to a Byte array; that's impossible. Instead the array literal is always realized as a Byte array and its elements are each converted to Byte.

Dim value = $"Cost {value:C02}"
Dim value

Because the conditional 'If' operator doesn't propagate target type context into its operands these expressions may be reclassified incorrectly producing programs with subtle bugs or which fail at run-time.

This makes the simple refactoring of an If block into an If expression dangerous. Additionally, each time we consider adding more target-typed expressions we compound this issue.

Examples of programs failing to compile or compiling with surprising behavior.

' Actually assigns False, not null.
Dim trueOrNull As Boolean? = If(False, True, Nothing) 

' Fails to infer common type; doesn't compile with Option Strict On.
Dim control As Control = If(isReadOnly, New Label, New TextBox)
control.Text = ...

' Fails to infer common type; doesn't compile at all.
Dim a As Action(Of String) = If(True, AddressOf Debug.WriteLine, AddressOf Trace.WriteLine)

Similar "common-type" examples can be made whenever both operands are typeless.

rskar-git commented 7 years ago

So just to be clear, if I may paraphrase this proposal:

In regards to the overload resolution process of the inline If() when either expressionIfTrue or expressionIfFalse is Nothing, the future design of the compiler's expression parsing routines should also include for consideration the data type of the destination variable. (Should that include the data type of a parameter of a Function/Sub call too?).

In the case of duck-typing or any other situation where the data type of the destination is unknown or not applicable, the rules of overload resolution then fall-back to as they currently are (i.e. data type of the destination isn't considered in the resolution process).

With this change in effect, this:

Dim x As Boolean? = If(False, 1, Nothing)

...would be equivalent to:

Dim x As Boolean? = (Function(condition As Boolean) As Boolean?
                         If condition Then
                             Return 1
                         Else
                             Return Nothing
                         End If
                     End Function)(False)

Or in more generic terms, this:

Dim v as T = If(<condition>, <expressionIfTrue>, <expressionIfFalse>)

...is equivalent to:

Dim v As T = (Function(condition As Boolean) As T
                         If condition Then
                             Return <expressionIfTrue>
                         Else
                             Return <expressionIfFalse>
                         End If
              End Function)(<condition>)

...but only if either <expressionIfTrue> or <expressionIfFalse> is Nothing.


Am I understanding it right?

AnthonyDGreen commented 7 years ago

This is not a proposal. It's a scenario. It just states that there is a problem. There is at least one proposal which I've created a stub issue for (#98) but there may be others. It's not really an overload resolution or parsing related issue. Today the type of the If expression is either the dominant type of the second and third operands or if one cannot be determined object. The entire value of the If expression is then converted to the destination type. #98 just proposes that that conversion happen sooner.

For example, If I write this today in VB:

Dim strings As String() = {1, 2, 3}

It is not the equivalent of saying:

Dim strings As String() = CType({1, 2, 3}, String())

That cast won't compile because an Integer array cannot be converted to a string array. Instead that code is the equivalent of this:

Dim strings As String() = {CStr(1), CStr(2), CStr(3)}

The conversion is pushed "inside" the array so that the actual type of the array is String(), not Integer(). Another example would be:

Dim strings As Action() = {AddressOf Console.WriteLine}

This only works because the conversion of the AddressOf happens inside of the array.

Dim strings As Action() = {CType(AddressOf Console.WriteLine, Action)}

If it worked the other way it wouldn't compile because an AddressOf has no type and therefore you can't make an array of them.

98 is the moral equivalent of the above. Instead of:

Dim x As Boolean? = If(False, 1, Nothing)

Being the equivalent of:

Dim x As Boolean? = CType(If(False, 1, CInt(Nothing)), Boolean?)

It'll be the equivalent of:

Dim x As Boolean? = If(False, CType(1, Boolean?), CType(Nothing, Boolean?))
craigajohnson commented 7 years ago

^^ above clarification is most fascinating.

AdamSpeight2008 commented 7 years ago

@AnthonyDGreen This one doesn't compile, I think it should.

Dim f As Func(Of FormattableString) = Function() If(True, $"Inherits {y}", $"")
AnthonyDGreen commented 7 years ago

@AdamSpeight2008 Brilliant example!

Bill-McC commented 7 years ago

The formattable string example isn't peculiar to If expressions. eg:

Dim f As Func(Of FormattableString) = Function() $"formattable"

will fail to compile. I'm guessing this is more to do with the IDE wanting to wrap interpolated strings and expose them as string.

AnthonyDGreen commented 7 years ago

Something terrible has happened if that doesn't compile, Bill.

AnthonyDGreen commented 7 years ago

And that's a terrible bug. \<profuse swearing>

For example this works fine:

Dim f As Func(Of FormattableString()) = Function() {$"{1:C02}"}

I'll go file a bug :(