@@ -529,6 +529,7 @@ public string GetDebuggerDisplay()
529529 // For purpose of nullability analysis, awaits create pending branches, so async usings and foreachs do too
530530 public sealed override bool AwaitUsingAndForeachAddsPendingBranch => true;
531531
532+ [DebuggerStepThrough]
532533 protected override void EnsureSufficientExecutionStack(int recursionDepth)
533534 {
534535 if (recursionDepth > StackGuard.MaxUncheckedRecursionDepth &&
@@ -542,6 +543,7 @@ protected override void EnsureSufficientExecutionStack(int recursionDepth)
542543 base.EnsureSufficientExecutionStack(recursionDepth);
543544 }
544545
546+ [DebuggerStepThrough]
545547 protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByStackGuardException()
546548 {
547549 return true;
@@ -595,7 +597,6 @@ private static void AssertPlaceholderAllowedWithoutRegistration(BoundValuePlaceh
595597 case BoundKind.InterpolatedStringHandlerPlaceholder:
596598 case BoundKind.InterpolatedStringArgumentPlaceholder:
597599 case BoundKind.ObjectOrCollectionValuePlaceholder:
598- case BoundKind.AwaitableValuePlaceholder:
599600 return;
600601
601602 case BoundKind.ImplicitIndexerValuePlaceholder:
@@ -3598,16 +3599,43 @@ private void VisitLocalFunctionUse(LocalFunctionSymbol symbol)
35983599 public override BoundNode? VisitUsingStatement(BoundUsingStatement node)
35993600 {
36003601 DeclareLocals(node.Locals);
3601- Visit (node.AwaitOpt);
3602+ VisitAwaitableInfoForUsing (node.AwaitOpt, node.PatternDisposeInfoOpt?.Method );
36023603 return base.VisitUsingStatement(node);
36033604 }
36043605
36053606 public override BoundNode? VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node)
36063607 {
3607- Visit (node.AwaitOpt);
3608+ VisitAwaitableInfoForUsing (node.AwaitOpt, node.PatternDisposeInfoOpt?.Method );
36083609 return base.VisitUsingLocalDeclarations(node);
36093610 }
36103611
3612+ private void VisitAwaitableInfoForUsing(BoundAwaitableInfo? awaitInfo, MethodSymbol? patternDisposeMethod)
3613+ {
3614+ // Placeholder can be null in error scenarios. In such cases, nothing is filled in and errors would
3615+ // have been reported already. We can just return.
3616+ if (awaitInfo is not { AwaitableInstancePlaceholder: { } placeholder })
3617+ {
3618+ return;
3619+ }
3620+
3621+ VisitResult placeholderResult;
3622+ if (patternDisposeMethod is not null)
3623+ {
3624+ placeholderResult = new VisitResult(GetReturnTypeWithState(patternDisposeMethod), patternDisposeMethod.ReturnTypeWithAnnotations);
3625+ }
3626+ else
3627+ {
3628+ // IAsyncDisposable.DisposeAsync returns ValueTask, which is a struct; we know it can never be `ValueTask?`, as that is a different
3629+ // type that would not match the descriptor. Any other scenario either has an error already, or will report an
3630+ // error during emit when we fail to find IAsyncDisposable.DisposeAsync.
3631+ placeholderResult = new VisitResult(placeholder.Type, NullableAnnotation.NotAnnotated, NullableFlowState.NotNull);
3632+ }
3633+
3634+ AddPlaceholderReplacement(placeholder, placeholder, placeholderResult);
3635+ Visit(awaitInfo);
3636+ RemovePlaceholderReplacement(placeholder);
3637+ }
3638+
36113639 public override BoundNode? VisitFixedStatement(BoundFixedStatement node)
36123640 {
36133641 DeclareLocals(node.Locals);
@@ -12069,23 +12097,13 @@ private void VisitForEachExpression(
1206912097 // Analyze `await DisposeAsync()`
1207012098 if (enumeratorInfoOpt is { NeedsDisposal: true, DisposeAwaitableInfo: BoundAwaitableInfo awaitDisposalInfo })
1207112099 {
12072- var disposalPlaceholder = awaitDisposalInfo.AwaitableInstancePlaceholder;
12073- bool addedPlaceholder = false;
12074- if (enumeratorInfoOpt.PatternDisposeInfo is { Method: var originalDisposeMethod }) // no statically known Dispose method if doing a runtime check
12100+ var patternDisposeMethod = enumeratorInfoOpt.PatternDisposeInfo?.Method;
12101+ if (patternDisposeMethod is not null)
1207512102 {
12076- Debug.Assert(disposalPlaceholder is not null);
12077- var disposeAsyncMethod = (MethodSymbol)AsMemberOfType(reinferredGetEnumeratorMethod.ReturnType, originalDisposeMethod);
12078- var result = new VisitResult(GetReturnTypeWithState(disposeAsyncMethod), disposeAsyncMethod.ReturnTypeWithAnnotations);
12079- AddPlaceholderReplacement(disposalPlaceholder, disposalPlaceholder, result);
12080- addedPlaceholder = true;
12103+ patternDisposeMethod = (MethodSymbol)AsMemberOfType(reinferredGetEnumeratorMethod.ReturnType, patternDisposeMethod);
1208112104 }
1208212105
12083- Visit(awaitDisposalInfo);
12084-
12085- if (addedPlaceholder)
12086- {
12087- RemovePlaceholderReplacement(disposalPlaceholder!);
12088- }
12106+ VisitAwaitableInfoForUsing(awaitDisposalInfo, patternDisposeMethod);
1208912107 }
1209012108 }
1209112109
@@ -12571,7 +12589,12 @@ protected override void AfterLeftChildOfBinaryLogicalOperatorHasBeenVisited(Boun
1257112589 Visit(awaitableInfo);
1257212590 RemovePlaceholderReplacement(placeholder);
1257312591
12574- if (node.Type.IsValueType || node.HasErrors || awaitableInfo.GetResult is null)
12592+ if (awaitableInfo is { GetResult: null, RuntimeAsyncAwaitCall: not null })
12593+ {
12594+ // This is AsyncHelpers.Await. We can directly use `_visitResult`
12595+ SetResult(node, _visitResult, updateAnalyzedNullability: true, isLvalue: false);
12596+ }
12597+ else if (node.Type.IsValueType || node.HasErrors || awaitableInfo.GetResult is null)
1257512598 {
1257612599 SetNotNullResult(node);
1257712600 }
@@ -13212,8 +13235,6 @@ private void VisitThrow(BoundExpression? expr)
1321213235
1321313236 public override BoundNode? VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node)
1321413237 {
13215- // These placeholders don't always follow proper placeholder discipline yet
13216- AssertPlaceholderAllowedWithoutRegistration(node);
1321713238 VisitPlaceholderWithReplacement(node);
1321813239 return null;
1321913240 }
@@ -13235,8 +13256,55 @@ private void VisitPlaceholderWithReplacement(BoundValuePlaceholderBase node)
1323513256
1323613257 public override BoundNode? VisitAwaitableInfo(BoundAwaitableInfo node)
1323713258 {
13238- Visit(node.AwaitableInstancePlaceholder);
13239- Visit(node.GetAwaiter);
13259+ if (node is { GetAwaiter: null, GetResult: null, RuntimeAsyncAwaitCall: null, RuntimeAsyncAwaitCallPlaceholder: null })
13260+ {
13261+ if (node.AwaitableInstancePlaceholder is not null)
13262+ {
13263+ // Ensure that the placeholder has an analyzed nullability for the rewriter
13264+ SetNotNullResult(node.AwaitableInstancePlaceholder);
13265+ }
13266+
13267+ SetInvalidResult();
13268+
13269+ // Error scenario
13270+ return null;
13271+ }
13272+
13273+ Debug.Assert(node.AwaitableInstancePlaceholder is not null);
13274+
13275+ if (node.RuntimeAsyncAwaitCall is not null)
13276+ {
13277+ Debug.Assert(node.RuntimeAsyncAwaitCallPlaceholder is not null);
13278+
13279+ if (node.GetAwaiter is null)
13280+ {
13281+ // This is the simple case of just `AsyncHelpers.Await(expr)`. AwaitableInstancePlaceholder and RuntimeAsyncAwaitCallPlaceholder
13282+ // refer to the same value.
13283+ Debug.Assert(node.GetResult is null);
13284+ VisitPlaceholderWithReplacement(node.AwaitableInstancePlaceholder);
13285+ AddPlaceholderReplacement(node.RuntimeAsyncAwaitCallPlaceholder, node.AwaitableInstancePlaceholder, _visitResult);
13286+ Visit(node.RuntimeAsyncAwaitCall);
13287+ RemovePlaceholderReplacement(node.RuntimeAsyncAwaitCallPlaceholder);
13288+ }
13289+ else
13290+ {
13291+ // More complicated case of `AsyncHelpers.AwaitAwaiter/UnsafeAwaitAwaiter`. We need to visit GetAwaiter first to get the nullability
13292+ // result for the awaiter, which we then save. It is used both here, as the state of the RuntimeAsyncAwaitCallPlaceholder replacement,
13293+ // and also for the caller to be able to re-infer GetResult with nullability.
13294+ Visit(node.GetAwaiter);
13295+ var getAwaiterResult = _visitResult;
13296+ AddPlaceholderReplacement(node.RuntimeAsyncAwaitCallPlaceholder, node.GetAwaiter, getAwaiterResult);
13297+ Visit(node.RuntimeAsyncAwaitCall);
13298+ RemovePlaceholderReplacement(node.RuntimeAsyncAwaitCallPlaceholder);
13299+ _visitResult = getAwaiterResult;
13300+ }
13301+ }
13302+ else
13303+ {
13304+ // Not runtime async
13305+ Visit(node.GetAwaiter);
13306+ }
13307+
1324013308 return null;
1324113309 }
1324213310
0 commit comments