Skip to content

Commit

Permalink
Merge pull request #19436 from dotnet/features/async-main
Browse files Browse the repository at this point in the history
Merge Features/async main
  • Loading branch information
TyOverby authored May 11, 2017
2 parents ca449a6 + 7a7210d commit d15e1a6
Show file tree
Hide file tree
Showing 27 changed files with 2,339 additions and 376 deletions.
12 changes: 12 additions & 0 deletions docs/features/async-main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Async Task Main
## [dotnet/csharplang proposal](https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md)

## Technical Details

* The compiler must recognize `Task` and `Task<int>` as valid entrypoint return types in addition to `void` and `int`.
* The compiler must allow `async` to be placed on a main method that returns a `Task` or a `Task<T>` (but not void).
* The compiler must generate a shim method `$EntrypointMain` that mimics the arguments of the user-defined main.
* `static async Task Main(...)` -> `static void $EntrypointMain(...)`
* `static async Task<int> Main(...)` -> `static int $EntrypointMain(...)`
* The parameters between the user-defined main and the generated main should match exactly.
* The body of the generated main should be `return Main(args...).GetAwaiter().GetResult();`
99 changes: 99 additions & 0 deletions docs/features/async-main.test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
Main areas to test:
* Signature acceptance / rejection
* Method body creation

# Signature acceptance / rejection

## Single Mains
Classes that contain only a single "main" method

### Single legal main
* Past: Ok
* New 7: Ok
* New 7.1 Ok

### Single async (void) main
* Past: ERR (Async can't be main)
* New 7: ERR (Update to get this to work)
* New 7.1: Ok

### Single async (Task) main
* Past: ERR (No entrypoints found), WARN (has the wrong signature to be an entry point)
* New 7: ERR (Update to get this to work)
* New 7.1 Ok

### Single async (Task<int>) main
* Past: ERR (No entrypoints found), WARN (has the wrong signature)
* New 7: ERR (Update to get this to work)
* New 7.1: Ok

## Multiple Mains
Classes that contain more than one main

### Multiple legal mains
* Past: Err
* New 7: Err
* New 7.1: Err

### Single legal main, single async (void) main
* Past: Err (an entrypoint cannot be marked with async)
* New 7: Err (new error here?)
* New 7.1: Err (new error here? "void is not an acceptable async return type")

### Single legal main, single legal Task main
* Past: Ok (warning: task main has wrong signature)
* New 7: Ok (new warning here)
* New 7.1: Ok (new warning here?)

### Single legal main, single legal Task<int> main
* Past: Ok (warning: task main has wrong signature)
* New 7: Ok (new warning here)
* New 7.1: Ok (new warning here?)

# Method body creation

* Inspect IL for correct codegen.
* Make sure that attributes are correctly applied to the synthesized mains.

## Broken methods on Task and Task<T>

* GetAwatier or GetResult are missing
* GetAwaiter or GetResult don't have the required signature
* Has the right shape, but types are missing

## Task or Task<T> is missing

This will be caught during entrypoint detection, should be a binding error.

## Void or int is missing

If Task can be found, but void or int can't be found, then the compiler should behave gracefully.

# Vlad Test Plan

## Public interface of compiler APIs
-N/A?

## General functionality
- `Task` and `Task<int>` returns are allowed. Do we require `async`.
- Multiple valid/invalid async candidates.
- process exit code on success (void and int) and on exception
- `/main:<type>` cmd switch is still functional

## Old versions, compat
- langver
- when both async and regular Main avaialble. With old/new compiler versions.
- async void Main. With old.new langver. With another applicable Main present.

## Type and members
- Access modifiers (public, protected, internal, protected internal, private), static modifier
- Parameter modifiers (ref, out, params)
- STA attribute
- Partial method
- Named and optional parameters
- `Task<dynamic>`
- Task-Like types

## Debug
- F11 works
- stepping through Main
35 changes: 23 additions & 12 deletions src/Compilers/CSharp/Portable/Binder/Binder_Await.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private BoundExpression BindAwait(BoundExpression expression, SyntaxNode node, D
bool hasErrors =
ReportBadAwaitWithoutAsync(node, diagnostics) |
ReportBadAwaitContext(node, diagnostics) |
!GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, node, diagnostics);
!GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, out _, node, diagnostics);

// Spec 7.7.7.2:
// The expression await t is classified the same way as the expression (t).GetAwaiter().GetResult(). Thus,
Expand Down Expand Up @@ -209,17 +209,19 @@ private bool ReportBadAwaitContext(SyntaxNode node, DiagnosticBag diagnostics)
/// Finds and validates the required members of an awaitable expression, as described in spec 7.7.7.1.
/// </summary>
/// <returns>True if the expression is awaitable; false otherwise.</returns>
private bool GetAwaitableExpressionInfo(
internal bool GetAwaitableExpressionInfo(
BoundExpression expression,
out MethodSymbol getAwaiter,
out PropertySymbol isCompleted,
out MethodSymbol getResult,
out BoundExpression getAwaiterGetResultCall,
SyntaxNode node,
DiagnosticBag diagnostics)
{
getAwaiter = null;
isCompleted = null;
getResult = null;
getAwaiterGetResultCall = null;

if (!ValidateAwaitedExpression(expression, node, diagnostics))
{
Expand All @@ -231,15 +233,16 @@ private bool GetAwaitableExpressionInfo(
return true;
}

if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter))
BoundExpression getAwaiterCall = null;
if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter, out getAwaiterCall))
{
return false;
}

TypeSymbol awaiterType = getAwaiter.ReturnType;
return GetIsCompletedProperty(awaiterType, node, expression.Type, diagnostics, out isCompleted)
&& AwaiterImplementsINotifyCompletion(awaiterType, node, diagnostics)
&& GetGetResultMethod(awaiterType, node, expression.Type, diagnostics, out getResult);
&& GetGetResultMethod(getAwaiterCall, node, expression.Type, diagnostics, out getResult, out getAwaiterGetResultCall);
}

/// <summary>
Expand Down Expand Up @@ -273,26 +276,29 @@ private static bool ValidateAwaitedExpression(BoundExpression expression, Syntax
/// NOTE: this is an error in the spec. An extension method of the form
/// Awaiter&lt;T&gt; GetAwaiter&lt;T&gt;(this Task&lt;T&gt;) may be used.
/// </remarks>
private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod)
private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod, out BoundExpression getAwaiterCall)
{
if (expression.Type.SpecialType == SpecialType.System_Void)
{
Error(diagnostics, ErrorCode.ERR_BadAwaitArgVoidCall, node);
getAwaiterMethod = null;
getAwaiterCall = null;
return false;
}

var getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray<BoundExpression>.Empty, diagnostics);
getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray<BoundExpression>.Empty, diagnostics);
if (getAwaiterCall.HasAnyErrors) // && !expression.HasAnyErrors?
{
getAwaiterMethod = null;
getAwaiterCall = null;
return false;
}

if (getAwaiterCall.Kind != BoundKind.Call)
{
Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type);
getAwaiterMethod = null;
getAwaiterCall = null;
return false;
}

Expand All @@ -303,6 +309,7 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Di
{
Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type);
getAwaiterMethod = null;
getAwaiterCall = null;
return false;
}

Expand Down Expand Up @@ -383,35 +390,39 @@ private bool AwaiterImplementsINotifyCompletion(TypeSymbol awaiterType, SyntaxNo
/// Spec 7.7.7.1:
/// An Awaiter A has an accessible instance method GetResult with no parameters and no type parameters.
/// </remarks>
private bool GetGetResultMethod(TypeSymbol awaiterType, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod)
private bool GetGetResultMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod, out BoundExpression getAwaiterGetResultCall)
{
var receiver = new BoundLiteral(node, ConstantValue.Null, awaiterType);
var getResultCall = MakeInvocationExpression(node, receiver, WellKnownMemberNames.GetResult, ImmutableArray<BoundExpression>.Empty, diagnostics);
if (getResultCall.HasAnyErrors)
var awaiterType = awaiterExpression.Type;
getAwaiterGetResultCall = MakeInvocationExpression(node, awaiterExpression, WellKnownMemberNames.GetResult, ImmutableArray<BoundExpression>.Empty, diagnostics);
if (getAwaiterGetResultCall.HasAnyErrors)
{
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

if (getResultCall.Kind != BoundKind.Call)
if (getAwaiterGetResultCall.Kind != BoundKind.Call)
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult);
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

getResultMethod = ((BoundCall)getResultCall).Method;
getResultMethod = ((BoundCall)getAwaiterGetResultCall).Method;
if (getResultMethod.IsExtensionMethod)
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult);
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

if (HasOptionalOrVariableParameters(getResultMethod) || getResultMethod.IsConditional)
{
Error(diagnostics, ErrorCode.ERR_BadAwaiterPattern, node, awaiterType, awaitedExpressionType);
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

Expand Down
9 changes: 6 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2060,28 +2060,31 @@ private AssemblySymbol GetForwardedToAssembly(string fullName, int arity, Diagno
return null;
}

internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null)
internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null)
{
var options = (CSharpParseOptions)syntax.SyntaxTree.Options;
if (options.IsFeatureEnabled(feature))
{
return;
return true;
}

var location = locationOpt ?? syntax.GetLocation();
string requiredFeature = feature.RequiredFeature();
if (requiredFeature != null)
{
diagnostics.Add(ErrorCode.ERR_FeatureIsExperimental, location, feature.Localize(), requiredFeature);
return;
return false;
}

LanguageVersion availableVersion = options.LanguageVersion;
LanguageVersion requiredVersion = feature.RequiredVersion();
if (requiredVersion > availableVersion)
{
diagnostics.Add(availableVersion.GetErrorCode(), location, feature.Localize(), new CSharpRequiredLanguageVersion(requiredVersion));
return false;
}

return true;
}
}
}
38 changes: 30 additions & 8 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d15e1a6

Please sign in to comment.