Skip to content

Commit

Permalink
Merge pull request #32321 from dotnet/merges/dev16.0-preview2-to-master
Browse files Browse the repository at this point in the history
Merge dev16.0-preview2 to master
  • Loading branch information
dotnet-automerge-bot committed Jan 10, 2019
2 parents 4ca8c46 + e70424c commit 8d00d4f
Show file tree
Hide file tree
Showing 28 changed files with 762 additions and 203 deletions.
8 changes: 5 additions & 3 deletions docs/features/async-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ An asynchronous `using` is lowered just like a regular `using`, except that `Dis
### Detailed design for `await foreach` statement

An `await foreach` is lowered just like a regular `foreach`, except that:
- `GetEnumerator()` is replaced with `await GetAsyncEnumerator(default)`
- `GetEnumerator()` is replaced with `await GetAsyncEnumerator()`
- `MoveNext()` is replaced with `await MoveNextAsync()`
- `Dispose()` is replaced with `await DisposeAsync()`

Note that pattern-based lookup for `GetAsyncEnumerator` and `MoveNextAsync` do not place particular requirements on those methods,
as long as they could be invoked without arguments.

Asynchronous foreach loops are disallowed on collections of type dynamic,
as there is no asynchronous equivalent of the non-generic `IEnumerable` interface.

The `CancellationToken` is always passed as `default` by the `await foreach` statement.
But wrapper types can pass non-default values (see `.WithCancellation(CancellationToken)` extension method),
thereby allowing consumers of async-streams to control cancellation.
A producer of async-streams can make use of the cancellation token by writing an
Expand Down Expand Up @@ -308,7 +310,7 @@ But if the suspension was a `yield return` (-N), you could also call DisposeAsyn

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.
The result of invoking `DisposeAsync` from states -1 or N is unspecified. This compiler generates `throw new NotSupportException()` for those cases.

```
DisposeAsync await
Expand Down
31 changes: 9 additions & 22 deletions src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -900,19 +900,7 @@ private bool SatisfiesGetEnumeratorPattern(ref ForEachEnumeratorInfo.Builder bui
{
var lookupResult = LookupResult.GetInstance();
string methodName = isAsync ? GetAsyncEnumeratorMethodName : GetEnumeratorMethodName;

ImmutableArray<BoundExpression> arguments;
if (isAsync)
{
var cancellationTokenType = Compilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken);
arguments = ImmutableArray.Create<BoundExpression>(new BoundAwaitableValuePlaceholder(_syntax, cancellationTokenType));
}
else
{
arguments = ImmutableArray<BoundExpression>.Empty;
}

MethodSymbol getEnumeratorMethod = FindForEachPatternMethod(collectionExprType, methodName, lookupResult, warningsOnly: true, diagnostics: diagnostics, isAsync: isAsync, arguments);
MethodSymbol getEnumeratorMethod = FindForEachPatternMethod(collectionExprType, methodName, lookupResult, warningsOnly: true, diagnostics: diagnostics, isAsync: isAsync);
lookupResult.Free();

builder.GetEnumeratorMethod = getEnumeratorMethod;
Expand All @@ -929,10 +917,9 @@ private bool SatisfiesGetEnumeratorPattern(ref ForEachEnumeratorInfo.Builder bui
/// <param name="warningsOnly">True if failures should result in warnings; false if they should result in errors.</param>
/// <param name="diagnostics">Populated with binding diagnostics.</param>
/// <returns>The desired method or null.</returns>
private MethodSymbol FindForEachPatternMethod(TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync, ImmutableArray<BoundExpression> arguments)
private MethodSymbol FindForEachPatternMethod(TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync)
{
Debug.Assert(lookupResult.IsClear);
Debug.Assert(!arguments.IsDefault);

// Not using LookupOptions.MustBeInvocableMember because we don't want the corresponding lookup error.
// We filter out non-methods below.
Expand Down Expand Up @@ -977,13 +964,16 @@ private MethodSymbol FindForEachPatternMethod(TypeSymbol patternType, string met
// some custom logic in ExpressionBinder.BindGrpToParams. The biggest difference
// we've found (so far) is that it only considers methods with expected number of parameters
// (i.e. doesn't work with "params" or optional parameters).
if (method.ParameterCount == arguments.Length)

// Note: for pattern-based lookup for `await foreach` we accept `GetAsyncEnumerator` and
// `MoveNextAsync` methods with optional/params parameters.
if (method.ParameterCount == 0 || isAsync)
{
candidateMethods.Add((MethodSymbol)member);
}
}

MethodSymbol patternMethod = PerformForEachPatternOverloadResolution(patternType, candidateMethods, warningsOnly, diagnostics, isAsync, arguments);
MethodSymbol patternMethod = PerformForEachPatternOverloadResolution(patternType, candidateMethods, warningsOnly, diagnostics, isAsync);

candidateMethods.Free();

Expand All @@ -994,12 +984,9 @@ private MethodSymbol FindForEachPatternMethod(TypeSymbol patternType, string met
/// The overload resolution portion of FindForEachPatternMethod.
/// If no arguments are passed in, then an empty argument list will be used.
/// </summary>
private MethodSymbol PerformForEachPatternOverloadResolution(TypeSymbol patternType, ArrayBuilder<MethodSymbol> candidateMethods, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync, ImmutableArray<BoundExpression> arguments)
private MethodSymbol PerformForEachPatternOverloadResolution(TypeSymbol patternType, ArrayBuilder<MethodSymbol> candidateMethods, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync)
{
Debug.Assert(!arguments.IsDefault);
var analyzedArguments = AnalyzedArguments.GetInstance();
analyzedArguments.Arguments.AddRange(arguments);

var typeArguments = ArrayBuilder<TypeSymbolWithAnnotations>.GetInstance();
var overloadResolutionResult = OverloadResolutionResult<MethodSymbol>.GetInstance();

Expand Down Expand Up @@ -1152,7 +1139,7 @@ private bool SatisfiesForEachPattern(ref ForEachEnumeratorInfo.Builder builder,

MethodSymbol moveNextMethodCandidate = FindForEachPatternMethod(enumeratorType,
isAsync ? MoveNextAsyncMethodName : MoveNextMethodName,
lookupResult, warningsOnly: false, diagnostics: diagnostics, isAsync: isAsync, arguments: ImmutableArray<BoundExpression>.Empty);
lookupResult, warningsOnly: false, diagnostics: diagnostics, isAsync: isAsync);

if ((object)moveNextMethodCandidate == null ||
moveNextMethodCandidate.IsStatic || moveNextMethodCandidate.DeclaredAccessibility != Accessibility.Public ||
Expand Down
29 changes: 18 additions & 11 deletions src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,28 +137,32 @@ internal override TypeSymbol GetIteratorElementType(YieldStatementSyntax node, D
// and deduce an iterator element type from the return type. If we didn't do this, the
// TypeInfo.ConvertedType of the yield statement would always be an error type. However, we will
// not mutate any state (i.e. we won't store the result).
return GetIteratorElementTypeFromReturnType(refKind, returnType, node, diagnostics) ?? CreateErrorType();
return GetIteratorElementTypeFromReturnType(refKind, returnType, node, diagnostics).elementType ?? CreateErrorType();
}

if (_iteratorInfo == IteratorInfo.Empty)
{
TypeSymbol elementType = null;
DiagnosticBag elementTypeDiagnostics = DiagnosticBag.GetInstance();

elementType = GetIteratorElementTypeFromReturnType(refKind, returnType, node, elementTypeDiagnostics);
(TypeSymbol elementType, bool asyncInterface) = GetIteratorElementTypeFromReturnType(refKind, returnType, node, elementTypeDiagnostics);

Location errorLocation = _methodSymbol.Locations[0];
if ((object)elementType == null)
{
if (refKind != RefKind.None)
{
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturnRef, _methodSymbol.Locations[0], _methodSymbol);
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturnRef, errorLocation, _methodSymbol);
}
else if (!returnType.IsErrorType())
{
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturn, _methodSymbol.Locations[0], _methodSymbol, returnType);
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturn, errorLocation, _methodSymbol, returnType);
}
elementType = CreateErrorType();
}
else if (asyncInterface && !_methodSymbol.IsAsync)
{
Error(elementTypeDiagnostics, ErrorCode.ERR_IteratorMustBeAsync, errorLocation, _methodSymbol, returnType);
}

var info = new IteratorInfo(elementType, elementTypeDiagnostics.ToReadOnlyAndFree());

Expand All @@ -175,12 +179,15 @@ internal override TypeSymbol GetIteratorElementType(YieldStatementSyntax node, D
return _iteratorInfo.ElementType;
}

private TypeSymbol GetIteratorElementTypeFromReturnType(RefKind refKind, TypeSymbol returnType, CSharpSyntaxNode errorLocationNode, DiagnosticBag diagnostics)
private (TypeSymbol elementType, bool asyncInterface) GetIteratorElementTypeFromReturnType(RefKind refKind, TypeSymbol returnType, CSharpSyntaxNode errorLocationNode, DiagnosticBag diagnostics)
{
return GetIteratorElementTypeFromReturnType(Compilation, refKind, returnType, errorLocationNode, diagnostics).TypeSymbol;
(TypeSymbolWithAnnotations elementType, bool asyncInterface) = GetIteratorElementTypeFromReturnType(Compilation, refKind, returnType, errorLocationNode, diagnostics);
return (elementType.TypeSymbol, asyncInterface);
}

internal static TypeSymbolWithAnnotations GetIteratorElementTypeFromReturnType(CSharpCompilation compilation, RefKind refKind, TypeSymbol returnType, CSharpSyntaxNode errorLocationNode, DiagnosticBag diagnostics)
// If an element type is found, we also return whether the interface is meant to be used with async.
internal static (TypeSymbolWithAnnotations elementType, bool asyncInterface) GetIteratorElementTypeFromReturnType(CSharpCompilation compilation,
RefKind refKind, TypeSymbol returnType, CSharpSyntaxNode errorLocationNode, DiagnosticBag diagnostics)
{
if (refKind == RefKind.None && returnType.Kind == SymbolKind.NamedType)
{
Expand All @@ -194,17 +201,17 @@ internal static TypeSymbolWithAnnotations GetIteratorElementTypeFromReturnType(C
{
ReportUseSiteDiagnostics(objectType, diagnostics, errorLocationNode);
}
return TypeSymbolWithAnnotations.Create(objectType);
return (TypeSymbolWithAnnotations.Create(objectType), false);

case SpecialType.System_Collections_Generic_IEnumerable_T:
case SpecialType.System_Collections_Generic_IEnumerator_T:
return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
return (((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0], false);
}

if (TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T), TypeCompareKind.ConsiderEverything2) ||
TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T), TypeCompareKind.ConsiderEverything2))
{
return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
return (((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0], true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,7 @@ private bool ExactOrBoundsNullableInference(ExactOrBoundsKind kind, TypeSymbolWi
return true;
}

if (s_isNullableOnly(source) && s_isNullableOnly(target))
if (isNullableOnly(source) && isNullableOnly(target))
{
ExactOrBoundsInference(kind, source.AsNotNullableReferenceType(), target.AsNotNullableReferenceType(), ref useSiteDiagnostics);
return true;
Expand All @@ -1512,7 +1512,7 @@ private bool ExactOrBoundsNullableInference(ExactOrBoundsKind kind, TypeSymbolWi
return false;

// True if the type is nullable but not an unconstrained type parameter.
bool s_isNullableOnly(TypeSymbolWithAnnotations type)
bool isNullableOnly(TypeSymbolWithAnnotations type)
=> type.NullableAnnotation.IsAnyNullable() && !type.TypeSymbol.IsTypeParameterDisallowingAnnotation();
}

Expand Down
9 changes: 9 additions & 0 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.

3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2794,6 +2794,9 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep
<data name="ERR_BadYieldInFinally" xml:space="preserve">
<value>Cannot yield in the body of a finally clause</value>
</data>
<data name="ERR_IteratorMustBeAsync" xml:space="preserve">
<value>Method '{0}' with an iterator block must be 'async' to return '{1}'</value>
</data>
<data name="ERR_BadYieldInTryOfCatch" xml:space="preserve">
<value>Cannot yield a value in the body of a try block with a catch clause</value>
</data>
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ internal enum ErrorCode
ERR_FeatureNotAvailableInVersion8 = 8400,
ERR_AltInterpolatedVerbatimStringsNotAvailable = 8401,
WRN_DefaultLiteralConvertedToNullIsNotIntended = 8402,
ERR_IteratorMustBeAsync = 8403,

ERR_NoConvToIAsyncDisp = 8410,
ERR_AwaitForEachMissingMember = 8411,
Expand Down
4 changes: 3 additions & 1 deletion src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5143,7 +5143,9 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no
return null;
}
var method = (MethodSymbol)_symbol;
TypeSymbolWithAnnotations elementType = InMethodBinder.GetIteratorElementTypeFromReturnType(compilation, RefKind.None, method.ReturnType.TypeSymbol, errorLocationNode: null, diagnostics: null);
TypeSymbolWithAnnotations elementType = InMethodBinder.GetIteratorElementTypeFromReturnType(compilation, RefKind.None,
method.ReturnType.TypeSymbol, errorLocationNode: null, diagnostics: null).elementType;

VisitOptionalImplicitConversion(expr, elementType, useLegacyWarnings: false, AssignmentKind.Return);
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ protected override BoundBinaryOperator ShouldEnterFinallyBlock()

/// <summary>
/// Lower the body, adding an entry state (-3) at the start,
/// so that we can differentiate a async-iterator that was never moved forward with MoveNextAsync()
/// so that we can differentiate an async-iterator that was never moved forward with MoveNextAsync()
/// from one that is running (-1).
/// Then we can guard against some bad usages of DisposeAsync.
/// </summary>
Expand Down
Loading

0 comments on commit 8d00d4f

Please sign in to comment.