Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for top level statements #980

Draft
wants to merge 7 commits into
base: draft-v9
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions standard/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

## 7.1 Application startup

### §app-startup-general General

A program may be compiled either as a ***class library*** to be used as part of other applications, or as an ***application*** that may be started directly. The mechanism for determining this mode of compilation is implementation-specific and external to this specification.

The entry point of an application may be specified in either of the following ways:

1. Explicitly, by declaring a method with appropriate characteristics (§named-entry-point).
1. Implicitly, by using top-level statements (§top-level-statements).

### §named-entry-point Using a named entry point

A program compiled as an application shall contain at least one method qualifying as an entry point by satisfying the following requirements:

- It shall have the name `Main`.
Expand Down Expand Up @@ -47,6 +56,166 @@ If the effective entry point’s return type is `int`, the return value from the

Other than the situations listed above, entry point methods behave like those that are not entry points in every respect. In particular, if the entry point is invoked at any other point during the application’s lifetime, such as by regular method invocation, there is no special handling of the method: if there is a parameter, it may have an initial value of `null`, or a non-`null` value referring to an array that contains null references. Likewise, the return value of the entry point has no special significance other than in the invocation from the execution environment.

### §top-level-statements Using top-level statements

Any one compilation unit ([§14.2](namespaces.md#142-compilation-units) in an application may contain one or more *statement_list*s—collectively called ***top-level statements***— in which case, the meaning is as if those *statement_list*s were combined in the block body of a static method within a partial class called `Program` in the global namespace, as follows:

```csharp
partial class Program
{
static «AsyncAndReturnType» «Main»(string[] args)
{
// top-level statements
}
}
```

The class name `Program` shall be referenceable by name from within the application. However, the method name `«Main»` is used here for illustrative purposes only. The actual name generated by the implementation is unspecified, and cannot be referenced by name from within the application.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this say that the generated name is readable via CallerMemberNameAttribute?

System.Console.WriteLine(WhoAmI()); // outputs "<Main>$"

System.Action action = () => System.Console.WriteLine(WhoAmI());
action(); // outputs "<Main>$" again, not the name of the method generated for the lambda

string WhoAmI(
    [System.Runtime.CompilerServices.CallerMemberName]string member = null)
{
    return member;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KalleOlaviNiemitalo, I confirm that the generated name can be found, but it's not clear to me how one might make use of that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be best to add something about this to the specification of CallerMemberNameAttribute, to clarify that the name of the generated method is substituted even though it does not occur in the source code. Because this doesn't work like in lambda expressions where the user-specified name is used even though the code is generated to a differently-named method.

using System;
using System.Runtime.CompilerServices;

class C
{
    void M(int i)
    {
        // calls N(i, "M") even though the call is in a method named something like <M>b__0
        Action a = () => N(i);
    }
    
    static void N(int i, [CallerMemberName] string mem = null)
    {
    }
}


The method is designated as the entry point of the program. Explicitly declared methods (including static ones called `Main`) that by convention could be considered as entry point candidates (§named-entry-point) shall be ignored for that purpose.

The entry-point method has one parameter, `string[] args`. This parameter is in scope within the top-level statements and not otherwise. Regular name conflict/shadowing rules apply.

Async operations are allowed in top-level statements to the degree they are allowed in statements within a named async entry-point method. However, they are not required.

The tokens that are generated in place of `«AsyncAndReturnType»` are determined based on operations used by the top-level statements, as follows:

| **Top-level code contains** | **Generated entry-point signature**
| ----------------------- | -------------------------------
| No `await` or `return` with value | `private static void «Main»(string[] args)`
| `return` with value only | `private static int «Main»(string[] args)`
| `await` only | `private static async Task «Main»(string[] args)`
| `await` and `return` with value | `private static async Task<int> «Main»(string[] args)`

This is illustrated by the following sets of top-level statements:

**Set 1**

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements2A", expectedOutput:["cmd-line args length = 0"]} -->
```csharp
using System;
Console.WriteLine($"cmd-line args length = {args.Length}");
C c = new C();
public class C {}
```

whose implementation-generated code is

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements2B", expectedOutput:["cmd-line args length = 0"]} -->
```csharp
using System;
partial class Program
{
static void «Main»(string[] args)
{
Console.WriteLine($"cmd-line args length = {args.Length}");
C c = new C();
}
}
public class C {}
```

*Note*: As required by the grammar for *compilation_unit* ([§14.2](namespaces.md#142-compilation-units)), top-level statements must come after *using_directive*s and before *namespace_member_declaration*s, such as types. *end note*

**Set 2**

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements3A", expectedOutput:["Hi!"]} -->
```csharp
System.Console.WriteLine("Hi!");
return 2;
```

whose implementation-generated code is

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements3B", expectedOutput:["Hi!"]} -->
```csharp
partial class Program
{
static int «Main»(string[] args)
{
System.Console.WriteLine("Hi!");
return 2;
}
}
```

**Set 3**

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements4A", expectedOutput:["Hi!"]} -->
```csharp
using System;
using System.Threading.Tasks;
await Task.Delay(1000);
Console.WriteLine("Hi!");
```

whose implementation-generated code is

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements4B", expectedOutput:["Hi!"]} -->
```csharp
using System;
using System.Threading.Tasks;
partial class Program
{
static async Task «Main»(string[] args)
{
await Task.Delay(1000);
Console.WriteLine("Hi!");
}
}
```

**Set 4**

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements5A", expectedOutput:["Hi!"]} -->
```csharp
using System;
using System.Threading.Tasks;
await Task.Delay(1000);
Console.WriteLine("Hi!");
return 0;
```

whose implementation-generated code is

<!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements5B", expectedOutput:["Hi!"]} -->
```csharp
using System;
using System.Threading.Tasks;
partial class Program
{
static async Task<int> «Main»(string[] args)
{
await Task.Delay(1000);
Console.WriteLine("Hi!");
return 0;
}
}
```

The implementation-generated class `Program` can be augmented by user-written code that declares one or more partial classes called `Program`.

> *Example*: Consider the following:
>
> <!-- Example: {template:"standalone-console-without-using", name:"TopLevelStatements6"} -->
> ```csharp
> M1(); // call top-level static local function M1
> M2(); // call top-level non-static local function M2
>
> static void M1() { } // static local function
> void M2() { } // non-static local function
>
> Program.M1(); // call static method M1
> new Program().M2(); // call non-static method M2
> partial class Program
> {
> static void M1() { } // static method
> void M2() { } // non-static method
> }
> ```
>
> As the first two declarations for `M1` and `M2` get wrapped inside the generated entry-point method, they are local functions. However, the second two declarations are methods, as they are declared inside a class rather than a method. *end example*

## 7.2 Application termination

***Application termination*** returns control to the execution environment.
Expand Down Expand Up @@ -684,6 +853,14 @@ Within the scope of a local variable, it is a compile-time error to refer to the
>
> *end note*

As described in §top-level-statements, top-level source tokens are enclosed by the generated entry-point method.

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 is the evaluation within the global namespace declaration 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, a compile-time error results.

### 7.7.2 Name hiding

#### 7.7.2.1 General
Expand Down
4 changes: 2 additions & 2 deletions standard/namespaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ Using directives ([§14.5](namespaces.md#145-using-directives)) are provided to

## 14.2 Compilation units

A *compilation_unit* consists of zero or more *extern_alias_directive*s followed by zero or more *using_directive*s followed by zero or one *global_attributes* followed by zero or more *namespace_member_declaration*s. The *compilation_unit* defines the overall structure of the input.
A *compilation_unit* consists of zero or more *extern_alias_directive*s followed by zero or more *using_directive*s followed by zero or one *global_attributes* followed by zero or more *statement_list*s followed by zero or more *namespace_member_declaration*s. The *compilation_unit* defines the overall structure of the input.

```ANTLR
compilation_unit
: extern_alias_directive* using_directive* global_attributes?
namespace_member_declaration*
statement_list* namespace_member_declaration*
;
```

Expand Down
1 change: 1 addition & 0 deletions standard/portability-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ A conforming implementation is required to document its choice of behavior in ea

## B.4 Unspecified behavior

1. The name of the entry-point method generated to contain top-level statements (§top-level-statements).
1. The time at which the finalizer (if any) for an object is run, once that object has become eligible for finalization ([§7.9](basic-concepts.md#79-automatic-memory-management)).
1. The representation of `true` ([§8.3.9](types.md#839-the-bool-type)).
1. The value of the result when converting out-of-range values from `float` or `double` values to an integral type in an `unchecked` context ([§10.3.2](conversions.md#1032-explicit-numeric-conversions)).
Expand Down
Loading