Inferred Type- and Method-Declarations ("Top-Level Code")
- Dominant language
- No language data
- Stars
- 328
- Forks
- 71
- PR merge metrics
- No merged PRs in 30d
Description
This proposal is a follow-up to the idea first introduced in #155 and #102
**The goal** of the proposal is to provide a new tool in VB.NET through which VB **abstraction authors**, e.g. library authors, educators/documentation writers, senior-developers/project-leads can create powerful yet streamlined experiences for end-developers requiring **minimal or no boilerplate** (ceremony) beyond use-case specific code. By removing the requirement for explicit type declarations, method declarations, and the like abstraction consumers (end-developers) can focus their attention on precisely the code that matters making those scenarios more approachable, more productive, and/or simply more enjoyable.
Here's a simple example of the syntax, with motivating scenarios to follow. The code depicted represents the *entire* code content of the program; nothing has been omitted.
**Example 1 - Simple program**
``` VB.NET
Console.Clear()
Console.Write("Enter your name: ")
Dim name = Console.ReadLine()
Console.WriteLine("Hello, " & name)
```
> **A note on "VB Interactive", "script" files (`.vbx`), and the scripting engine**
> There have been prior proposals from circa 2012 for supporting top-level code related to the VB Interactive window, `.vbx` (script) files, and the Roslyn-based scripting engine. This proposal is unrelated to those proposals and the prototype does not build off of them. The key difference between what's proposed here and those proposals is that this is not a separate scripting dialect of VB.NET. It doesn't require a different extension or parsing options. It's just an extension to vanilla VB. In several other places the designs diverge, e.g. as there are no scripts, there are no "script locals"--there are fields, which can be referenced by top-level methods and plain locals, which can't--and all of the top-level code in a project isn't stitched together implicitly to form one large method. I was not involved in the majority of the design for the proposed `.vbx` dialect, which happened ~2011 and this proposal reflects my current thinking on a lighter-weight alternative. I do not believe that integrating top-level code directly into the language prohibits application scripting or interactive at a later time.
# Motivating Scenarios
The above shows syntax but the feature is motivated far beyond "Hello, World!" programs. Using a [prototype](https://github.com/AnthonyDGreen/roslyn/tree/prototypes/vb-top-level) I have constructed **5** example scenarios and recorded a series of videos demonstrating them (~5-minutes each) showing a breadth of applications from educational to professional and from traditional desktop applications to the web. Each video has a companion blog post which expands on the scenario depicted and why it matters. They are as follows:
* **Scenario A:** The Basics (Console) [3:29]
[](https://youtu.be/aJTX5SYu2GY)
[Companion post](https://anthonydgreen.net/2019/04/23/top-level-code-scenario-a/)
* **Scenario B:** Python Notebooks/Xamarin Workbooks/Try.NET-style Rich text document intermixed with code [5:53]
[](https://youtu.be/75QBryAPZpE)
[Companion post](https://anthonydgreen.net/2019/04/24/top-level-code-scenario-b/)
* **Scenario C:** An animated game-loop (Windows desktop) [5:07]
[](https://youtu.be/cGA8fB8P9WA)
[Companion post](https://anthonydgreen.net/2019/04/30/top-level-code-scenario-c/)
* **Scenario D:** An ASPX/Razor-like ASP.NET Core-based "View Engine" (Web) [5:17]
[](https://youtu.be/QS0IXfHp2Gg)
[Companion post](https://anthonydgreen.net/2019/05/03/top-level-code-scenario-d/)
* **Scenario E:** A WYSIWYG ASP.NET Core REST service (Web) [5:16]
[](https://youtu.be/4sTH_YfeP7s)
[Companion post](https://anthonydgreen.net/2019/05/23/top-level-code-scenario-e/)
The first four are built solely using components of the proposal while the last combines it with the [JSON Literals proposal](https://github.com/dotnet/vblang/issues/101). This is not an exhaustive list and other potential examples not explored include (but are not limited to):
* A LINQ-pad-style/SQL results view
* Async "Main" w/ different schedulers
* Azure Functions?
# I. Inferred Type Declarations ("Top-Level Declarations")
The proposed feature would allow type member declarations (methods, properties, fields, etc.) as well as `Inherits` and `Implements` statements to appear directly in a compilation-unit (source file) without an explicit enclosing type declaration (class, module, etc). In such case a type declaration will be inferred, having a name inferred from the (file)name of the containing source file.
Any member declaration at the compilation-unit level would be declared as a child of this inferred type declaration and any `Inherits` or `Implements` statement would be considered to specify the base type and/or implemented interfaces of this type. In all other respects this is an ordinary VB type indistinguishable from one with an explicit declaration **and may be combined with other partial definitions!**
**Example - VB6-style "classless" code-behinds**
``` VB.NET
Sub Button1_Click()
End Sub
Sub TextBox1_TextChanged()
End Sub
```
## Design Issues
### If all top-level members in the file become members of the inferred type declaration, does that mean that top-level type declarations are now implicitly nested once a top-level `Sub` is added?
**Example - CodeSnippet.vb**
``` VB.NET
' Is this Global.SomeUtilityThing or CodeSnippet.SomeUtilityThing?
Class SomeUtilityThing
End Class
Shared Sub Main()
Console.WriteLine(New SomeUtilityThing)
End Sub
```
> **ADG**: **I recommend nesting**. Given the idea of self-contained documentation snippets I'm kinda inclined to prefer the nested scope. It's very much the same as if all those members were just dropped into a `Module`. If you wanted to "escape" that behavior the way to do it would be to either move the type definition to another file or potentially be to wrap it in an explicit namespace declaration. If the decision goes the other way forcing nesting would be much harder to express: moving the type to another file AND wrapping it in a partial class.
### What about Modules? They can't be nested today so if a module appears in an inferred type declaration what happens?
**Options**
1. The module obviously can't be nested so it just have it fall out.
2. Make it an error.
3. Lift the restriction on modules being nested.
> **ADG**: **My preferences on risk/cost are 2, 3, 1**. If it's an error now it's consistent with the existing language and **3** can still happen later. **3** is interesting and there's probably a proposal for it somewhere because it would allow localized extension methods. **Option 1** is the worst because it either precludes **3** from happening later or if **3** does happen later the behavior still can't be changed and is now inconsistent with the decision of the previous issue.
### What if a namespace appears in a file with an inferred type declaration?
**Options**
1. The namespace obviously can't be nested so it just have it fall out.
2. Make it an error.
3. Invent class "inner namespaces".
> **ADG**: I really like the idea that a single file can be as self-contained as desired. If you consider **Scenario B** that kind of documentation is broken if it requires an namespace for some reason, and namespaces are disallowed. **I prefer Option 1**. Unlike in the previous issue I think 3 will likely never happen.
### How do you parse top-level fields vs top-level local declarations?
**It's a field if has field modifier, local otherwise, `Dim` and `Const` prefer local.**
Note that this still allows you to declare type-level constants, you just have to explicitly add a class-level only modifier like `Public`.
> **ADG**: The native compiler actually parsed local and field declarations the same way with a single type representing both. When the Roslyn VB parser was written I made the decision to have two different types for the two concepts even though structurally they are 100% identical. This briefly created a problem because after parsing a method or property header it's unknown whether the declaration was one-line like an auto-prop or `MustOverride` member and the next `Const` is a "field" or if you're in the body now and that should be parsed as a local. David (Schach) just made the parser more context aware here around `MustOverride` members. In the case of properties there is no keyword to signal user intent but we decided that since a local declaration would still be illegal *even* in an expanded property that it was fine to always parse it as a field declaration.
> My point is, there's nothing new here. It would mean not re-using the `ReadOnly` modifier for read-only locals if that ever happens in VB (which I kinda hope it won't). But if you want to defend against that just require `ReadOnly` fields in inferred type declarations to also use another unambiguous modifier like `Shared` or `Private`.
### Should it always be a class?
**Almost certainly**
I went with a class because of the inheritance **Scenarios C, D, & E**. But someone noted it does produce awkwardness if you're not doing any inheritance because you have to explicitly mark things as `Shared` to be able to call them from other places in your program or to just explicitly define `Main`. What's most important for the "no ceremony" thing is that the top-level code and the top-level members both be either instance or `Shared` so they can interact without qualification.
Could say the inferred type declaration has no kind so that it could be a partial part to a structure/module/etc (maybe).
> **ADG**: I don't think there's any scenario for an inferred type declaration becoming an `Interface`, `Enum`, or `Delegate`, and the `Structure` scenario is **very** weak. But the module case is arguable. One could go with the design that in the absence of either an explicit base class (or project default) that the code should just be a `Module`.
> This isn't necessary for an inferred type declaration to be the start-up type in a project so the only reason to do it is if it's anticipated/desired for inferred type declarations to be the way most modules are declared and a lot of them have a magic `Shared` `Execute` method on them for unknown reasons. **I don't think the inferred module scenario is as valuable as it seems at first in the long run and it's nothing compared to the inferred class scenarios.**
### Can I define extension methods in an inferred type?
**No special rules here. If it's a class, then no, until such time as extension methods are allowed in classes.**
### Inferred type attributes?
**See "VI. `Type` and `Method` Attribute Target" below.**
### Inferred type modifiers?
In VB types are `Friend` by default, heritable, and `Partial` isn't required on *every* definition. How will that effect the utility of this feature?
Another way of putting the question is, should making the type `Public` sacrifice all of the brevity of the feature at large? I tend to think no. In fact, in **Scenario E** I ran into an issue where my inferred controller wasn't being recognized by MVC routing because it wasn't `Public`. It was straightforward to work-around it by amending the routing rules to find non-public controllers but there could be other scenarios where this doesn't work. WinRT as I recall requires types to be `NotInheritable`, for example.
One pretty egregious work-around is to simply say that the inferred type declaration is like a partial declaration with no modifier. This means if you want to override the behavior you just declare a partial class elsewhere with the modifiers (and attributes) you need.
Another solution is some alternate syntax for specifying modifiers such as a pseudo-attribute:
``` VB.NET
' All of my logic goes here
```
> **ADG**: I don't hate this idea but I need to stare at it for a long time.
The last option, which gets more compelling by the end of the document is described in **"VII. A Magic Attribute That Lets The Abstract Author Prescribe Various Things"**
### How is the inferred type name inferred?
The compiler starts out with a full path. Any algorithm can be decided from that and it's a matter of deciding which parts to care about and which parts not to care about. But consider a few things:
1. Most files will have a `.vb` extension
2. Eventually someone may decide that `.vbxhtml`, or `.vbdoc` or whatever is an extension that helps VS out even if the compiler processes it normally (the compiler does not care about extensions).
3. It may be the case, similar to **Scenario D** and **Scenario E**, that there's a future where these inferred method declarations are where markup is mostly kept and there are still code-behind files. In which case they may be named `MainWindow.designer.vb` or `MainPage.markup.vb`.
4. Some people use dotted names as a convention for nested types, `MyCollection.Enumerator.vb`.
5. Filenames may not be valid identifiers for various reasons.
6. Source files can be linked (e.g. Shared Projects) and so its path may have no relation to its place in the current project.
With all that said what I prototyped is simple, take the path, get the filename, split it on dots, take the first part. Nothing clever to try and figure out a namespace from the folder structure, no inferring a nested type from dots, no special treatment of the `.vb` extension. If the name isn't a valid identifier it can either be an error or the compiler can do substitutions within reason like spaces with underscores or non-id characters with their unicode value or something. I'm open to suggestions.
### Can there by multiple partial inferred type declarations per type?
**Sure!**
Actually, there's just nothing to stop this. If the name is inferred from the path but without using every bit of the filename then both `Index.vb` and `Index.markup.vb` would infer the same name and be considered two parts to the same class. That's nothing special, it just "falls out" of how types are built. What *will* fail is if both parts define members with the same name/signature (like with explicit type declarations). This means that only one file gets to have an inferred method declaration since, as currently proposed they would both declare a method with the same name and signature. But as long as it's just non-conflicting top-level member declarations explicit top-level member declarations in one file and an inferred method declaration in another there's no problem here.
### Can an `Inherits` or `Implements` statement appear anywhere in the file or must it be at the top?
As prototyped, this is not a requirement and it's not technically problematic or expensive to go either way. It does make it harder to locate *the* `Inherits` or `Implements` statement for API consumers, including the IDE.
Given that inheritance affects name lookup it makes sense for the `Inherits` statement to precede any members or executable code. But if I did define any types in a separate namespace I could easily see myself putting such declarations before the `Inherits` statement or waaaaaaaaaaaaaaaay at the bottom of the file:
``` VB.NET
Namespace Global.MyApp.Utilities
Class StringUtils : End Class
End Namespace
Inherits StringProcessor
Return StringUtils.M(arg)
```
Maybe we split the difference, a compilation-unit becomes:
* Options
* Imports
* Attributes
* Members
* Inherits
* Implements
* Top-Level Members and Code
> **ADG**: I feel like "at the top" is best but I don't have a strong case on whether that should be a rule or a style thing. I would prefer `CompilationUnitSyntax` to have explicit properties for these. Because of **Scenario B** I'm inclined to let the needs of the documentation writer dictate what goes where, though obviously that argument could as easily be made for `Imports` and `Option` statements so...
# II. Inferred Method Declarations ("Top-Level (Executable) Statements")
The proposal feature would allow executable statements (code which traditionally may only appear within method/accessor bodies) to appear directly in a compilation-unit (source file) without an explicit enclosing method declaration. In such case a method declaration will be inferred. **Note** that this inferred method declaration still constitutes a member declaration and as such its existence may itself cause an inferred type declaration. It is **NOT** proposed to allow executable statements to appear directly within a *explicit* type declaration.
The base class of the containing type of such a declaration may designate a particular `Overridable` method to be overridden by this method, in which case the inferred method declaration will infer the same name, signature, and accessibility as the method being overridden. Otherwise the name, signature, and accessibility shall be some set of defaults to be determined.
## Design Issues
### Can top-level members reference top-level (non-field) locals?
**No**
``` VB.NET
Dim startTime = Date.Now
Sub M()
' ERROR: 'startTime' not defined.
Console.WriteLine(startTime)
End Sub
```
Fields are fields and locals are locals.
### What about Field/Property initializer execution order when interleaved with executable code?
**Field and Property initializers execute normally (during construction) rather than when any inferred method declaration is executed**
We *could* rewrite field initializers to execute inside of inferred method declarations so that this code works.
``` VB.NET
Imports System.Console
Write("Enter a number: ")
' This statement will run when the object is created, not after the previous statement runs.
Private response As String = ReadLine()
WriteLine("You've entered " & response)
```
But why? This code could have just as easily used a local and gotten the correct behavior. If we change the behavior of fields it weakens their utility for the use cases they are appropriate for. **Scenario C** makes use of a lot of fields and properties that would be broken by this idea.
> **ADG**: Today conventionally we put fields and properties at the top of the declaration rather than intermixing them with methods in part to signify that they're special. I think this is more of a style thing.
### Does the inferred method have the `Async` and/or `Iterator` modifiers?
**Syntactically `Await` and `Yield` are always keywords inside of an inferred method though semantically they may be invalid.**
This is completely new code so there's no reason for `Await` to be an identifier. `Yield` is arguable for finance apps but for now it's the same way. The question isn't whether those parse as keywords, they always will, but rather whether it's legal to use those keywords inside such methods.
VB does not at this time support `Async Sub Main`. In part I initially thought of this proposal as a possible alternative to making approaching async easier. While this proposal would do so I don't think it necessarily precludes supporting a proper `Async Sub Main`. Certainly, the documentation scenario says there will be cases where `Await` is valuable in these methods. **Scenarios B, D, and E** all *could* have used `Await`.
In the case that the inferred method overrides a base method this is easy enough. The method just has to return the correct type:
If base method returns...
* `Task` or `Task(Of T)`, method is an `Async Function` and `Await` is legal.
* `IEnumerable`/`IEnumerator`/`IEnumerable(Of T)`/`IEnumerator(Of T)` (etc), method is an `Iterator` method and `Yield` is legal.
* Other future special-type like `IAsyncEnumerator`, `ValueTask(Of T)` (etc), decide appropriately
* Void, just a `Sub` but **NEVER** an `Async Sub`
Alternately, see **"VII. A Magic Attribute That Lets The Abstract Author Prescribe Various Things"**.
In the case that there is no base method there are a few options:
1. Always generate an `` and suppress diagnostics if the function doesn't return a value or fails to use `Await`.
2. Infer the signature based on the content of the method.
3. Always generate a synchronous `Sub` (or ``).
**Option 1** gives good flexibility in most cases because whoever calls this code can use or not use the return value if any and/or wait for the method to execute synchronously.
**Option 2** is the most flexible but is problematic since the method signature, specifically its return type can change based on its content. That's unprecedented in the compiler and I'm not enthusiastic about breaking new ground here.
**Option 3** is the simplest and means in the very simple case, a simple program won't be able to use `Await` which is important when learning about I/O in .NET.
Another way of looking at the problem is this. If `Async Main` style tutorials are the key scenario to consider, we can get that for free with appropriate base-types in a library (e.g. the VB runtime). Just set the default base type to e.g. `Microsoft.VisualBasic.ApplicationServices.AsyncConsoleApp` and those programs will work fine even if we go with **Option 3**. The question is whether to require that or whether the compiler does the work that that base class would have.
> **ADG**: When I consider that possibility--shipping pre-built base types for inferred classes, I wonder if the case where there's just a `Execute` method with no base type is worth pursuing at all because all of those scenarios can be hit with the right base-classes.
### Are `Exit Sub`, `Exit Function`, `Return` statements valid in an inferred method?
**As long as they are of the right kind for that method, yes, it's an ordinary method.**
### If the inferred method is a function, is the return variable valid to use inside?
**Yes, it's an ordinary method.**
The method has an utter-able name; it's not like an `Operator` or a lambda. It's arbitrary to disallow this in this one case and the compiler has a lot of assumptions about functions always having one.
### Does an inferred method constitute user-code for the purpose of `CallerInfo` attributes?
**Yes, it's an ordinary method.**.
This isn't a compiler-generated method-body (like an auto-prop) invoking a method; the code that causes the inferred method to come into being is user-code. Crippling `CallerInfo` in this case serves no one.
### Can you overload the inferred method?
**Yes, it's an ordinary method.**
### How does `GetDeclaredSymbol` work?
In order to get the Nav bar working here's what I implemented. The inferred method uses the `CompilationUnitSyntax` as its declaring syntax because there is no other `SyntaxNode` that would work. The inferred type *also* uses the `CompilationUnitSyntax` as *one of* its declaring syntax references. So, obviously consumers will pass those nodes to the `SemanticModel` to get back to the `ISymbol` for those entities. When passing a compilation-unit node to `GetDeclaredSymbol` here's what you get back:
* If the compilation-unit has an inferred method declaration, the corresponding `IMethodSymbol` is returned.
* If the compilation-unit has an inferred type declaration (and **no** inferred method declaration), the corresponding `INamedTypeSymbol` is returned.
* If the compilation-unit has neither, null is returned.
All of these situations are new and some invariants have been stretched or broken. As I recall, `GetDeclaredSymbol` either *always* returns null for a given `SyntaxNode` type or *always* returns non-null. In places like `For` loops where the `i` in `For i = 1 To 10` could be declaring an `i` or referring to an existing one we opted to just say it never declares a variable but rather always refers to one (though that one may also be implicitly declared) rather than sometimes returning something and sometimes not.
Further, the type of symbol returned for a given syntax type has always been one kind and in this case it's one of two. But, `CompilationUnitSyntax` is/can be pretty special. We'd have to look at the impact on IDE code (and others) to understand whether this design is elegant or a nightmare for API consumers.
> **ADG**: Open to feedback and suggestions on this.
### How does the language determine which method an inferred method declaration will override?
**Options**
1. All inferred method declarations have the same name, e.g. `Execute`, and will override a base method by name-only. If more than one method named `Execute` exists in the base classes it is ambiguous.
2. Methods intended to be overridden by an inferred method declaration can be marked by an attribute.
3. Base types with methods intended to be the default override can be marked with an attribute specifying the name of the method.
It's important that the compiler check each level of the hierarchy in turn so that a more derived type can change the default override method. **Scenario D** does this when on file inherits `Layout` and defines another placeholder for content.
I started out with **Option 1** but the name appearing in the nav bar is intuitively the most informative piece of information you can have when dropping code into one of these overrides. Requiring all of them to use the generic name `Execute` instead of meaningful names seems wrong.
Then I moved to **Option 2** with the `DefaultOverrideMethodAttribute` idea, which is better. The only problem is that it requires enumerating all methods and realizing their attributes to determine what to override.
**Option 3** is more inline with how indexers work. There's an attribute applied at the type level that tells you the name of the property that is the default property for that type. This would simplify the code to find such a method a lot and there are other things which might go into such an attribute, see **"VII. A Magic Attribute That Lets The Abstract Author Prescribe Defaults"**
> **ADG**: I prototyped **Option 2** but **my inclination is toward Option 3**, at this time.
# III. Pattern-Based Non-Form-Derived Start-up Types
*Startable non-Form types is demonstrated in **Scenario B** video and in it different types are specified as the start-up type at different points in the video to show why you might use a different type at different times. Other scenarios might include test runners/harnesses, etc., in non-documentation projects.*
Today the entry-point to an executable VB program is determined one of several ways:
* It can be inferred by searching all named types declared in a program for a `Shared` method with the correct name `Main` and signature. If there is a **unique** candidate across the entire compilation (Project), that is the entry-point.
* It can be specified with the `/main:` compiler option.
In the case that it is specified with `/main:` the type specified or one of its base-types must declare a valid `Main` method **OR** if the type inherits `System.Windows.Forms.Form` (directly or indirectly) an entry point will be generated, roughly equivalent to the following:
``` VB.NET
Class SomeFormInMyProject : Inherits Form
Shared Sub Main()
#If Then
Application.Run(My.Forms.SomeFormInMyProject)
#Else
Application.Run(New SomeFormInMyProject)
#End If
End Sub
End Class
```
I propose **generalizing** this special case further:
* If the specified type (`T`) is **NOT** derived from `Form`, and `T` or one of its base types has an accessible `Shared` `Run` method taking a parameter assignable from `T`, the compiler will synthesize an entry point passing an instance of `T` to `Run` (using `My` default instances if available, or `New T` otherwise).
* If `Run` is a function returning an `Integer` the synthesized `Main` method will be a function returning the value of the invocation of `Run`, otherwise the synthesized `Main` method will be a sub calling `Run`.
This indirection is necessary for several reasons. First, it allows the `Run` method to do any scenario-specific setup and control exactly when the top-level code gets run and what happens before or after and what arguments, if any get passed to it.
In **Scenario C** the `Execute` method isn't run once at the start of the program, it's actually run every frame and "starting" the app is independent of invoking that method, though in that scenario this is accomplished by the fact that the game loop is implemented as a type deriving from `Form` so the existing start-up code that calls `Application.Run` is called.
Another scenario that would benefit from this is `Async Main`. The compiler would only need to call (e.g.) `AsyncConsoleApp.Run(New Program)` and the `Run` method would have the smarts to call `ExecuteAsync().GetAwaiter().GetResult()`. It also means that a different base class could use a different scheduling strategy and could provide its own message pump, if so inclined.
# Secondary Proposals
## IV. Default inferred type base class compilation option
*Default base types for inferred type declarations is demonstrated in **Scenario D** above.*
I propose a new compiler option allowing one to specify the fully-qualified name of a type to be used by default as the base type of an inferred type declaration *if and only if* no other base type is explicitly specified either within the inferred type declaration or any of its other partial definitions.
**Note**: That just as with project-level imports this name can specify a generic instantiation so `/defaultinferredtypebaseclass:System.Collections.Generic.Dictionary(Of String, Object)` is perfectly valid.
While one could always specify a base type explicitly, that constitutes boilerplate; especially in projects where a significant number of files use inferred type declarations. In **Scenario D** potentially dozens of types might all inherit from `View`. In WinForms the user-file is kept uncluttered like this by putting the `Inherits` statement in the `Partial` declaration in the designer-generated file by default.
> **ADG**: For a feature that aims to reduce boilerplate, mandating a second file with a partial declaration for every inferred type would defeat the purpose. Further, it would actually be pretty confusing in situations like **Scenario B** where the content of the file benefits most from omitting setup code (like namespace declarations, `Imports` statements, and `Inherits` statements) which would otherwise appear out of place in the "document".
### Design Issues
#### What should it be named?
## V. Implicit Return Expression Statements ("Top-Level Expressions")
*Implicit return expressions are demonstrated in **Scenario D** and **Scenario E** above.*
**Example - TopBuyers2018.vb**
``` VB.NET
From o In db.Orders
Where o.PurchaseDate.Year = 2018
Group By o.Customer
Into Group, GrandTotal = Sum(o.Total)
Order By o.GrandTotal Descending
Take 20
```
*When combined with VBs **existing** MyGroupCollection extensibility this becomes a very fast way to build up suite of queries because the compiler itself already knows how to collect all types of a given base class and manage their instances: `My.Queries.TopBuyers2018` could come into existence simply by defining this file.*
I propose that *some* set of expressions in VB are often of sufficient size and complexity to both be the central content of their containing method AND to benefit from having the maximum horizontal space for their use, particularly in an inferred method declaration, and that it should be permissible in an inferred method declaration (at least) to use such expressions in an expression statement and have that statement act as an implicit return statement returning the value of that expression.
Specifically, expressions that I believe fit this criteria at this time include:
* XML literal **documents**, and
* Query expressions (LINQ)
I also believe that if that proposal were accepted, JSON literal object and array expressions *could* fit this criteria when writing web services. This allows for an immersive authoring experience and, in the case of an XML literal may have practical implications on the output because indentation contributes whitespace to the constructed object.
Functional languages like F# lean hard into this and I don't think VB will or should ever go that deep but there could be some measured toe-dipping here.
### Design issues
#### Should it be all expressions?
**No**.
Practically it can't be because there are some ambiguities between expressions and statements. The most well-known example is `a = b` which is either an assignment or a comparison depending on how it's interpreted. But also, from a parsing (and reading) perspective, the first token of most valid VB statements is a keyword describing the kind of statement it is or an identifier for assignments and invocations. There are exceptions, such as numeric labels, but it's pretty unknown what ambiguities arise if that convention is dropped entirely.
In the case of LINQ or a hypothetical JSON literal there is no ambiguity, these are safe. But in the case of a top-level XML elements it's ambiguous whether a line starting with `<` is the beginning of an XML literal or a attribute specification and I haven't yet thought my way out of how to resolve it elegantly. Even with an XML namespace it looks the same as an attribute target specifier and I don't we would want to key the parsing on whether the namespace prefix is a valid target anyway as that would tie our hands in the future if more attribute targets are added. It's not ambiguous with XML documents though, which is good which is why **Scenario D** specifically uses whole documents.
> **ADG**: I mean, come on, **DOCUMENT** is in the name; they're supposed to take up the whole file :)
Arguably it could be fine for parenthesized expressions, which would consequently let all the other expressions in. This is something that was discussed for VB Interactive submissions as well as the Immediate Window. It would have to be weighed against any possible target-type-inference/expression-classification issues (e.g. if the return type of the containing method is delegate/array-literal/interpolated string).
> **ADG**: It would make it marginally easy to write a VB expression evaluator but once the tool author has to wrap every expression in parentheses to do it it doesn't matter that they also have to wrap them in a `Return` statement, plus the scenario is very narrow, so I don't think parenthesized expressions are worth it.
#### Should this be expanded to cases outside of inferred method declarations?
**Maybe**.
> **ADG**: It would give a consistent experience in **Scenario D**-like situations where multiple portions of content are overridden in the same file
#### Does it have to be the last expression in the compilation-unit and and can it be inside a block?
There's potentially some ambiguity with XML elements because "technically" VB `static` local variable declarations can have attributes, but they are ignored is provided, aren't actually bound, no tooling, don't even have to refer to real attributes. I believe it's a parsing artifact from the native compilers which was maintained for back-compat. If the attribute appears on a non-static local it's an error, so it could probably be worked around.
> **ADG**: In **Scenario E**, Scott's original code had all of the I/O wrapped in a `Using` block to properly dispose of resources. I rewrote it to use an iterator in part to work-around the limitation of the prototype not allowing this. My gut says to wait for more scenarios before pursuing this.
## VI. `Type` and `Method` Attribute Targets
Because under this proposal, type and method declarations may be inferred there's no place to attach attributes which would otherwise appear on an explicit declaration. Similar to the approach one might take when, say, applying an attribute to the implicitly declared backing field of an auto-prop, this would be a good scenario to extend VBs existing `Assembly` and `Module` attribute target specifiers to include some form of `Type` and `Method` targets. They would still appear at the file-level but would be taken to apply to the inferred declarations.
At this time I don't have concrete scenarios for this at the top of my mind with the exception of **Scenario E**. Because ASP.NET Core can also make use of attribution to describe URL routing it would be valuable to be able to specify those attributes on a stand-alone file. In my demo I designed my base classes such that this wasn't necessary but there are certainly other situations out there.
`Type` and `Method` attribute targets are orthogonal to this proposal and aren't necessary for it. This is just a note to keep in mind that doing this feature adds another place where such targets would potentially add value should they be needed/added later.
## VII. A Magic Attribute That Lets The Abstract Author Prescribe Defaults
As I worked through the scenarios there were an increasing number of knobs an abstraction author might want to configure and it could be beneficial to have a centralized attribute for configuring them all. For example:
* The name of the method to override.
* The default accessibility level or other modifiers of the inferred type (e.g. `Public` for controllers, `NotInheritable` for WinRT types)
* Whether to copy attributes from the base method to the override method (e.g. `HttpGetAttribute`).
* Modifiers for the inferred method (e.g. `Async`, `Iterator`)
* Whether to copy attributes from the base type to the inferred type (e.g. `HttpRouteAttribute`).
* If the compiler can generate a `Main` method to make an inferred type a start-up type, any attributes that should go on such a `Main` method such as `STAThreadAttribute`.
* Potentially an default set of namespace imports?
* Potentially a set of diagnostics to enable or disable by default within the inferred method (e.g. "Function does not return a value on all paths")
Because this feature is a tool for abstraction authors to create particular experiences for end-developers it's conceivable that there might be a richer `InferredClassAndMethodConfigurationAttribute` which makes all that information available to the compiler so that those decisions can remain in the hands of abstraction authors.
The alternative is to pick a set of defaults with various escape hatches for abstraction consumers (e.g. attribute targets) at the cost of increasing the complexity of the abstractions we're trying to simplify in the first place.
Today attribute inheritance is largely governed by the setting of `AttributeUsageAttribute.Inherited` which gives power to the developer defining the attribute to decide how it behaves. I think there's a case to be made that there should *also* be a mechanism for the developer applying an attribute to decide how it will behave for their derivatives.
By analogy, a base class author can mark a method `Overridable` but another derived base class author can revisit and decide the method is `NotOverridable` or even `MustOverride`. It would be interesting to look into that.
The issue of attribute inheritance was entirely motivated by how ASP.NET MVC finds and uses attributes in **Scenario E**. However, the biggest issue with **Scenario E** was that, by default, controllers have to be `Public` and types in VB are `Friend` by default. To work around this issue I used MVCs extensibility to add a filter for controllers that permits non-public controllers but the documentation indicated there may be similar issues with route attributes that would require similar workarounds.
> **ADG**: More scenarios are needed for attribution inheritance behavior and attribute targets are a pretty general solution for this problem that has been discussed for VB for other issues anyway.
> `Async` and `Iterator` modifiers are more about the method author than the method definer and I'm more inclined to just deal with whether or not the user writes them than to have the abstraction author prescribe them.
> Other modifiers seem reasonable provided they can be explicitly stated in a partial and that takes precedence. These are just defaults, after all. I like it a little better than `` pseudo-attributes (which I don't hate).
> **I'm at least sold on the name being in a base-type-applied attribute.**
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.