private ForEachEnumeratorInfo(TypeSymbol iterableType, TypeSymbolWithAnnotations elementType, TypeSymbol iteratorType, MethodSymbol iterateBegin, MethodSymbol iterateHasCurrent, MethodSymbol iterateCurrent, MethodSymbol iterateNext, MethodSymbol iterateEnd, bool needsDisposal, AwaitableInfo disposeAwaitableInfo, BinderFlags location) { Debug.Assert((object)iterableType != null, "Field 'collectionType' cannot be null"); Debug.Assert(!elementType.IsNull, "Field 'elementType' cannot be null"); Debug.Assert((object)iteratorType != null, "Field 'stateType' cannot be null"); Debug.Assert((object)iterateBegin != null, "Field 'iterateBegin' cannot be null"); Debug.Assert((object)iterateNext != null, "Field 'iterateItem' cannot be null"); Debug.Assert((object)iterateHasCurrent != null, "Field 'iterateNext' cannot be null"); Debug.Assert((object)iterateEnd != null, "Field 'iterateEnd' cannot be null"); this.IterableType = iterableType; this.ElementType = elementType; this.IteratorType = iteratorType; this.IterateBegin = iterateBegin; this.IterateHasCurrent = iterateHasCurrent; this.IterateCurrent = iterateCurrent; this.IterateNext = iterateNext; this.IterateEnd = iterateEnd; this.NeedsDisposal = needsDisposal; this.DisposeAwaitableInfo = disposeAwaitableInfo; this.Location = location; }
private ForEachEnumeratorInfo( TypeSymbol collectionType, TypeSymbolWithAnnotations elementType, MethodSymbol getEnumeratorMethod, MethodSymbol currentPropertyGetter, MethodSymbol moveNextMethod, bool needsDisposal, AwaitableInfo disposeAwaitableInfo, MethodSymbol disposeMethod, Conversion collectionConversion, Conversion currentConversion, Conversion enumeratorConversion, BinderFlags location) { Debug.Assert((object)collectionType != null, "Field 'collectionType' cannot be null"); Debug.Assert(!elementType.IsNull, "Field 'elementType' cannot be null"); Debug.Assert((object)getEnumeratorMethod != null, "Field 'getEnumeratorMethod' cannot be null"); Debug.Assert((object)currentPropertyGetter != null, "Field 'currentPropertyGetter' cannot be null"); Debug.Assert((object)moveNextMethod != null, "Field 'moveNextMethod' cannot be null"); this.CollectionType = collectionType; this.ElementType = elementType; this.GetEnumeratorMethod = getEnumeratorMethod; this.CurrentPropertyGetter = currentPropertyGetter; this.MoveNextMethod = moveNextMethod; this.NeedsDisposal = needsDisposal; this.DisposeAwaitableInfo = disposeAwaitableInfo; this.DisposeMethod = disposeMethod; this.CollectionConversion = collectionConversion; this.CurrentConversion = currentConversion; this.EnumeratorConversion = enumeratorConversion; this.Location = location; }
private BoundAwaitExpression BindAwait(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics) { bool hasErrors = false; AwaitableInfo info = BindAwaitInfo(expression, node, node.Location, diagnostics, ref hasErrors); // Spec 7.7.7.2: // The expression await t is classified the same way as the expression (t).GetAwaiter().GetResult(). Thus, // if the return type of GetResult is void, the await-expression is classified as nothing. If it has a // non-void return type T, the await-expression is classified as a value of type T. TypeSymbol awaitExpressionType = info.GetResult?.ReturnType.TypeSymbol ?? (hasErrors ? CreateErrorType() : Compilation.DynamicType); return(new BoundAwaitExpression(node, expression, info, awaitExpressionType, hasErrors)); }
private BoundStatement MakeDeclarationUsingStatement(SyntaxNode syntax, BoundBlock body, ImmutableArray <LocalSymbol> locals, BoundLocalDeclaration declaration, Conversion iDisposableConversion, MethodSymbol disposeMethodOpt, AwaitableInfo awaitOpt, SyntaxToken awaitKeyword) { Debug.Assert(declaration != null); BoundBlock result = body; result = RewriteDeclarationUsingStatement(syntax, declaration, result, iDisposableConversion, awaitKeyword, awaitOpt, disposeMethodOpt); // Declare all locals in a single, top-level block so that the scope is correct in the debugger // (Dev10 has them all come into scope at once, not per-declaration.) return(new BoundBlock( syntax, locals, ImmutableArray.Create <BoundStatement>(result))); }
private BoundExpression GenerateDisposeCall(SyntaxNode syntax, BoundExpression disposedExpression, MethodSymbol methodOpt, AwaitableInfo awaitOpt, SyntaxToken awaitKeyword) { Debug.Assert(awaitOpt is null || awaitKeyword != default); // If we don't have an explicit dispose method, try and get the special member for IDiposable/IAsyncDisposable if (methodOpt is null) { if (awaitOpt is null) { // IDisposable.Dispose() Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, syntax, _diagnostics, out methodOpt); } else { // IAsyncDisposable.DisposeAsync() TryGetWellKnownTypeMember(syntax: null, WellKnownMember.System_IAsyncDisposable__DisposeAsync, out methodOpt, location: awaitKeyword.GetLocation()); } } BoundExpression disposeCall; if (methodOpt is null) { disposeCall = new BoundBadExpression(syntax, LookupResultKind.NotInvocable, ImmutableArray <Symbol> .Empty, ImmutableArray.Create(disposedExpression), ErrorTypeSymbol.UnknownResultType); } else { disposeCall = MakeCallWithNoExplicitArgument(syntax, disposedExpression, methodOpt); if (!(awaitOpt is null)) { // await local.DisposeAsync() _sawAwaitInExceptionHandler = true; TypeSymbol awaitExpressionType = awaitOpt.GetResult?.ReturnType.TypeSymbol ?? _compilation.DynamicType; disposeCall = RewriteAwaitExpression(syntax, disposeCall, awaitOpt, awaitExpressionType, false); } } return(disposeCall); }
private BoundStatement RewriteUsingStatementTryFinally(SyntaxNode syntax, BoundBlock tryBlock, BoundLocal local, SyntaxToken awaitKeywordOpt, AwaitableInfo awaitOpt, MethodSymbol methodOpt) { // SPEC: When ResourceType is a non-nullable value type, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when Resource type is a nullable value type or // SPEC: a reference type other than dynamic, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { if (resource != null) ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when ResourceType is dynamic, the expansion is: // SPEC: { // SPEC: dynamic resource = expr; // SPEC: IDisposable d = (IDisposable)resource; // SPEC: try { statement; } // SPEC: finally { if (d != null) d.Dispose(); } // SPEC: } // SPEC: // SPEC: An implementation is permitted to implement a given using statement // SPEC: differently -- for example, for performance reasons -- as long as the // SPEC: behavior is consistent with the above expansion. // // In the case of using-await statement, we'll use "IAsyncDisposable" instead of "IDisposable", "await DisposeAsync()" instead of "Dispose()" // // And we do in fact generate the code slightly differently than precisely how it is // described above. // // First: if the type is a non-nullable value type then we do not do the // *boxing conversion* from the resource to IDisposable. Rather, we do // a *constrained virtual call* that elides the boxing if possible. // // Now, you might wonder if that is legal; isn't skipping the boxing producing // an observable difference? Because if the value type is mutable and the Dispose // mutates it, then skipping the boxing means that we are now mutating the original, // not the boxed copy. But this is never observable. Either (1) we have "using(R r = x){}" // and r is out of scope after the finally, so it is not possible to observe the mutation, // or (2) we have "using(x) {}". But that has the semantics of "using(R temp = x){}", // so again, we are not mutating x to begin with; we're always mutating a copy. Therefore // it doesn't matter if we skip making *a copy of the copy*. // // This is what the dev10 compiler does, and we do so as well. // // Second: if the type is a nullable value type then we can similarly elide the boxing. // We can generate // // { // ResourceType resource = expr; // try { statement; } // finally { if (resource.HasValue) resource.GetValueOrDefault().Dispose(); } // } // // Where again we do a constrained virtual call to Dispose, rather than boxing // the value to IDisposable. // // Note that this optimization is *not* what the native compiler does; in this case // the native compiler behavior is to test for HasValue, then *box* and convert // the boxed value to IDisposable. There's no need to do that. // // Third: if we have "using(x)" and x is dynamic then obviously we need not generate // "{ dynamic temp1 = x; IDisposable temp2 = (IDisposable) temp1; ... }". Rather, we elide // the completely unnecessary first temporary. Debug.Assert((awaitKeywordOpt == default) == (awaitOpt == default(AwaitableInfo))); BoundExpression disposedExpression; bool isNullableValueType = local.Type.IsNullableType(); if (isNullableValueType) { MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, local.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // local.GetValueOrDefault() disposedExpression = BoundCall.Synthesized(syntax, local, getValueOrDefault); } else { // local disposedExpression = local; } BoundExpression disposeCall = GenerateDisposeCall(syntax, disposedExpression, methodOpt, awaitOpt, awaitKeywordOpt); // local.Dispose(); or await variant BoundStatement disposeStatement = new BoundExpressionStatement(syntax, disposeCall); BoundExpression ifCondition; if (isNullableValueType) { // local.HasValue ifCondition = MakeNullableHasValue(syntax, local); } else if (local.Type.IsValueType) { ifCondition = null; } else { // local != null ifCondition = MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); } BoundStatement finallyStatement; if (ifCondition == null) { // local.Dispose(); or await variant finallyStatement = disposeStatement; } else { // if (local != null) local.Dispose(); // or // if (local.HasValue) local.GetValueOrDefault().Dispose(); // or // await variants finallyStatement = RewriteIfStatement( syntax: syntax, rewrittenCondition: ifCondition, rewrittenConsequence: disposeStatement, rewrittenAlternativeOpt: null, hasErrors: false); } // try { ... } finally { if (local != null) local.Dispose(); } // or // nullable or await variants BoundStatement tryFinally = new BoundTryStatement( syntax: syntax, tryBlock: tryBlock, catchBlocks: ImmutableArray <BoundCatchBlock> .Empty, finallyBlockOpt: BoundBlock.SynthesizedNoLocals(syntax, finallyStatement)); return(tryFinally); }
/// <summary> /// Lower "using [await] (ResourceType resource = expression) statement" to a try-finally block. /// </summary> /// <remarks> /// Assumes that the local symbol will be declared (i.e. in the LocalsOpt array) of an enclosing block. /// Assumes that using statements with multiple locals have already been split up into multiple using statements. /// </remarks> private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, BoundLocalDeclaration localDeclaration, BoundBlock tryBlock, Conversion iDisposableConversion, SyntaxToken awaitKeywordOpt, AwaitableInfo awaitOpt, MethodSymbol methodSymbol) { SyntaxNode declarationSyntax = localDeclaration.Syntax; LocalSymbol localSymbol = localDeclaration.LocalSymbol; TypeSymbol localType = localSymbol.Type.TypeSymbol; Debug.Assert((object)localType != null); //otherwise, there wouldn't be a conversion to IDisposable BoundLocal boundLocal = new BoundLocal(declarationSyntax, localSymbol, localDeclaration.InitializerOpt.ConstantValue, localType); BoundStatement rewrittenDeclaration = (BoundStatement)Visit(localDeclaration); // If we know that the expression is null, then we know that the null check in the finally block // will fail, and the Dispose call will never happen. That is, the finally block will have no effect. // Consequently, we can simply skip the whole try-finally construct and just create a block containing // the new declaration. if (boundLocal.ConstantValue == ConstantValue.Null) { //localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryBlock)); } if (localType.IsDynamic()) { TypeSymbol iDisposableType = awaitOpt is null? _compilation.GetSpecialType(SpecialType.System_IDisposable) : _compilation.GetWellKnownType(WellKnownType.core_IAsyncDisposable); BoundExpression tempInit = MakeConversionNode( declarationSyntax, boundLocal, iDisposableConversion, iDisposableType, @checked: false); BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(tempInit, out tempAssignment, kind: SynthesizedLocalKind.Using); BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, awaitKeywordOpt, awaitOpt, methodSymbol); return(new BoundBlock( syntax: usingSyntax, locals: ImmutableArray.Create <LocalSymbol>(boundTemp.LocalSymbol), //localSymbol will be declared by an enclosing block statements: ImmutableArray.Create <BoundStatement>( rewrittenDeclaration, new BoundExpressionStatement(declarationSyntax, tempAssignment), tryFinally))); } else { BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal, awaitKeywordOpt, awaitOpt, methodSymbol); // localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryFinally)); } }
private BoundExpression RewriteAwaitExpression(SyntaxNode syntax, BoundExpression rewrittenExpression, AwaitableInfo awaitableInfo, TypeSymbol type, bool used) { return(RewriteAwaitExpression(new BoundAwaitExpression(syntax, rewrittenExpression, awaitableInfo, type) { WasCompilerGenerated = true }, used)); }
/// <summary> /// Lower "using [await] (ResourceType resource = expression) statement" to a try-finally block. /// </summary> /// <remarks> /// Assumes that the local symbol will be declared (i.e. in the LocalsOpt array) of an enclosing block. /// Assumes that using statements with multiple locals have already been split up into multiple using statements. /// </remarks> private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, BoundLocalDeclaration localDeclaration, BoundBlock tryBlock, Conversion iDisposableConversion, SyntaxToken awaitKeywordOpt, AwaitableInfo awaitOpt, MethodSymbol methodSymbol) { SyntaxNode declarationSyntax = localDeclaration.Syntax; LocalSymbol localSymbol = localDeclaration.LocalSymbol; TypeSymbol localType = localSymbol.Type.TypeSymbol; Debug.Assert((object)localType != null); //otherwise, there wouldn't be a conversion to IDisposable BoundLocal boundLocal = new BoundLocal(declarationSyntax, localSymbol, localDeclaration.InitializerOpt.ConstantValue, localType); BoundStatement rewrittenDeclaration = (BoundStatement)Visit(localDeclaration); // If we know that the expression is null, then we know that the null check in the finally block // will fail, and the Dispose call will never happen. That is, the finally block will have no effect. // Consequently, we can simply skip the whole try-finally construct and just create a block containing // the new declaration. if (boundLocal.ConstantValue == ConstantValue.Null) { //localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryBlock)); } BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal, awaitKeywordOpt, awaitOpt, methodSymbol); // localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryFinally)); }
internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNode syntax, SyntaxToken usingKeyword, SyntaxToken awaitKeyword, Binder originalBinder, UsingStatementBinder usingBinderOpt, DiagnosticBag diagnostics) { bool isUsingDeclaration = syntax.Kind() == SyntaxKind.LocalDeclarationStatement; bool isExpression = !isUsingDeclaration && syntax.Kind() != SyntaxKind.VariableDeclaration; bool hasAwait = awaitKeyword != default; Debug.Assert(isUsingDeclaration || usingBinderOpt != null); TypeSymbol disposableInterface = getDisposableInterface(hasAwait); Debug.Assert((object)disposableInterface != null); bool hasErrors = ReportUseSiteDiagnostics(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); Conversion iDisposableConversion = Conversion.NoConversion; BoundLocalDeclaration declarationOpt = default; BoundExpression expressionOpt = null; AwaitableInfo awaitOpt = null; TypeSymbol declarationTypeOpt = null; MethodSymbol disposeMethodOpt = null; TypeSymbol awaitableTypeOpt = null; if (isExpression) { expressionOpt = usingBinderOpt.BindTargetExpression(diagnostics, originalBinder); hasErrors |= !populateDisposableConversionOrDisposeMethod(fromExpression: true); } else { VariableDeclarationSyntax declarationSyntax = isUsingDeclaration ? ((LocalDeclarationStatementSyntax)syntax).Declaration : (VariableDeclarationSyntax)syntax; declarationOpt = originalBinder.BindForOrUsingOrFixedDeclarations(declarationSyntax, LocalDeclarationKind.UsingVariable, diagnostics); Debug.Assert(declarationOpt != null); declarationTypeOpt = declarationOpt.DeclaredType.Type; hasErrors |= !populateDisposableConversionOrDisposeMethod(fromExpression: false); } if (hasAwait) { BoundAwaitableValuePlaceholder placeholderOpt; if (awaitableTypeOpt is null) { placeholderOpt = null; } else { hasErrors |= ReportUseSiteDiagnostics(awaitableTypeOpt, diagnostics, awaitKeyword); placeholderOpt = new BoundAwaitableValuePlaceholder(syntax, awaitableTypeOpt).MakeCompilerGenerated(); } // even if we don't have a proper value to await, we'll still report bad usages of `await` awaitOpt = originalBinder.BindAwaitInfo(placeholderOpt, syntax, awaitKeyword.GetLocation(), diagnostics, ref hasErrors); } // This is not awesome, but its factored. // In the future it might be better to have a seperate shared type that we add the info to, and have the callers create the appropriate bound nodes from it if (isUsingDeclaration) { return(new BoundUsingLocalDeclaration(syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, declarationOpt, hasErrors)); } else { BoundStatement boundBody = originalBinder.BindPossibleEmbeddedStatement(usingBinderOpt._syntax.Statement, diagnostics); return(new BoundUsingStatement( usingBinderOpt._syntax, usingBinderOpt.Locals, declarationOpt, expressionOpt, iDisposableConversion, boundBody, awaitOpt, disposeMethodOpt, hasErrors)); } // initializes iDisposableConversion, awaitableTypeOpt and disposeMethodOpt bool populateDisposableConversionOrDisposeMethod(bool fromExpression) { HashSet <DiagnosticInfo> useSiteDiagnostics = null; iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteDiagnostics); diagnostics.Add(syntax, useSiteDiagnostics); if (iDisposableConversion.IsImplicit) { if (hasAwait) { awaitableTypeOpt = originalBinder.Compilation.GetWellKnownType(WellKnownType.core_Threading_Tasks_ValueTask); } return(true); } TypeSymbol type = fromExpression ? expressionOpt.Type : declarationTypeOpt; // If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern. // We won't need to try and bind a second time if it fails, as async dispose can't be pattern based (ref structs are not allowed in async methods) if (!(type is null) && (type.IsRefLikeType || hasAwait)) { BoundExpression receiver = fromExpression ? expressionOpt : new BoundLocal(syntax, declarationOpt.LocalSymbol, null, type) { WasCompilerGenerated = true }; disposeMethodOpt = originalBinder.TryFindDisposePatternMethod(receiver, syntax, hasAwait, diagnostics); if (!(disposeMethodOpt is null)) { if (hasAwait) { awaitableTypeOpt = disposeMethodOpt.ReturnType.TypeSymbol; } return(true); } } if (type is null || !type.IsErrorType()) { // Retry with a different assumption about whether the `using` is async TypeSymbol alternateInterface = getDisposableInterface(!hasAwait); HashSet <DiagnosticInfo> ignored = null; Conversion alternateConversion = classifyConversion(fromExpression, alternateInterface, ref ignored); bool wrongAsync = alternateConversion.IsImplicit; ErrorCode errorCode = wrongAsync ? (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDispWrongAsync : ErrorCode.ERR_NoConvToIDispWrongAsync) : (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDisp : ErrorCode.ERR_NoConvToIDisp); Error(diagnostics, errorCode, syntax, declarationTypeOpt ?? expressionOpt.Display); } return(false); } Conversion classifyConversion(bool fromExpression, TypeSymbol targetInterface, ref HashSet <DiagnosticInfo> diag) { return(fromExpression ? originalBinder.Conversions.ClassifyImplicitConversionFromExpression(expressionOpt, targetInterface, ref diag) : originalBinder.Conversions.ClassifyImplicitConversionFromType(declarationTypeOpt, targetInterface, ref diag)); } TypeSymbol getDisposableInterface(bool isAsync) { return(isAsync ? originalBinder.Compilation.GetWellKnownType(WellKnownType.core_IAsyncDisposable) : originalBinder.Compilation.GetSpecialType(SpecialType.System_IDisposable)); } }
internal AwaitExpressionInfo(AwaitableInfo awaitableInfo) { Debug.Assert(awaitableInfo != null); _awaitableInfo = awaitableInfo; }