diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index ce6fe2db6ef18..70439c0b547dc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3101,5 +3101,201 @@ internal virtual ImmutableArray Labels return ImmutableArray.Empty; } } + + /// + /// Perform a lookup for the specified method on the specified type. Perform overload resolution + /// on the lookup results. + /// + /// Type to search. + /// Method to search for. + /// Passed in for reusability. + /// True if failures should result in warnings; false if they should result in errors. + /// Populated with binding diagnostics. + /// The desired method or null. + internal MethodSymbol FindPatternMethod(TypeSymbol patternType, string methodName, LookupResult lookupResult, + SyntaxNode syntaxExpr, bool warningsOnly, DiagnosticBag diagnostics, + SyntaxTree syntaxTree, MessageID messageID) + { + Debug.Assert(lookupResult.IsClear); + + // Not using LookupOptions.MustBeInvocableMember because we don't want the corresponding lookup error. + // We filter out non-methods below. + HashSet useSiteDiagnostics = null; + this.LookupMembersInType( + lookupResult, + patternType, + methodName, + arity: 0, + basesBeingResolved: null, + options: LookupOptions.Default, + originalBinder: this, + diagnose: false, + useSiteDiagnostics: ref useSiteDiagnostics); + + diagnostics.Add(syntaxExpr, useSiteDiagnostics); + + if (!lookupResult.IsMultiViable) + { + ReportPatternMemberLookupDiagnostics(lookupResult, patternType, methodName, syntaxExpr, warningsOnly, diagnostics, messageID); + return null; + } + + ArrayBuilder candidateMethods = ArrayBuilder.GetInstance(); + + foreach (Symbol member in lookupResult.Symbols) + { + if (member.Kind != SymbolKind.Method) + { + candidateMethods.Free(); + + if (warningsOnly) + { + ReportPatternWarning(diagnostics, patternType, member, syntaxExpr, messageID); + } + + return null; + } + + MethodSymbol method = (MethodSymbol)member; + + // SPEC VIOLATION: The spec says we should apply overload resolution, but Dev10 uses + // some custom logic in ExpressionBinder.BindGrpToParams. The biggest difference + // we've found (so far) is that it only considers methods with zero parameters + // (i.e. doesn't work with "params" or optional parameters). + if (!method.Parameters.Any()) + { + candidateMethods.Add((MethodSymbol)member); + } + } + + MethodSymbol patternMethod = PerformPatternOverloadResolution(patternType, candidateMethods, syntaxExpr, warningsOnly, diagnostics, syntaxTree, messageID); + + candidateMethods.Free(); + + return patternMethod; + } + + /// + /// The overload resolution portion of FindPatternMethod. + /// + private MethodSymbol PerformPatternOverloadResolution( + TypeSymbol patternType, ArrayBuilder candidateMethods, + SyntaxNode syntaxExpression, bool warningsOnly, DiagnosticBag diagnostics, + SyntaxTree syntaxTree, MessageID messageID) + { + ArrayBuilder typeArguments = ArrayBuilder.GetInstance(); + AnalyzedArguments arguments = AnalyzedArguments.GetInstance(); + OverloadResolutionResult overloadResolutionResult = OverloadResolutionResult.GetInstance(); + + HashSet useSiteDiagnostics = null; + // We create a dummy receiver of the invocation so MethodInvocationOverloadResolution knows it was invoked from an instance, not a type + var dummyReceiver = new BoundImplicitReceiver(syntaxExpression, patternType); + this.OverloadResolution.MethodInvocationOverloadResolution( + methods: candidateMethods, + typeArguments: typeArguments, + receiver: dummyReceiver, + arguments: arguments, + result: overloadResolutionResult, + useSiteDiagnostics: ref useSiteDiagnostics); + diagnostics.Add(syntaxExpression, useSiteDiagnostics); + + MethodSymbol result = null; + + if (overloadResolutionResult.Succeeded) + { + result = overloadResolutionResult.ValidResult.Member; + + if (result.IsStatic || result.DeclaredAccessibility != Accessibility.Public) + { + if (warningsOnly) + { + diagnostics.Add(ErrorCode.WRN_PatternStaticOrInaccessible, syntaxExpression.Location, patternType, messageID.Localize(), result); + } + + result = null; + } + else if (result.CallsAreOmitted(syntaxTree)) + { + // Calls to this method are omitted in the current syntax tree, i.e it is either a partial method with no implementation part OR a conditional method whose condition is not true in this source file. + // We don't want to allow this case, see StatementBinder::bindPatternToMethod. + result = null; + } + } + else if (overloadResolutionResult.Results.Length > 1) + { + if (warningsOnly) + { + diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, syntaxExpression.Location, patternType, messageID.Localize(), + overloadResolutionResult.Results[0].Member, overloadResolutionResult.Results[1].Member); + } + } + + overloadResolutionResult.Free(); + arguments.Free(); + typeArguments.Free(); + + return result; + } + + private void ReportPatternWarning(DiagnosticBag diagnostics, TypeSymbol patternType, Symbol patternMemberCandidate, SyntaxNode expression, MessageID messageID) + { + HashSet useSiteDiagnostics = null; + if (this.IsAccessible(patternMemberCandidate, ref useSiteDiagnostics)) + { + diagnostics.Add(ErrorCode.WRN_PatternBadSignature, expression.Location, patternType, messageID.Localize(), patternMemberCandidate); + } + + diagnostics.Add(expression, useSiteDiagnostics); + } + + /// + /// Report appropriate diagnostics when lookup of a pattern member (i.e. GetEnumerator, Current, or MoveNext) fails. + /// + /// Failed lookup result. + /// Type in which member was looked up. + /// Name of looked up member. + /// True if failures should result in warnings; false if they should result in errors. + /// Populated appropriately. + internal void ReportPatternMemberLookupDiagnostics( + LookupResult lookupResult, TypeSymbol patternType, + string memberName, SyntaxNode expression, + bool warningsOnly, DiagnosticBag diagnostics, + MessageID messageID) + { + if (lookupResult.Symbols.Any()) + { + if (warningsOnly) + { + ReportPatternWarning(diagnostics, patternType, lookupResult.Symbols.First(), expression, messageID); + } + else + { + lookupResult.Clear(); + + HashSet useSiteDiagnostics = null; + this.LookupMembersInType( + lookupResult, + patternType, + memberName, + arity: 0, + basesBeingResolved: null, + options: LookupOptions.Default, + originalBinder: this, + diagnose: true, + useSiteDiagnostics: ref useSiteDiagnostics); + + diagnostics.Add(expression, useSiteDiagnostics); + + if (lookupResult.Error != null) + { + diagnostics.Add(lookupResult.Error, expression.Location); + } + } + } + else if (!warningsOnly) + { + diagnostics.Add(ErrorCode.ERR_NoSuchMember, expression.Location, patternType, memberName); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 749ac547e7c5f..9a1b0b6e7a447 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -1,13 +1,14 @@ // 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.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using System.Collections.Generic; namespace Microsoft.CodeAnalysis.CSharp { @@ -766,141 +767,14 @@ private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumerator private bool SatisfiesGetEnumeratorPattern(ref ForEachEnumeratorInfo.Builder builder, TypeSymbol collectionExprType, DiagnosticBag diagnostics) { LookupResult lookupResult = LookupResult.GetInstance(); - MethodSymbol getEnumeratorMethod = FindForEachPatternMethod(collectionExprType, GetEnumeratorMethodName, lookupResult, warningsOnly: true, diagnostics: diagnostics); + MethodSymbol getEnumeratorMethod = FindPatternMethod(collectionExprType, GetEnumeratorMethodName, lookupResult, + _syntax.Expression, warningsOnly: true, diagnostics: diagnostics, + _syntax.SyntaxTree, MessageID.IDS_Collection); lookupResult.Free(); builder.GetEnumeratorMethod = getEnumeratorMethod; return (object)getEnumeratorMethod != null; } - - /// - /// Perform a lookup for the specified method on the specified type. Perform overload resolution - /// on the lookup results. - /// - /// Type to search. - /// Method to search for. - /// Passed in for reusability. - /// True if failures should result in warnings; false if they should result in errors. - /// Populated with binding diagnostics. - /// The desired method or null. - private MethodSymbol FindForEachPatternMethod(TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, DiagnosticBag diagnostics) - { - Debug.Assert(lookupResult.IsClear); - - // Not using LookupOptions.MustBeInvocableMember because we don't want the corresponding lookup error. - // We filter out non-methods below. - HashSet useSiteDiagnostics = null; - this.LookupMembersInType( - lookupResult, - patternType, - methodName, - arity: 0, - basesBeingResolved: null, - options: LookupOptions.Default, - originalBinder: this, - diagnose: false, - useSiteDiagnostics: ref useSiteDiagnostics); - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - if (!lookupResult.IsMultiViable) - { - ReportPatternMemberLookupDiagnostics(lookupResult, patternType, methodName, warningsOnly, diagnostics); - return null; - } - - ArrayBuilder candidateMethods = ArrayBuilder.GetInstance(); - - foreach (Symbol member in lookupResult.Symbols) - { - if (member.Kind != SymbolKind.Method) - { - candidateMethods.Free(); - - if (warningsOnly) - { - ReportEnumerableWarning(diagnostics, patternType, member); - } - return null; - } - - MethodSymbol method = (MethodSymbol)member; - - // SPEC VIOLATION: The spec says we should apply overload resolution, but Dev10 uses - // some custom logic in ExpressionBinder.BindGrpToParams. The biggest difference - // we've found (so far) is that it only considers methods with zero parameters - // (i.e. doesn't work with "params" or optional parameters). - if (!method.Parameters.Any()) - { - candidateMethods.Add((MethodSymbol)member); - } - } - - MethodSymbol patternMethod = PerformForEachPatternOverloadResolution(patternType, candidateMethods, warningsOnly, diagnostics); - - candidateMethods.Free(); - - return patternMethod; - } - - /// - /// The overload resolution portion of FindForEachPatternMethod. - /// - private MethodSymbol PerformForEachPatternOverloadResolution(TypeSymbol patternType, ArrayBuilder candidateMethods, bool warningsOnly, DiagnosticBag diagnostics) - { - ArrayBuilder typeArguments = ArrayBuilder.GetInstance(); - AnalyzedArguments arguments = AnalyzedArguments.GetInstance(); - OverloadResolutionResult overloadResolutionResult = OverloadResolutionResult.GetInstance(); - - HashSet useSiteDiagnostics = null; - // We create a dummy receiver of the invocation so MethodInvocationOverloadResolution knows it was invoked from an instance, not a type - var dummyReceiver = new BoundImplicitReceiver(_syntax.Expression, patternType); - this.OverloadResolution.MethodInvocationOverloadResolution( - methods: candidateMethods, - typeArguments: typeArguments, - receiver: dummyReceiver, - arguments: arguments, - result: overloadResolutionResult, - useSiteDiagnostics: ref useSiteDiagnostics); - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - MethodSymbol result = null; - - if (overloadResolutionResult.Succeeded) - { - result = overloadResolutionResult.ValidResult.Member; - - if (result.IsStatic || result.DeclaredAccessibility != Accessibility.Public) - { - if (warningsOnly) - { - diagnostics.Add(ErrorCode.WRN_PatternStaticOrInaccessible, _syntax.Expression.Location, patternType, MessageID.IDS_Collection.Localize(), result); - } - result = null; - } - else if (result.CallsAreOmitted(_syntax.SyntaxTree)) - { - // Calls to this method are omitted in the current syntax tree, i.e it is either a partial method with no implementation part OR a conditional method whose condition is not true in this source file. - // We don't want to want to allow this case, see StatementBinder::bindPatternToMethod. - result = null; - } - } - else if (overloadResolutionResult.Results.Length > 1) - { - if (warningsOnly) - { - diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, _syntax.Expression.Location, patternType, MessageID.IDS_Collection.Localize(), - overloadResolutionResult.Results[0].Member, overloadResolutionResult.Results[1].Member); - } - } - - overloadResolutionResult.Free(); - arguments.Free(); - typeArguments.Free(); - - return result; - } - /// /// Called after it is determined that the expression being enumerated is of a type that /// has a GetEnumerator method. Checks to see if the return type of the GetEnumerator @@ -961,7 +835,7 @@ private bool SatisfiesForEachPattern(ref ForEachEnumeratorInfo.Builder builder, if (!lookupResult.IsSingleViable) { - ReportPatternMemberLookupDiagnostics(lookupResult, enumeratorType, CurrentPropertyName, warningsOnly: false, diagnostics: diagnostics); + ReportPatternMemberLookupDiagnostics(lookupResult, enumeratorType, CurrentPropertyName, _syntax.Expression, warningsOnly: false, diagnostics: diagnostics, MessageID.IDS_Collection); return false; } @@ -997,7 +871,8 @@ private bool SatisfiesForEachPattern(ref ForEachEnumeratorInfo.Builder builder, lookupResult.Clear(); // Reuse the same LookupResult - MethodSymbol moveNextMethodCandidate = FindForEachPatternMethod(enumeratorType, MoveNextMethodName, lookupResult, warningsOnly: false, diagnostics: diagnostics); + MethodSymbol moveNextMethodCandidate = FindPatternMethod(enumeratorType, MoveNextMethodName, lookupResult, _syntax.Expression, + warningsOnly: false, diagnostics, _syntax.SyntaxTree, MessageID.IDS_Collection); // SPEC VIOLATION: Dev10 checks the return type of the original definition, rather than the return type of the actual method. @@ -1018,17 +893,6 @@ private bool SatisfiesForEachPattern(ref ForEachEnumeratorInfo.Builder builder, } } - private void ReportEnumerableWarning(DiagnosticBag diagnostics, TypeSymbol enumeratorType, Symbol patternMemberCandidate) - { - HashSet useSiteDiagnostics = null; - if (this.IsAccessible(patternMemberCandidate, ref useSiteDiagnostics)) - { - diagnostics.Add(ErrorCode.WRN_PatternBadSignature, _syntax.Expression.Location, enumeratorType, MessageID.IDS_Collection.Localize(), patternMemberCandidate); - } - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - } - private static bool IsIEnumerable(TypeSymbol type) { switch (((TypeSymbol)type.OriginalDefinition).SpecialType) @@ -1118,53 +982,6 @@ private static void GetIEnumerableOfT(ImmutableArray interfaces } } } - - /// - /// Report appropriate diagnostics when lookup of a pattern member (i.e. GetEnumerator, Current, or MoveNext) fails. - /// - /// Failed lookup result. - /// Type in which member was looked up. - /// Name of looked up member. - /// True if failures should result in warnings; false if they should result in errors. - /// Populated appropriately. - private void ReportPatternMemberLookupDiagnostics(LookupResult lookupResult, TypeSymbol patternType, string memberName, bool warningsOnly, DiagnosticBag diagnostics) - { - if (lookupResult.Symbols.Any()) - { - if (warningsOnly) - { - ReportEnumerableWarning(diagnostics, patternType, lookupResult.Symbols.First()); - } - else - { - lookupResult.Clear(); - - HashSet useSiteDiagnostics = null; - this.LookupMembersInType( - lookupResult, - patternType, - memberName, - arity: 0, - basesBeingResolved: null, - options: LookupOptions.Default, - originalBinder: this, - diagnose: true, - useSiteDiagnostics: ref useSiteDiagnostics); - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - if (lookupResult.Error != null) - { - diagnostics.Add(lookupResult.Error, _syntax.Expression.Location); - } - } - } - else if (!warningsOnly) - { - diagnostics.Add(ErrorCode.ERR_NoSuchMember, _syntax.Expression.Location, patternType, memberName); - } - } - internal override ImmutableArray GetDeclaredLocalsForScope(SyntaxNode scopeDesignator) { if (_syntax == scopeDesignator) diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index af1db0b6a32d6..fc92d39187c73 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; using System.Collections.Generic; -using System; namespace Microsoft.CodeAnalysis.CSharp { @@ -67,6 +66,7 @@ internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnosti bool hasErrors = false; BoundMultipleLocalDeclarations declarationsOpt = null; BoundExpression expressionOpt = null; + MethodSymbol disposeMethod = null; Conversion iDisposableConversion = Conversion.NoConversion; TypeSymbol iDisposable = this.Compilation.GetSpecialType(SpecialType.System_IDisposable); // no need for diagnostics, so use the Compilation version Debug.Assert((object)iDisposable != null); @@ -79,18 +79,27 @@ internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnosti iDisposableConversion = originalBinder.Conversions.ClassifyImplicitConversionFromExpression(expressionOpt, iDisposable, ref useSiteDiagnostics); diagnostics.Add(expressionSyntax, useSiteDiagnostics); + TypeSymbol expressionType = expressionOpt.Type; + if (!iDisposableConversion.IsImplicit) { - TypeSymbol expressionType = expressionOpt.Type; - if ((object)expressionType == null || !expressionType.IsErrorType()) + if (!(expressionType is null)) { - Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, expressionSyntax, expressionOpt.Display); + disposeMethod = TryFindDisposePatternMethod(expressionType, diagnostics); } - hasErrors = true; + if (disposeMethod is null) + { + if (expressionType is null || !expressionType.IsErrorType()) + { + Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, expressionSyntax, expressionOpt.Display); + } + hasErrors = true; + } } } else { + ImmutableArray declarations; originalBinder.BindForOrUsingOrFixedDeclarations(declarationSyntax, LocalDeclarationKind.UsingVariable, diagnostics, out declarations); @@ -112,12 +121,15 @@ internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnosti if (!iDisposableConversion.IsImplicit) { - if (!declType.IsErrorType()) + disposeMethod = TryFindDisposePatternMethod(declType, diagnostics); + if (disposeMethod is null) { - Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, declarationSyntax, declType); + if (!declType.IsErrorType()) + { + Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, declarationSyntax, declType); + } + hasErrors = true; } - - hasErrors = true; } } } @@ -132,9 +144,33 @@ internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnosti expressionOpt, iDisposableConversion, boundBody, + disposeMethod, hasErrors); } + /// + /// Checks for a Dispose method on exprType in the case that there is no explicit + /// IDisposable conversion. + /// + /// Type of the expression over which to iterate + /// Populated with warnings if there are near misses + /// True if a matching method is found with correct return type. + private MethodSymbol TryFindDisposePatternMethod(TypeSymbol exprType, DiagnosticBag diagnostics) + { + LookupResult lookupResult = LookupResult.GetInstance(); + SyntaxNode syntax = _syntax.Expression != null ? (SyntaxNode)_syntax.Expression : (SyntaxNode)_syntax.Declaration; + MethodSymbol disposeMethod = FindPatternMethod(exprType, WellKnownMemberNames.DisposeMethodName, lookupResult, syntax, warningsOnly: true, diagnostics, _syntax.SyntaxTree, MessageID.IDS_Disposable); + lookupResult.Free(); + + if (disposeMethod?.ReturnsVoid == false) + { + diagnostics.Add(ErrorCode.WRN_PatternBadSignature, syntax.Location, exprType, MessageID.IDS_Disposable.Localize(), disposeMethod); + disposeMethod = null; + } + + return disposeMethod; + } + internal override ImmutableArray GetDeclaredLocalsForScope(SyntaxNode scopeDesignator) { if (_syntax == scopeDesignator) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 40be77162aab7..fa357605d8758 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -934,6 +934,7 @@ + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 3d72a4e0c0808..0bda4c957ddab 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -6983,7 +6983,7 @@ internal static string ERR_NoConversionForNubDefaultParam { } /// - /// Looks up a localized string similar to '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable'. + /// Looks up a localized string similar to '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method.. /// internal static string ERR_NoConvToIDisp { get { @@ -10394,6 +10394,15 @@ internal static string IDS_DirectoryHasInvalidPath { } } + /// + /// Looks up a localized string similar to disposable. + /// + internal static string IDS_Disposable { + get { + return ResourceManager.GetString("IDS_Disposable", resourceCulture); + } + } + /// /// Looks up a localized string similar to anonymous methods. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index aded03347cd8b..8daeb907f590c 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -198,6 +198,9 @@ collection + + disposable + access modifiers on properties @@ -2939,7 +2942,7 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep Anonymous methods, lambda expressions, and query expressions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression or query expression and using the local instead. - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. Parameter {0} must be declared with the '{1}' keyword diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 80381fc65cd12..1ee2434e0cf69 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -160,6 +160,7 @@ internal enum MessageID IDS_FeatureExpressionVariablesInQueriesAndInitializers = MessageBase + 12742, IDS_FeatureExtensibleFixedStatement = MessageBase + 12743, IDS_FeatureIndexingMovableFixedBuffers = MessageBase + 12744, + IDS_Disposable = MessageBase + 12745, } // Message IDs may refer to strings that need to be localized. diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 7124629dd1a57..6e69798c659ce 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -3344,7 +3344,7 @@ public BoundForEachDeconstructStep Update(BoundDeconstructionAssignmentOperator internal sealed partial class BoundUsingStatement : BoundStatement { - public BoundUsingStatement(SyntaxNode syntax, ImmutableArray locals, BoundMultipleLocalDeclarations declarationsOpt, BoundExpression expressionOpt, Conversion iDisposableConversion, BoundStatement body, bool hasErrors = false) + public BoundUsingStatement(SyntaxNode syntax, ImmutableArray locals, BoundMultipleLocalDeclarations declarationsOpt, BoundExpression expressionOpt, Conversion iDisposableConversion, BoundStatement body, MethodSymbol disposeMethodOpt, bool hasErrors = false) : base(BoundKind.UsingStatement, syntax, hasErrors || declarationsOpt.HasErrors() || expressionOpt.HasErrors() || body.HasErrors()) { @@ -3356,6 +3356,7 @@ public BoundUsingStatement(SyntaxNode syntax, ImmutableArray locals this.ExpressionOpt = expressionOpt; this.IDisposableConversion = iDisposableConversion; this.Body = body; + this.DisposeMethodOpt = disposeMethodOpt; } @@ -3369,16 +3370,18 @@ public BoundUsingStatement(SyntaxNode syntax, ImmutableArray locals public BoundStatement Body { get; } + public MethodSymbol DisposeMethodOpt { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitUsingStatement(this); } - public BoundUsingStatement Update(ImmutableArray locals, BoundMultipleLocalDeclarations declarationsOpt, BoundExpression expressionOpt, Conversion iDisposableConversion, BoundStatement body) + public BoundUsingStatement Update(ImmutableArray locals, BoundMultipleLocalDeclarations declarationsOpt, BoundExpression expressionOpt, Conversion iDisposableConversion, BoundStatement body, MethodSymbol disposeMethodOpt) { - if (locals != this.Locals || declarationsOpt != this.DeclarationsOpt || expressionOpt != this.ExpressionOpt || iDisposableConversion != this.IDisposableConversion || body != this.Body) + if (locals != this.Locals || declarationsOpt != this.DeclarationsOpt || expressionOpt != this.ExpressionOpt || iDisposableConversion != this.IDisposableConversion || body != this.Body || disposeMethodOpt != this.DisposeMethodOpt) { - var result = new BoundUsingStatement(this.Syntax, locals, declarationsOpt, expressionOpt, iDisposableConversion, body, this.HasErrors); + var result = new BoundUsingStatement(this.Syntax, locals, declarationsOpt, expressionOpt, iDisposableConversion, body, disposeMethodOpt, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -9166,7 +9169,7 @@ public override BoundNode VisitUsingStatement(BoundUsingStatement node) BoundMultipleLocalDeclarations declarationsOpt = (BoundMultipleLocalDeclarations)this.Visit(node.DeclarationsOpt); BoundExpression expressionOpt = (BoundExpression)this.Visit(node.ExpressionOpt); BoundStatement body = (BoundStatement)this.Visit(node.Body); - return node.Update(node.Locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body); + return node.Update(node.Locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body, node.DisposeMethodOpt); } public override BoundNode VisitFixedStatement(BoundFixedStatement node) { @@ -10442,7 +10445,8 @@ public override TreeDumperNode VisitUsingStatement(BoundUsingStatement node, obj new TreeDumperNode("declarationsOpt", null, new TreeDumperNode[] { Visit(node.DeclarationsOpt, null) }), new TreeDumperNode("expressionOpt", null, new TreeDumperNode[] { Visit(node.ExpressionOpt, null) }), new TreeDumperNode("iDisposableConversion", node.IDisposableConversion, null), - new TreeDumperNode("body", null, new TreeDumperNode[] { Visit(node.Body, null) }) + new TreeDumperNode("body", null, new TreeDumperNode[] { Visit(node.Body, null) }), + new TreeDumperNode("disposeMethodOpt", node.DisposeMethodOpt, null) } ); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 1a77a73f9b12c..d168573facd4d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -48,7 +48,7 @@ public override BoundNode VisitUsingStatement(BoundUsingStatement node) int numDeclarations = declarations.Length; for (int i = numDeclarations - 1; i >= 0; i--) //NB: inner-to-outer = right-to-left { - result = RewriteDeclarationUsingStatement(usingSyntax, declarations[i], result, idisposableConversion); + result = RewriteDeclarationUsingStatement(usingSyntax, declarations[i], result, idisposableConversion, node.DisposeMethodOpt); } // Declare all locals in a single, top-level block so that the scope is correct in the debugger @@ -135,7 +135,7 @@ private BoundBlock RewriteExpressionUsingStatement(BoundUsingStatement node, Bou expressionStatement = _instrumenter.InstrumentUsingTargetCapture(node, expressionStatement); } - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, node.DisposeMethodOpt); // { ResourceType temp = expr; try { ... } finally { ... } } return new BoundBlock( @@ -151,7 +151,7 @@ private BoundBlock RewriteExpressionUsingStatement(BoundUsingStatement node, Bou /// 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. /// - private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, BoundLocalDeclaration localDeclaration, BoundBlock tryBlock, Conversion idisposableConversion) + private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, BoundLocalDeclaration localDeclaration, BoundBlock tryBlock, Conversion idisposableConversion, MethodSymbol methodSymbol) { SyntaxNode declarationSyntax = localDeclaration.Syntax; @@ -185,7 +185,7 @@ private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, Boun BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(tempInit, out tempAssignment, kind: SynthesizedLocalKind.Using); - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, methodSymbol); return new BoundBlock( syntax: usingSyntax, @@ -197,14 +197,14 @@ private BoundBlock RewriteDeclarationUsingStatement(SyntaxNode usingSyntax, Boun } else { - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal, methodSymbol); // localSymbol will be declared by an enclosing block return BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryFinally); } } - private BoundStatement RewriteUsingStatementTryFinally(SyntaxNode syntax, BoundBlock tryBlock, BoundLocal local) + private BoundStatement RewriteUsingStatementTryFinally(SyntaxNode syntax, BoundBlock tryBlock, BoundLocal local, MethodSymbol methodOpt) { // SPEC: When ResourceType is a non-nullable value type, the expansion is: // SPEC: @@ -290,11 +290,10 @@ private BoundStatement RewriteUsingStatementTryFinally(SyntaxNode syntax, BoundB // local.Dispose() BoundExpression disposeCall; - - MethodSymbol disposeMethodSymbol; - if (Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, syntax, _diagnostics, out disposeMethodSymbol)) + + if ((!(methodOpt is null)) || Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, syntax, _diagnostics, out methodOpt)) { - disposeCall = BoundCall.Synthesized(syntax, disposedExpression, disposeMethodSymbol); + disposeCall = BoundCall.Synthesized(syntax, disposedExpression, methodOpt); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index 6ce8bf3446196..5e35ade4bc754 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -194,7 +194,7 @@ public override BoundNode VisitUsingStatement(BoundUsingStatement node) BoundExpression expressionOpt = (BoundExpression)this.Visit(node.ExpressionOpt); BoundStatement body = (BoundStatement)this.Visit(node.Body); Conversion disposableConversion = RewriteConversion(node.IDisposableConversion); - return node.Update(newLocals, declarationsOpt, expressionOpt, disposableConversion, body); + return node.Update(newLocals, declarationsOpt, expressionOpt, disposableConversion, body, node.DisposeMethodOpt); } private Conversion RewriteConversion(Conversion conversion) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 5098c8a9f9544..90f98a679caad 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ Blok catch() po bloku catch (System.Exception e) může zachytit výjimky, kter - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}: Typ použitý v příkazu using musí být implicitně převeditelný na System.IDisposable. + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}: Typ použitý v příkazu using musí být implicitně převeditelný na System.IDisposable. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 156fb24fef980..b68f06626044e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <NULL> @@ -4691,8 +4696,8 @@ Ein catch()-Block nach einem catch (System.Exception e)-Block kann nicht-CLS-Aus - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '"{0}": Der in einer using-Anweisung verwendete Typ muss implizit in System.IDisposable konvertiert werden können. + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '"{0}": Der in einer using-Anweisung verwendete Typ muss implizit in System.IDisposable konvertiert werden können. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index a98a4bbe9626c..7fba46a8309e4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <NULL> @@ -4691,8 +4696,8 @@ Un bloque catch() después de un bloque catch (System.Exception e) puede abarcar - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': el tipo usado en una instrucción using debe poder convertirse implícitamente en 'System.IDisposable' + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': el tipo usado en una instrucción using debe poder convertirse implícitamente en 'System.IDisposable' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index b7875ef5bd740..64090ef3e5402 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <Null> @@ -4691,8 +4696,8 @@ Un bloc catch() après un bloc catch (System.Exception e) peut intercepter des e - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}' : le type utilisé dans une instruction using doit être implicitement convertible en 'System.IDisposable' + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}' : le type utilisé dans une instruction using doit être implicitement convertible en 'System.IDisposable' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 29505ee647492..13aec3ad0b05e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <Null> @@ -4691,8 +4696,8 @@ Un blocco catch() dopo un blocco catch (System.Exception e) è in grado di rilev - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': il tipo usato in un'istruzione using deve essere convertibile in modo implicito in 'System.IDisposable' + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': il tipo usato in un'istruzione using deve essere convertibile in modo implicito in 'System.IDisposable' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 1b1a97dad674d..0f1a81d5ff483 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ AssemblyInfo.cs ファイルで RuntimeCompatibilityAttribute が false に設 - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': using ステートメントで使用される型は、暗黙的に 'System.IDisposable' への変換が可能でなければなりません。 + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': using ステートメントで使用される型は、暗黙的に 'System.IDisposable' への変換が可能でなければなりません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 14270b8dcee83..c1d0acb810923 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ catch (System.Exception e) 블록 뒤의 catch() 블록은 RuntimeCompatibilityA - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': using 문에 사용된 형식은 암시적으로 'System.IDisposable'로 변환할 수 있어야 합니다. + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': using 문에 사용된 형식은 암시적으로 'System.IDisposable'로 변환할 수 있어야 합니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index b5ad071d9aca8..618960f92a693 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ Blok catch() po bloku catch (System.Exception e) może przechwytywać wyjątki n - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '„{0}”: musi istnieć możliwość niejawnego przekonwertowania typu użytego w instrukcji using na interfejs „System.IDisposable” + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '„{0}”: musi istnieć możliwość niejawnego przekonwertowania typu użytego w instrukcji using na interfejs „System.IDisposable” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 884aa94f2222e..afe9a921d3dc7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <nulo> @@ -4691,8 +4696,8 @@ Um bloco catch() depois de um bloco catch (System.Exception e) poderá capturar - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '"{0}": tipo usado em uma instrução using deve ser implicitamente conversível para "System. IDisposable" + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '"{0}": tipo usado em uma instrução using deve ser implicitamente conversível para "System. IDisposable" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 59cad27635595..cb6245185615c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <NULL> @@ -4691,8 +4696,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}": тип, использованный в операторе using, должен иметь неявное преобразование в System.IDisposable. + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}": тип, использованный в операторе using, должен иметь неявное преобразование в System.IDisposable. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 74f8c70934158..dcdb658e86334 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ RuntimeCompatibilityAttribute AssemblyInfo.cs dosyasında false olarak ayarlanm - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': bir using deyiminde kullanılan tür açıkça 'System.IDisposable' öğesine dönüştürülebilir olmalıdır + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': bir using deyiminde kullanılan tür açıkça 'System.IDisposable' öğesine dönüştürülebilir olmalıdır diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 08fadda9b7970..b0de35273d0af 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '“{0}”: using 语句中使用的类型必须可隐式转换为“System.IDisposable” + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '“{0}”: using 语句中使用的类型必须可隐式转换为“System.IDisposable” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index aa828129be2b9..52a99a1a44c67 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -7,6 +7,11 @@ An out variable cannot be declared as a ref local + + disposable + disposable + + <null> <null> @@ -4691,8 +4696,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' - '{0}': 在 using 陳述式中所用的類型,必須可以隱含轉換成 'System.IDisposable'。 + '{0}': type used in a using statement must be implicitly convertible to 'System.IDisposable' or have a public void-returning Dispose() instance method. + '{0}': 在 using 陳述式中所用的類型,必須可以隱含轉換成 'System.IDisposable'。 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs index 0c15966e1fe19..5f904580434a9 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs @@ -521,18 +521,18 @@ .locals init (DisposableStruct V_0) //d IL_000c: ldstr ""A"" IL_0011: call ""DisposableStruct..ctor(string)"" .try -{ - IL_0016: ldstr ""In"" - IL_001b: call ""void System.Console.WriteLine(string)"" - IL_0020: leave.s IL_0030 -} + { + IL_0016: ldstr ""In"" + IL_001b: call ""void System.Console.WriteLine(string)"" + IL_0020: leave.s IL_0030 + } finally -{ - IL_0022: ldloca.s V_0 - IL_0024: constrained. ""DisposableStruct"" - IL_002a: callvirt ""void System.IDisposable.Dispose()"" - IL_002f: endfinally -} + { + IL_0022: ldloca.s V_0 + IL_0024: constrained. ""DisposableStruct"" + IL_002a: callvirt ""void System.IDisposable.Dispose()"" + IL_002f: endfinally + } IL_0030: ldstr ""After"" IL_0035: call ""void System.Console.WriteLine(string)"" IL_003a: ret @@ -703,8 +703,8 @@ Disposing A // Code size 96 (0x60) .maxstack 1 .locals init (DisposableClass V_0, //d1 - DisposableClass V_1, //d2 - DisposableClass V_2) //d3 + DisposableClass V_1, //d2 + DisposableClass V_2) //d3 IL_0000: ldstr ""Before"" IL_0005: call ""void System.Console.WriteLine(string)"" IL_000a: ldstr ""A"" @@ -791,52 +791,52 @@ Disposing A // Code size 109 (0x6d) .maxstack 2 .locals init (DisposableStruct V_0, //d1 - DisposableStruct V_1, //d2 - DisposableStruct V_2) //d3 + DisposableStruct V_1, //d2 + DisposableStruct V_2) //d3 IL_0000: ldstr ""Before"" IL_0005: call ""void System.Console.WriteLine(string)"" IL_000a: ldloca.s V_0 IL_000c: ldstr ""A"" IL_0011: call ""DisposableStruct..ctor(string)"" .try -{ - IL_0016: ldstr ""B"" - IL_001b: newobj ""DisposableStruct..ctor(string)"" - IL_0020: stloc.1 - .try -{ - IL_0021: ldstr ""C"" - IL_0026: newobj ""DisposableStruct..ctor(string)"" - IL_002b: stloc.2 - .try -{ - IL_002c: ldstr ""In"" - IL_0031: call ""void System.Console.WriteLine(string)"" - IL_0036: leave.s IL_0062 -} - finally -{ - IL_0038: ldloca.s V_2 - IL_003a: constrained. ""DisposableStruct"" - IL_0040: callvirt ""void System.IDisposable.Dispose()"" - IL_0045: endfinally -} -} - finally -{ - IL_0046: ldloca.s V_1 - IL_0048: constrained. ""DisposableStruct"" - IL_004e: callvirt ""void System.IDisposable.Dispose()"" - IL_0053: endfinally -} -} + { + IL_0016: ldstr ""B"" + IL_001b: newobj ""DisposableStruct..ctor(string)"" + IL_0020: stloc.1 + .try + { + IL_0021: ldstr ""C"" + IL_0026: newobj ""DisposableStruct..ctor(string)"" + IL_002b: stloc.2 + .try + { + IL_002c: ldstr ""In"" + IL_0031: call ""void System.Console.WriteLine(string)"" + IL_0036: leave.s IL_0062 + } + finally + { + IL_0038: ldloca.s V_2 + IL_003a: constrained. ""DisposableStruct"" + IL_0040: callvirt ""void System.IDisposable.Dispose()"" + IL_0045: endfinally + } + } + finally + { + IL_0046: ldloca.s V_1 + IL_0048: constrained. ""DisposableStruct"" + IL_004e: callvirt ""void System.IDisposable.Dispose()"" + IL_0053: endfinally + } + } finally -{ - IL_0054: ldloca.s V_0 - IL_0056: constrained. ""DisposableStruct"" - IL_005c: callvirt ""void System.IDisposable.Dispose()"" - IL_0061: endfinally -} + { + IL_0054: ldloca.s V_0 + IL_0056: constrained. ""DisposableStruct"" + IL_005c: callvirt ""void System.IDisposable.Dispose()"" + IL_0061: endfinally + } IL_0062: ldstr ""After"" IL_0067: call ""void System.Console.WriteLine(string)"" IL_006c: ret @@ -875,7 +875,7 @@ Disposing A // Code size 75 (0x4b) .maxstack 1 .locals init (DisposableClass V_0, //d1 - DisposableClass V_1) //d3 + DisposableClass V_1) //d3 IL_0000: ldstr ""Before"" IL_0005: call ""void System.Console.WriteLine(string)"" IL_000a: ldstr ""A"" @@ -1011,20 +1011,153 @@ .locals init (Program.MyManagedClass V_0) //mnObj IL_0000: newobj ""Program.MyManagedClass..ctor()"" IL_0005: stloc.0 .try + { + IL_0006: ldloc.0 + IL_0007: callvirt ""void Program.MyManagedClass.Use()"" + IL_000c: leave.s IL_0018 + } + finally + { + IL_000e: ldloc.0 + IL_000f: brfalse.s IL_0017 + IL_0011: ldloc.0 + IL_0012: callvirt ""void System.IDisposable.Dispose()"" + IL_0017: endfinally + } + IL_0018: ret +}"); + } + + [Fact] + public void UsingPatternTest() + { + var source = @" +class C1 { - IL_0006: ldloc.0 - IL_0007: callvirt ""void Program.MyManagedClass.Use()"" - IL_000c: leave.s IL_0018 + public C1() { } + public void Dispose() { } } + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + } +}"; + CompileAndVerify(source).VerifyIL("C2.Main()", @" +{ + // Code size 19 (0x13) + .maxstack 1 + .locals init (C1 V_0) //c + IL_0000: newobj ""C1..ctor()"" + IL_0005: stloc.0 + .try + { + IL_0006: leave.s IL_0012 + } finally + { + IL_0008: ldloc.0 + IL_0009: brfalse.s IL_0011 + IL_000b: ldloc.0 + IL_000c: callvirt ""void C1.Dispose()"" + IL_0011: endfinally + } + IL_0012: ret +}"); + } + + [Fact] + public void UsingPatternDiffParameterOverloadTest() + { + var source = @" +class C1 { - IL_000e: ldloc.0 - IL_000f: brfalse.s IL_0017 - IL_0011: ldloc.0 - IL_0012: callvirt ""void System.IDisposable.Dispose()"" - IL_0017: endfinally + public C1() { } + public void Dispose() { } + public void Dispose(int x) { } } - IL_0018: ret + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + } +}"; + CompileAndVerify(source).VerifyIL("C2.Main()", @" +{ + // Code size 19 (0x13) + .maxstack 1 + .locals init (C1 V_0) //c + IL_0000: newobj ""C1..ctor()"" + IL_0005: stloc.0 + .try + { + IL_0006: leave.s IL_0012 + } + finally + { + IL_0008: ldloc.0 + IL_0009: brfalse.s IL_0011 + IL_000b: ldloc.0 + IL_000c: callvirt ""void C1.Dispose()"" + IL_0011: endfinally + } + IL_0012: ret +}" + ); + } + + [Fact] + public void UsingPatternInheritedTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } +} + +class C2 : C1 +{ + internal void Dispose(int x) { } +} + +class C3 +{ + static void Main() + { + using (C2 c = new C2()) + { + } + } +}"; + CompileAndVerify(source).VerifyIL("C3.Main()", @" +{ + // Code size 19 (0x13) + .maxstack 1 + .locals init (C2 V_0) //c + IL_0000: newobj ""C2..ctor()"" + IL_0005: stloc.0 + .try + { + IL_0006: leave.s IL_0012 + } + finally + { + IL_0008: ldloc.0 + IL_0009: brfalse.s IL_0011 + IL_000b: ldloc.0 + IL_000c: callvirt ""void C1.Dispose()"" + IL_0011: endfinally + } + IL_0012: ret }"); } @@ -1183,10 +1316,10 @@ public static void TestUsing() } "; CreateCompilation(source).VerifyDiagnostics( - // (13,16): error CS1674: 'T': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (13,16): error CS1674: 'T': type used in a using statement must have a public void-returning Dispose() instance method. // using (val) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "val").WithArguments("T"), - // (24,16): error CS1674: 'T': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (24,16): error CS1674: 'T': type used in a using statement must have a public void-returning Dispose() instance method. // using (T disp = new T()) // Invalid Diagnostic(ErrorCode.ERR_NoConvToIDisp, "T disp = new T()").WithArguments("T")); } @@ -1240,7 +1373,7 @@ public void Dispose() } } "; - CreateCompilation(source).VerifyDiagnostics(Diagnostic(ErrorCode.ERR_NoConvToIDisp, "res").WithArguments("Program.MyManagedClass")); + CreateCompilation(source).VerifyDiagnostics(); } [Fact] @@ -1263,7 +1396,7 @@ public void Dispose() } } "; - CreateCompilation(source).VerifyDiagnostics(Diagnostic(ErrorCode.ERR_NoConvToIDisp, "res").WithArguments("Program.MyManagedClass")); + CreateCompilation(source).VerifyDiagnostics(); } // Implicit implement IDisposable @@ -2482,13 +2615,13 @@ static void Main(string[] args) } "; CreateCompilation(source).VerifyDiagnostics( - // (6,16): error CS1674: 'lambda expression': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'lambda expression': type used in a using statement must have a public void-returning Dispose() instance method. // using (x => x) // err Diagnostic(ErrorCode.ERR_NoConvToIDisp, "x => x").WithArguments("lambda expression"), - // (9,16): error CS1674: 'lambda expression': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (9,16): error CS1674: 'lambda expression': type used in a using statement must have a public void-returning Dispose() instance method. // using (() => { }) // err Diagnostic(ErrorCode.ERR_NoConvToIDisp, "() => { }").WithArguments("lambda expression"), - // (12,16): error CS1674: 'lambda expression': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (12,16): error CS1674: 'lambda expression': type used in a using statement must have a public void-returning Dispose() instance method. // using ((int @int) => { return @int; }) // err Diagnostic(ErrorCode.ERR_NoConvToIDisp, "(int @int) => { return @int; }").WithArguments("lambda expression")); } @@ -2522,19 +2655,19 @@ static void Main(string[] args) } "; CreateCompilation(source).VerifyDiagnostics( - // (6,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (var a = new { }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var a = new { }").WithArguments(""), - // (9,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (9,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (var b = new { p1 = 10 }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var b = new { p1 = 10 }").WithArguments(""), - // (12,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (12,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (var c = new { p1 = 10.0, p2 = 'a' }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var c = new { p1 = 10.0, p2 = 'a' }").WithArguments(""), - // (15,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (15,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (new { }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "new { }").WithArguments(""), - // (19,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (19,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (new { f1 = "12345", f2 = 'S' }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, @"new { f1 = ""12345"", f2 = 'S' }").WithArguments("") ); diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUsingStatement.cs index 0c29a4811cdd7..0a116b5e880f1 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUsingStatement.cs @@ -560,7 +560,7 @@ public static void M1() OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) "; var expectedDiagnostics = new DiagnosticDescription[] { - // CS1674: 'C': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // CS1674: 'C': type used in a using statement must have a public void-returning Dispose() instance method. // /**/using (var c1 = new C()) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var c1 = new C()").WithArguments("C").WithLocation(9, 26) }; @@ -609,7 +609,7 @@ public static void M1() OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) "; var expectedDiagnostics = new DiagnosticDescription[] { - // CS1674: 'C': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // CS1674: 'C': type used in a using statement must have a public void-returning Dispose() instance method. // /**/using (c1) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1").WithArguments("C").WithLocation(10, 26) }; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index b7891063de1fe..9a33b6afaf395 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -30,7 +30,7 @@ ref struct S2 } }"); comp.VerifyDiagnostics( - // (6,16): error CS1674: 'C.S2': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'C.S2': type used in a using statement must have a public void-returning Dispose() instance method. // using (var x = GetRefStruct()) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var x = GetRefStruct()").WithArguments("C.S2").WithLocation(6, 16)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 37bd84a38c2ce..5ecf6d7aee31d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -15725,9 +15725,9 @@ public static void Main() CreateCompilation(text).VerifyDiagnostics( // (7,22): warning CS0642: Possible mistaken empty statement Diagnostic(ErrorCode.WRN_PossibleMistakenNullStatement, ";"), - // (6,16): error CS1674: 'int': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'int': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "int a = 0").WithArguments("int"), - // (7,20): error CS1674: 'int': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (7,20): error CS1674: 'int': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "a").WithArguments("int")); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs index 0fa9d0df68d1b..be071e1a7c489 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SpanStackSafetyTests.cs @@ -719,10 +719,34 @@ public void Dispose() { } comp.VerifyDiagnostics( // (14,28): error CS8343: 'Program.S1': ref structs cannot implement interfaces // public ref struct S1 : IDisposable - Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IDisposable").WithArguments("Program.S1", "System.IDisposable").WithLocation(14, 28), - // (8,16): error CS1674: 'Program.S1': type used in a using statement must be implicitly convertible to 'System.IDisposable' - // using (new S1()) - Diagnostic(ErrorCode.ERR_NoConvToIDisp, "new S1()").WithArguments("Program.S1").WithLocation(8, 16) + Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IDisposable").WithArguments("Program.S1", "System.IDisposable").WithLocation(14, 28) + ); + } + + [Fact] + public void NoInterfaceImp() + { + var text = @" +public class Program +{ + static void Main(string[] args) + { + using (new S1()) + { + + } + } + + public ref struct S1 + { + public void Dispose() { } + } +} +"; + + CSharpCompilation comp = CreateCompilationWithMscorlibAndSpan(text); + + comp.VerifyDiagnostics( ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs index 9ab2d9fe4a581..12fd6a74b5b14 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs @@ -1396,13 +1396,13 @@ unsafe public static void Main() } "; CreateCompilationWithMscorlibAndSpan(test, options: TestOptions.ReleaseDll.WithAllowUnsafe(true)).VerifyDiagnostics( - // (6,16): error CS1674: 'int*': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'int*': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v1 = stackalloc int [3] { 1, 2, 3 }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v1 = stackalloc int [3] { 1, 2, 3 }").WithArguments("int*").WithLocation(6, 16), - // (7,16): error CS1674: 'int*': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (7,16): error CS1674: 'int*': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v2 = stackalloc int [ ] { 1, 2, 3 }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v2 = stackalloc int [ ] { 1, 2, 3 }").WithArguments("int*").WithLocation(7, 16), - // (8,16): error CS1674: 'int*': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (8,16): error CS1674: 'int*': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v3 = stackalloc [ ] { 1, 2, 3 }) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v3 = stackalloc [ ] { 1, 2, 3 }").WithArguments("int*").WithLocation(8, 16) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs index 4bf4959ffe828..7f5c493b6f101 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs @@ -419,7 +419,7 @@ unsafe public static void Main() } "; CreateCompilationWithMscorlibAndSpan(test, options: TestOptions.ReleaseDll.WithAllowUnsafe(true)).VerifyDiagnostics( - // (6,16): error CS1674: 'int*': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'int*': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v = stackalloc int[1]) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v = stackalloc int[1]").WithArguments("int*").WithLocation(6, 16)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs index ac8e5476ceafe..b0937cf00844f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs @@ -1,11 +1,9 @@ // 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; -using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -87,10 +85,311 @@ static void Main() "; CreateCompilation(source).VerifyDiagnostics( - // (6,16): error CS1674: 'method group': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'method group': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "Main").WithArguments("method group")); } + [Fact] + public void UsingPatternTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + } +}"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingPatternSameSignatureAmbiguousTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } + public void Dispose() { } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (6,17): error CS0111: Type 'C1' already defines a member called 'Dispose' with the same parameter types + // public void Dispose() { } + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Dispose").WithArguments("Dispose", "C1").WithLocation(6, 17), + // (13,16): warning CS0278: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is ambiguous with 'C1.Dispose()'. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.WRN_PatternIsAmbiguous, "C1 c = new C1()").WithArguments("C1", "disposable", "C1.Dispose()", "C1.Dispose()").WithLocation(13, 16), + // (13,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(13, 16) + ); + } + + [Fact] + public void UsingPatternAmbiguousOverloadTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } + public bool Dispose() { return false; } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (6,17): error CS0111: Type 'C1' already defines a member called 'Dispose' with the same parameter types + // public bool Dispose() { return false; } + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Dispose").WithArguments("Dispose", "C1").WithLocation(6, 17), + // (13,16): warning CS0278: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is ambiguous with 'C1.Dispose()'. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.WRN_PatternIsAmbiguous, "C1 c = new C1()").WithArguments("C1", "disposable", "C1.Dispose()", "C1.Dispose()").WithLocation(13, 16), + // (13,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(13, 16), + // (17,16): warning CS0278: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is ambiguous with 'C1.Dispose()'. + // using (c1b) { } + Diagnostic(ErrorCode.WRN_PatternIsAmbiguous, "c1b").WithArguments("C1", "disposable", "C1.Dispose()", "C1.Dispose()").WithLocation(17, 16), + // (17,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (c1b) { } + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1b").WithArguments("C1").WithLocation(17, 16) + ); + } + + [Fact] + public void UsingPatternDifferentParameterOverloadTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } + public void Dispose(int x) { } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + // Shouldn't throw an error as the method signatures are different. + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingPatternInaccessibleAmbiguousTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } + internal void Dispose() { } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (6,19): error CS0111: Type 'C1' already defines a member called 'Dispose' with the same parameter types + // internal void Dispose() { } + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Dispose").WithArguments("Dispose", "C1").WithLocation(6, 19), + // (13,16): warning CS0278: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is ambiguous with 'C1.Dispose()'. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.WRN_PatternIsAmbiguous, "C1 c = new C1()").WithArguments("C1", "disposable", "C1.Dispose()", "C1.Dispose()").WithLocation(13, 16), + // (13,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(13, 16), + // (17,16): warning CS0278: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is ambiguous with 'C1.Dispose()'. + // using (c1b) { } + Diagnostic(ErrorCode.WRN_PatternIsAmbiguous, "c1b").WithArguments("C1", "disposable", "C1.Dispose()", "C1.Dispose()").WithLocation(17, 16), + // (17,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (c1b) { } + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1b").WithArguments("C1").WithLocation(17, 16) + ); + } + + [Fact] + public void UsingPatternInheritedTest() + { + var source = @" +class C1 +{ + public C1() { } + public void Dispose() { } +} + +class C2 : C1 +{ + internal void Dispose(int x) { } +} + +class C3 +{ + static void Main() + { + using (C2 c = new C2()) + { + } + C2 c2b = new C2(); + using (c2b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingPatternExtensionMethodTest() + { + var source = @" +class C1 +{ + public C1() { } +} + +static class C2 +{ + public static void Dispose(this C1 c1) { } +} + +class C3 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (16,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(16, 16), + // (20,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (c1b) { } + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1b").WithArguments("C1").WithLocation(20, 16) + ); + } + + [Fact] + public void UsingPatternWrongReturnTest() + { + var source = @" +class C1 +{ + public C1() { } + public bool Dispose() { return false; } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (12,16): warning CS0280: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' has the wrong signature. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.WRN_PatternBadSignature, "C1 c = new C1()").WithArguments("C1", "disposable", "C1.Dispose()").WithLocation(12, 16), + // (12,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(12, 16), + // (16,16): warning CS0280: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' has the wrong signature. + // using (c1b) { } + Diagnostic(ErrorCode.WRN_PatternBadSignature, "c1b").WithArguments("C1", "disposable", "C1.Dispose()").WithLocation(16, 16), + // (16,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (c1b) { } + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1b").WithArguments("C1").WithLocation(16, 16) + ); + } + + [Fact] + public void UsingPatternWrongAccessibilityTest() + { + var source = @" +class C1 +{ + public C1() { } + internal void Dispose() { } +} + +class C2 +{ + static void Main() + { + using (C1 c = new C1()) + { + } + C1 c1b = new C1(); + using (c1b) { } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (12,16): warning CS0279: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is either static or not public. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.WRN_PatternStaticOrInaccessible, "C1 c = new C1()").WithArguments("C1", "disposable", "C1.Dispose()").WithLocation(12, 16), + // (12,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (C1 c = new C1()) + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "C1 c = new C1()").WithArguments("C1").WithLocation(12, 16), + // (16,16): warning CS0279: 'C1' does not implement the 'disposable' pattern. 'C1.Dispose()' is either static or not public. + // using (c1b) { } + Diagnostic(ErrorCode.WRN_PatternStaticOrInaccessible, "c1b").WithArguments("C1", "disposable", "C1.Dispose()").WithLocation(16, 16), + // (16,16): error CS1674: 'C1': type used in a using statement must have a public void-returning Dispose() instance method. + // using (c1b) { } + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "c1b").WithArguments("C1").WithLocation(16, 16) + ); + } + [Fact] public void Lambda() { @@ -107,7 +406,7 @@ static void Main() "; CreateCompilation(source).VerifyDiagnostics( - // (6,16): error CS1674: 'lambda expression': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'lambda expression': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "x => x").WithArguments("lambda expression")); } @@ -581,7 +880,7 @@ static void M(T0 t0, T1 t1, T2 t2, T3 t3, T4 t4) } }"; CreateCompilation(source).VerifyDiagnostics( - // (16,16): error CS1674: 'T0': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (16,16): error CS1674: 'T0': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "t0").WithArguments("T0").WithLocation(16, 16)); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs index fb25657ef0706..a58890518b20b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs @@ -1245,7 +1245,7 @@ public static void Test1(int x) } }"; var data = Compile(source, 1, - // (6,16): error CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v1 = new { } ) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v1 = new { }").WithArguments("") ); @@ -1282,7 +1282,7 @@ public static void Test1(int x) Initializer: null"; var expectedDiagnostics = new DiagnosticDescription[] { - // CS1674: '': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // CS1674: '': type used in a using statement must have a public void-returning Dispose() instance method. // using (/**/var v1 = new { }/**/) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v1 = new { }").WithArguments("").WithLocation(6, 26) }; diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs index daec45df9889d..f0a07781db453 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs @@ -1197,7 +1197,7 @@ static void Main() } }"; CreateCompilationWithILAndMscorlib40(csharp, il).VerifyDiagnostics( - // (6,16): error CS1674: 'ConvertibleToIDisposable': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'ConvertibleToIDisposable': type used in a using statement must have a public void-returning Dispose() instance method. Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var d = new ConvertibleToIDisposable()").WithArguments("ConvertibleToIDisposable")); } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs index fd73ba452fd49..c1a92e3a92dd1 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ParserErrorMessageTests.cs @@ -4812,7 +4812,7 @@ unsafe public static void Main() } "; CreateCompilation(test, options: TestOptions.ReleaseDll.WithAllowUnsafe(true)).VerifyDiagnostics( - // (6,16): error CS1674: 'int*': type used in a using statement must be implicitly convertible to 'System.IDisposable' + // (6,16): error CS1674: 'int*': type used in a using statement must have a public void-returning Dispose() instance method. // using (var v = stackalloc int[1]) Diagnostic(ErrorCode.ERR_NoConvToIDisp, "var v = stackalloc int[1]").WithArguments("int*").WithLocation(6, 16)); } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 63616a06ac26f..a87040c455ed4 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,2 +1,3 @@ Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.Concurrent.get -> bool Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.Concurrent.set -> void +const Microsoft.CodeAnalysis.WellKnownMemberNames.DisposeMethodName = "Dispose" -> string diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 1cf4403dad8ad..561be40dd9e0e 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -303,5 +303,10 @@ public static class WellKnownMemberNames /// (see C# Specification, §7.7.7.1 Awaitable expressions). /// public const string OnCompleted = nameof(OnCompleted); + + /// + /// The required name for the Dispose method used in a Using statement. + /// + public const string DisposeMethodName = "Dispose"; } }