示例#1
0
        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;
        }
示例#2
0
        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;
        }
示例#3
0
        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));
            }
        }
示例#8
0
 private BoundExpression RewriteAwaitExpression(SyntaxNode syntax, BoundExpression rewrittenExpression, AwaitableInfo awaitableInfo, TypeSymbol type, bool used)
 {
     return(RewriteAwaitExpression(new BoundAwaitExpression(syntax, rewrittenExpression, awaitableInfo, type)
     {
         WasCompilerGenerated = true
     }, used));
 }
示例#9
0
        /// <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));
        }
示例#10
0
        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));
            }
        }
示例#11
0
 internal AwaitExpressionInfo(AwaitableInfo awaitableInfo)
 {
     Debug.Assert(awaitableInfo != null);
     _awaitableInfo = awaitableInfo;
 }