csharplang/proposals/top-level-statements.md

203 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Top-level statements
* [x] Proposed
* [x] Prototype: Started
* [x] Implementation: Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary
Allow a sequence of *statements* to occur right before the *namespace_member_declaration*s of a *compilation_unit* (i.e. source file).
The semantics are that if such a sequence of *statements* is present, the following type declaration, modulo the actual type name and the method name, would be emitted:
``` c#
static class Program
{
static async Task Main(string[] args)
{
// statements
}
}
```
See also https://github.com/dotnet/csharplang/issues/3117.
## Motivation
[motivation]: #motivation
There's a certain amount of boilerplate surrounding even the simplest of programs,
because of the need for an explicit `Main` method. This seems to get in the way of
language learning and program clarity. The primary goal of the feature therefore is
to allow C# programs without unnecessary boilerplate around them, for the sake of
learners and the clarity of code.
## Detailed design
[design]: #detailed-design
### Syntax
The only additional syntax is allowing a sequence of *statement*s in a compilation unit,
just before the *namespace_member_declaration*s:
``` antlr
compilation_unit
: extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
;
```
Only one *compilation_unit* is allowed to have *statement*s.
Example:
``` c#
if (args.Length == 0
|| !int.TryParse(args[0], out int n)
|| n < 0) return;
Console.WriteLine(Fib(n).curr);
(int curr, int prev) Fib(int i)
{
if (i == 0) return (1, 0);
var (curr, prev) = Fib(i - 1);
return (curr + prev, curr);
}
```
### Semantics
If any top-level statements are present in any compilation unit of the program, the meaning is as if
they were combined in the block body of a `Main` method of a `Program` class in the global namespace,
as follows:
``` c#
static class Program
{
static async Task Main(string[] args)
{
// statements
}
}
```
Note that the names "Program" and "Main" are used only for illustrations purposes, actual names used by
compiler are implementation dependent and neither the type, nor the method can be referenced by name from
source code.
The method is designated as the entry point of the program. Explicitly declared methods that by convention
could be considered as an entry point candidates are ignored. A warning is reported when that happens. It is
an error to specify `-main:<type>` compiler switch when there are top-level statements.
The entry point method always has one formal parameter, ```string[] args```. The execution environment creates and passes a ```string[]``` argument containing the command-line arguments that were specified when the application was started. The ```string[]``` argument is never null, but it may have a length of zero if no command-line arguments were specified. The args parameter is in scope within top-level statements and is not in scope outside of them. Regular name conflict/shadowing rules apply.
Async operations are allowed in top-level statements to the degree they are allowed in statements within
a regular async entry point method. However, they are not required, if `await` expressions and other async
operations are omitted, no warning is produced.
The signature of the generated entry point method is determined based on operations used by the top level
statements as follows:
**Async-operations\Return-with-expression** | **Present** | **Absent**
----------------------------------------| -------------|-------------
**Present** | ```static Task<int> Main(string[] args)```| ```static Task Main(string[] args)```
**Absent** | ```static int Main(string[] args)``` | ```static void Main(string[] args)```
The example above would yield the following `$Main` method declaration:
``` c#
static class $Program
{
static void $Main(string[] args)
{
if (args.Length == 0
|| !int.TryParse(args[0], out int n)
|| n < 0) return;
Console.WriteLine(Fib(n).curr);
(int curr, int prev) Fib(int i)
{
if (i == 0) return (1, 0);
var (curr, prev) = Fib(i - 1);
return (curr + prev, curr);
}
}
}
```
At the same time an example like this:
``` c#
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
```
would yield:
``` c#
static class $Program
{
static async Task $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
}
}
```
An example like this:
``` c#
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
```
would yield:
``` c#
static class $Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
}
}
```
And an example like this:
``` c#
System.Console.WriteLine("Hi!");
return 2;
```
would yield:
``` c#
static class $Program
{
static int $Main(string[] args)
{
System.Console.WriteLine("Hi!");
return 2;
}
}
```
### Scope of top-level local variables and local functions
Even though top-level local variables and functions are "wrapped"
into the generated entry point method, they should still be in scope throughout the program in
every compilation unit.
For the purpose of simple-name evaluation, once the global namespace is reached:
- First, an attempt is made to evaluate the name within the generated entry point method and
only if this attempt fails
- The "regular" evaluation within the global namespace declaration is performed.
This could lead to name shadowing of namespaces and types declared within the global namespace
as well as to shadowing of imported names.
If the simple name evaluation occurs outside of the top-level statements and the evaluation
yields a top-level local variable or function, that should lead to an error.
In this way we protect our future ability to better address "Top-level functions" (scenario 2
in https://github.com/dotnet/csharplang/issues/3117), and are able to give useful diagnostics
to users who mistakenly believe them to be supported.