Skip to content

Commit

Permalink
Merge pull request #32181 from dotnet/merges/master-to-master-vs-deps
Browse files Browse the repository at this point in the history
Merge master to master-vs-deps
  • Loading branch information
dotnet-automerge-bot committed Jan 5, 2019
2 parents cddb0fa + c640ee9 commit 60541a8
Show file tree
Hide file tree
Showing 104 changed files with 9,695 additions and 1,337 deletions.
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 @@ private static Location GetLocation(Symbol symbol)
{
// Type parameter declaration name conflicts are detected elsewhere
}
else
else if (!allowShadowingNames)
{
ValidateDeclarationNameConflictsInScope(tp, diagnostics);
}
Expand Down Expand Up @@ -81,7 +77,7 @@ private static Location GetLocation(Symbol symbol)
// 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 @@ private static Location GetLocation(Symbol symbol)
/// <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 @@ private BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSymbol in
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

0 comments on commit 60541a8

Please sign in to comment.