dotnet / dotnet/vblang

[Discussion] Loud (Non-Silent) Breaking Changes, e.g. New Keywords

Open
#312 6 comments 0 reactions 0 assignees View on GitHub
Discussion
Dominant language
No language data
Stars
328
Forks
71
PR merge metrics
No merged PRs in 30d

Description

I'm just posting this for discussion. But I've been giving a lot of thought the how we handled the breaking change of making `NameOf` a reserved keyword in VB14. It's certainly not the first version of VB to introduce new reserved keywords:

* In VB2005 all of the new non-CLS-compliant types (`SByte`, `UShort`, `UInteger`, `ULong`) became reserved keywords, potentially changing the meaning of (unlikely) code where these identifiers were used for types.
* All of the corresponding conversion operators (`CSByte`, `CUShort`, `CUInt`, `CLng`) were also made reserved keywords, potentially changing the meaning of existing callsites.
* In VB2008 the `GetXmlNamespace` keyword was added.

But, in VB2012 we decided to make `Await` contextual because of heightened concerns about an in-place update to the compilers changing the meaning of code, potentially failing to compile, on a live running dynamically compiled ASP.NET web app.

Oddly enough, in that release both VB and C# took a silent* break to the way For Each variables are captured by lambda and query expressions. In VB it wasn't entirely silent since earlier versions of the compiler always warned that the previous behavior was busted and likely to change in a future release.

In VB2015 we considered introducing the approach C# uses for `var` and `dynamic` to VB for the `NameOf` feature. For those unfamiliar, in C# `var` and `dynamic` only take on their special meaning in C# if not types exist with those names already. Likewise, `nameof(expression)` only works to get the name of the provided symbol if no method named `nameof` exists. We decided against it based on a number of things including MVP feedback (\*cough\* Kathleen \*cough\*), VBs prior history of adding new keywords, and the downstream cost of such a design to tools authors like the IDE team and analyzer writers. The amount of life wasted for the edge case of `var` binding to a type is ... considerable.

The problem with making the keyword semantically contextual, like C#, is that in a context where that symbol has been defined new features become unusable. We've seen projects where someone has defined a type named `var` in the global namespace to block all use of type inference in that project (or any project that references it). If a method named `NameOf` is in scope, in some cases there's no way to get it out of scope and the `NameOf` feature would be unusable in VB in those places.

I don't regret the choice to go all-in on the keyword but I have been playing around with alternative approaches for the future. One of which is to turn the C# approach on its head. Instead of looking for an existing method named `NameOf` and only exposing new functionality if that fails, we could prioritize the new behavior but report a warning in the very rare case that the meaning of code changes. By reporting a warning we give the IDE a change to provide a quick-fix ("Fix All In Solution") which can update all occurrences in a solution at once to make the code back-compatible (by escaping the keyword). This avoids the _silent breaking change_ problem and ensures that code which is being round-tripped between two versions of Visual Studio will mean the same thing in both versions, while not forever encumbering future code and future generations of users with the trivia legacy that "`var` wasn't always a keyword, you know".

This approach could be used in some other cases where the community has debated adding new keywords like `Null` and `.Me` (#31) or even to fix some places where the compiler is just broken. e.g. type inference doesn't work with `Static` locals. No language reason for that (I suspect it's an implementation detail/constraint). Instead of having this wort on the language forever:

``` VB.NET
' Type of l is actually 'Object' because static locals
' don't care about your Option Infer settings.
Static l = New C
l.Foo()
```

We could fix the bug and on upgrade users would get a clean message in the error list:

"Semantic change: Due to a bug in previous versions the type of 'l' was 'Object'. Due to late-binding you might not have noticed this. To change this code to match its old behavior in both current and previous versions of VB, hit Ctrl+." and the fix turns it into: `Static l As Object = New C`. After fixing the code you could just silence the warning for the project and start writing code "the new way".

I'm not saying this is a perfect solution or that it should be used all the time. Just that it could be a good intermediate lever between `Option` statements and contextual keywords that strikes a good balance between discover-ability and providing the cleanest semantics for first-time users out of the box.

What do you guys think?

Would you be ok moving to a new version of VB if you were told up-front if any code changed meaning with a one-click fix to restore the old behavior? Are there any proposed language changes you could see taking advantage of such a mechanism? Where would you draw the line?

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.