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

Merge dev16.0-preview2-vs-deps to master-vs-deps #32196

Merged
5 commits merged into from
Jan 8, 2019
Merged
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
1 change: 1 addition & 0 deletions docs/Language Feature Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ efforts behind them.
| [Alternative interpolated verbatim strings](https://github.com/dotnet/csharplang/issues/1630) | master | Merged to dev16 preview1 | [jcouv](https://github.com/jcouv) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv)
| [stackalloc in nested contexts](https://github.com/dotnet/csharplang/issues/1412) | [nested-stackalloc](https://github.com/dotnet/roslyn/tree/features/nested-stackalloc) | [In Progress](https://github.com/dotnet/roslyn/issues/28968) | [gafter](https://github.com/gafter) | - | [gafter](https://github.com/gafter)
| [Unmanaged generic structs](https://github.com/dotnet/csharplang/issues/1744) | master | [In Progress](https://github.com/dotnet/roslyn/issues/31374) | [RikkiGibson](https://github.com/RikkiGibson) | - | [jaredpar](https://github.com/jaredpar) |
| [Static local functions](https://github.com/dotnet/csharplang/issues/1565) | master | [Implemented](https://github.com/dotnet/roslyn/issues/32069) | [cston](https://github.com/cston) | [jaredpar](https://github.com/jaredpar) | [jcouv](https://github.com/jcouv)

# VB 16.0

Expand Down
87 changes: 49 additions & 38 deletions docs/features/async-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ See more details about those types at https://blogs.msdn.microsoft.com/dotnet/20

Compared to the state machine for a regular async method, the `MoveNext()` for an async-iterator method adds logic:
- to support handling a `yield return` statement, which saves the current value and fulfills the promise with result `true`,
- to support handling a `yield break` statement, which sets the dispose mode on and jumps to the closest `finally` or exit,
- to support handling a `yield break` statement, which sets the dispose mode on and jumps to the enclosing `finally` or exit,
- to dispatch execution to `finally` blocks (when disposing),
- to exit the method, which fulfills the promise with result `false`,
- to catch exceptions, which set the exception into the promise.
Expand Down Expand Up @@ -168,12 +168,13 @@ IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token)
{StateMachineType} result;
if (initialThreadId == /*managedThreadId*/ && state == StateMachineStates.FinishedStateMachine)
{
state = StateMachineStates.NotStartedStateMachine;
state = InitialState; // -3
disposeMode = false;
result = this;
}
else
{
result = new {StateMachineType}(StateMachineStates.NotStartedStateMachine);
result = new {StateMachineType}(InitialState);
}
/* copy all of the parameter proxies */
}
Expand All @@ -197,26 +198,26 @@ In contrast, async methods continue running autonomously until they are done. Th

In summary, disposal of an async-iterator works based on four design elements:
- `yield return` (jumps to finally when resuming in dispose mode)
- `yield break` (enters dispose mode and jumps to finally)
- `finally` (after a `finally` we jump to the next one)
- `yield break` (enters dispose mode and jumps to enclosing finally)
- `finally` (after a `finally` we jump to the next enclosing one)
- `DisposeAsync` (enters dispose mode and resumes execution)

The caller of an async-iterator method should only call `DisposeAsync()` when the method completed or was suspended by a `yield return`.
`DisposeAsync` sets a flag on the state machine ("dispose mode") and (if the method wasn't completed) resumes the execution from the current state.
The state machine can resume execution from a given state (even those located within a `try`).
When the execution is resumed in dispose mode, it jumps straight to the relevant `finally`.
When the execution is resumed in dispose mode, it jumps straight to the enclosing `finally`.
`finally` blocks may involve pauses and resumes, but only for `await` expressions. As a result of the restrictions imposed on `yield return` (described above), dispose mode never runs into a `yield return`.
Once a `finally` block completes, the execution in dispose mode jumps to the next relevant `finally`, or the end of the method once we reach the top-level.
Once a `finally` block completes, the execution in dispose mode jumps to the next enclosing `finally`, or the end of the method once we reach the top-level.

Reaching a `yield break` also sets the dispose mode flag and jumps to the next relevant `finally` (or end of the method).
Reaching a `yield break` also sets the dispose mode flag and jumps to the enclosing `finally` (or end of the method).
By the time we return control to the caller (completing the promise as `false` by reaching the end of the method) all disposal was completed,
and the state machine is left in finished state. So `DisposeAsync()` has no work left to do.

Looking at disposal from the perspective of a given `finally` block, the code in that block can get executed:
- by normal execution (ie. after the code in the `try` block),
- by raising an exception inside the `try` block (which will execute the necessary `finally` blocks and terminate the method in Finished state),
- by calling `DisposeAsync()` (which resumes execution in dispose mode and jumps to the relevant finally),
- following a `yield break` (which enters dispose mode and jumps to the relevant finally),
- by calling `DisposeAsync()` (which resumes execution in dispose mode and jumps to the enclosing finally),
- following a `yield break` (which enters dispose mode and jumps to the enclosing finally),
- in dispose mode, following a nested `finally`.

A `yield return` is lowered as:
Expand All @@ -240,16 +241,19 @@ disposeMode = true;
```C#
ValueTask IAsyncDisposable.DisposeAsync()
{
disposeMode = true;
if (state == StateMachineStates.FinishedStateMachine ||
state == StateMachineStates.NotStartedStateMachine)
if (state >= StateMachineStates.NotStartedStateMachine /* -1 */)
{
throw new NotSupportedException();
}
if (state == StateMachineStates.FinishedStateMachine /* -2 */)
{
return default;
}
disposeMode = true;
_valueOrEndPromise.Reset();
var inst = this;
_builder.Start(ref inst);
return new ValueTask(this, _valueOrEndPromise.Version); // note this leverages the state machine's implementation of IValueTaskSource
return new ValueTask(this, _valueOrEndPromise.Version); // note this leverages the state machine's implementation of IValueTaskSource
}
```

Expand Down Expand Up @@ -286,39 +290,46 @@ finallyEntryLabel:
}
```

In both cases, we will add a `if (disposeMode) /* jump to next finally or exit */` after the block for `finally` logic.
In both cases, we will add a `if (disposeMode) /* jump to enclosing finally or exit */` after the block for `finally` logic.

#### State values and transitions

The enumerable starts with state -2.
Calling GetAsyncEnumerator sets the state to -1, or returns a fresh enumerator (also with state -1).
Calling GetAsyncEnumerator sets the state to -3, or returns a fresh enumerator (also with state -3).

From there, MoveNext will either:
- reach the end of the method (-2)
- reach a `yield break` (-1, dispose mode = true)
- reach a `yield return` or `await` (N)
- reach the end of the method (-2, we're done and disposed)
- reach a `yield break` (state unchanged, dispose mode = true)
- reach a `yield return` (-N, decreasing from -4)
- reach an `await` (N, increasing from 0)

From suspended state N, MoveNext will resume execution (-1).
But if the suspension was a `yield return`, you could also call DisposeAsync, which resumes execution (-1) in dispose mode.
From suspended state N or -N, MoveNext will resume execution (-1).
But if the suspension was a `yield return` (-N), you could also call DisposeAsync, which resumes execution (-1) in dispose mode.

When in dispose mode, MoveNext continues to suspend (N) and resume (-1) until the end of the method is reached (-2).

The result of invoking `DisposeAsync` from states -1 or N is unspecified. This compiler throws `NotSupportException` for those cases.

```
GetAsyncEnumerator suspension (yield return, await)
-2 -----------------> -1 -------------------------------> N
^ | ^ | Dispose mode = false
| done and disposed | | resuming |
+-------------------+ +---------------------------------+
| | |
| | |
| yield | |
| break | DisposeAsync |
| | +---------------------------------+
| | |
| | |
| done and disposed v v suspension (await)
+------------------- -1 -------------------------------> N
^ | Dispose mode = true
| resuming |
+---------------------------------+
DisposeAsync await
+------------------------+ +------------------------> N
| | | |
v GetAsyncEnumerator | | resuming |
-2 --------------------> -3 --------> -1 <-------------------------+ Dispose mode = false
^ | | |
| done and disposed | | yield return |
+-----------------------------------+ +-----------------------> -N
| | |
| | |
| yield | |
| break | DisposeAsync |
| | +--------------------------+
| | |
| | |
| done and disposed v v suspension (await)
+----------------------------------- -1 ------------------------> N
^ | Dispose mode = true
| resuming |
+--------------------------+
```

35 changes: 21 additions & 14 deletions src/Compilers/CSharp/Portable/Binder/Binder_NameConflicts.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;

namespace Microsoft.CodeAnalysis.CSharp
Expand All @@ -33,6 +28,7 @@ private static Location GetLocation(Symbol symbol)
internal void ValidateParameterNameConflicts(
ImmutableArray<TypeParameterSymbol> typeParameters,
ImmutableArray<ParameterSymbol> parameters,
bool allowShadowingNames,
DiagnosticBag diagnostics)
{
PooledHashSet<string> tpNames = null;
Expand All @@ -51,7 +47,7 @@ internal void ValidateParameterNameConflicts(
{
// Type parameter declaration name conflicts are detected elsewhere
}
else
else if (!allowShadowingNames)
{
ValidateDeclarationNameConflictsInScope(tp, diagnostics);
}
Expand Down Expand Up @@ -81,7 +77,7 @@ internal void ValidateParameterNameConflicts(
// The parameter name '{0}' is a duplicate
diagnostics.Add(ErrorCode.ERR_DuplicateParamName, GetLocation(p), name);
}
else
else if (!allowShadowingNames)
{
ValidateDeclarationNameConflictsInScope(p, diagnostics);
}
Expand All @@ -95,30 +91,41 @@ internal void ValidateParameterNameConflicts(
/// <remarks>
/// Don't call this one directly - call one of the helpers.
/// </remarks>
protected bool ValidateNameConflictsInScope(Symbol symbol, Location location, string name, DiagnosticBag diagnostics)
private bool ValidateNameConflictsInScope(Symbol symbol, Location location, string name, DiagnosticBag diagnostics)
{
if (string.IsNullOrEmpty(name))
{
return false;
}

bool error = false;
bool allowShadowing = Compilation.IsFeatureEnabled(MessageID.IDS_FeatureStaticLocalFunctions);

for (Binder binder = this; binder != null; binder = binder.Next)
{
// no local scopes enclose members
if (binder is InContainerBinder || error)
if (binder is InContainerBinder)
{
break;
return false;
}

var scope = binder as LocalScopeBinder;
if (scope != null)
if (scope?.EnsureSingleDefinition(symbol, name, location, diagnostics) == true)
{
return true;
}

// If shadowing is enabled, avoid checking for conflicts outside of local functions.
if (allowShadowing)
{
error |= scope.EnsureSingleDefinition(symbol, name, location, diagnostics);
var containingMethod = (binder as InMethodBinder)?.ContainingMemberOrLambda as MethodSymbol;
if (containingMethod?.MethodKind == MethodKind.LocalFunction)
{
return false;
}
}
}

return error;
return false;
}
}
}
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ internal BoundConstantPattern BindConstantPattern(
convertedExpression = new BoundConversion(
convertedExpression.Syntax, convertedExpression, Conversion.NoConversion, isBaseConversion: false, @checked: false,
explicitCastInCode: false, constantValueOpt: constantValueOpt, conversionGroupOpt: default, type: CreateErrorType(), hasErrors: true)
{ WasCompilerGenerated = true };
{ WasCompilerGenerated = true };
}

return new BoundConstantPattern(node, convertedExpression, constantValueOpt ?? ConstantValue.Bad, inputType, hasErrors);
Expand Down Expand Up @@ -796,7 +796,7 @@ private static FieldSymbol CheckIsTupleElement(SyntaxNode node, NamedTypeSymbol

if (foundElement is null || foundElement.TupleElementIndex != tupleIndex)
{
diagnostics.Add(ErrorCode.ERR_TupleElementNameMismatch, node.Location, name, $"Item{tupleIndex+1}");
diagnostics.Add(ErrorCode.ERR_TupleElementNameMismatch, node.Location, name, $"Item{tupleIndex + 1}");
}

return foundElement;
Expand Down
Loading