From a22a1e0aae8861b3963c76563a219718ff2bb433 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Thu, 20 Dec 2018 21:46:08 -0800 Subject: [PATCH] Using declaration lowering rewrite (#31674) * Fix data flow pass to not report using declarations as unused: - Report using declaration variables as read - Add test * Refactor LocalUsingVarRewriter: - Delete LocalUsingVarRewriter - Split UsingStatement lowering into visit + make methods - Split LabelStatement lowering into visit + make methods - Correctly lower using declarations with instrumentation - Add tests * Recursively lower using declarations: Visit a 'sublist' of statements in a block: - For each statement in the sublist, visit with the set of statements after it: - If the statement is a label, recursively visit the body with the same set of following statements - If the statement is a using declaration, recursively visit the following statements and use those as the body of the using - Other statements are visited as normal - If we saw a using during the statement visit, we've finished lowering. If not, continue down the list * Disallow goto's across using declarations as per LDM decisions: - Note the using variables in a block, and currently in scope - When visiting a goto, check the location of the branch and error if it crosses illegally - Add an extra error to distinguish between forward and backward goto errors - Add tests --- Compilers.sln | 2 +- .../Portable/CSharpResources.Designer.cs | 33 +- .../CSharp/Portable/CSharpResources.resx | 8 +- .../Portable/Compiler/MethodCompiler.cs | 4 +- .../CSharp/Portable/Errors/ErrorCode.cs | 3 +- .../Portable/FlowAnalysis/ControlFlowPass.cs | 60 +- .../Portable/FlowAnalysis/DataFlowPass.cs | 9 + .../LocalRewriter/LocalRewriter_Block.cs | 71 +- .../LocalRewriter_LabeledStatement.cs | 4 + ...LocalRewriter_MultipleLocalDeclarations.cs | 5 + .../LocalRewriter_UsingStatement.cs | 74 ++- .../LocalRewriter/LocalUsingVarRewriter.cs | 112 ---- .../Portable/xlf/CSharpResources.cs.xlf | 10 + .../Portable/xlf/CSharpResources.de.xlf | 10 + .../Portable/xlf/CSharpResources.es.xlf | 10 + .../Portable/xlf/CSharpResources.fr.xlf | 10 + .../Portable/xlf/CSharpResources.it.xlf | 10 + .../Portable/xlf/CSharpResources.ja.xlf | 10 + .../Portable/xlf/CSharpResources.ko.xlf | 10 + .../Portable/xlf/CSharpResources.pl.xlf | 10 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 + .../Portable/xlf/CSharpResources.ru.xlf | 10 + .../Portable/xlf/CSharpResources.tr.xlf | 10 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 + ...sts.cs => CodeGenUsingDeclarationTests.cs} | 439 +++++++++++- .../Semantics/UsingDeclarationTests.cs | 624 ++++++++++++++++++ .../Semantic/Semantics/UsingStatementTests.cs | 51 -- 28 files changed, 1416 insertions(+), 213 deletions(-) delete mode 100644 src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalUsingVarRewriter.cs rename src/Compilers/CSharp/Test/Emit/CodeGen/{CodeGenUsingVariableTests.cs => CodeGenUsingDeclarationTests.cs} (59%) create mode 100644 src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs diff --git a/Compilers.sln b/Compilers.sln index 41868969cb8..137403f2a2a 100644 --- a/Compilers.sln +++ b/Compilers.sln @@ -512,4 +512,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6F599E08-A9EA-4FAA-897F-5D824B0210E6} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 8644a6aedad..5bad42e701a 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.CSharp { using System; - using System.Reflection; /// @@ -40,7 +39,7 @@ internal class CSharpResources { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.CSharp.CSharpResources", typeof(CSharpResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.CSharp.CSharpResources", typeof(CSharpResources).Assembly); resourceMan = temp; } return resourceMan; @@ -5218,6 +5217,24 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to A goto cannot jump to a location before a using declaration within the same block.. + /// + internal static string ERR_GoToBackwardJumpOverUsingVar { + get { + return ResourceManager.GetString("ERR_GoToBackwardJumpOverUsingVar", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A goto cannot jump to a location after a using declaration.. + /// + internal static string ERR_GoToForwardJumpOverUsingVar { + get { + return ResourceManager.GetString("ERR_GoToForwardJumpOverUsingVar", resourceCulture); + } + } + /// /// Looks up a localized string similar to The non-generic {1} '{0}' cannot be used with type arguments. /// @@ -10629,20 +10646,20 @@ internal class CSharpResources { } /// - /// Looks up a localized string similar to alternative interpolated verbatim strings. + /// Looks up a localized string similar to disposable. /// - internal static string IDS_FeatureAltInterpolatedVerbatimStrings { + internal static string IDS_Disposable { get { - return ResourceManager.GetString("IDS_FeatureAltInterpolatedVerbatimStrings", resourceCulture); + return ResourceManager.GetString("IDS_Disposable", resourceCulture); } } /// - /// Looks up a localized string similar to disposable. + /// Looks up a localized string similar to alternative interpolated verbatim strings. /// - internal static string IDS_Disposable { + internal static string IDS_FeatureAltInterpolatedVerbatimStrings { get { - return ResourceManager.GetString("IDS_Disposable", resourceCulture); + return ResourceManager.GetString("IDS_FeatureAltInterpolatedVerbatimStrings", resourceCulture); } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index db0efc9e036..fa677d606ff 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5634,4 +5634,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A using variable cannot be used directly within a switch section (consider using braces). - + + A goto cannot jump to a location after a using declaration. + + + A goto cannot jump to a location before a using declaration within the same block. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 8f0a9b8a51f..64979b6aaec 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1253,8 +1253,6 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum try { - BoundNode usingVarLoweredNode = LocalUsingVarRewriter.Rewrite(body); - BoundStatement toLower = usingVarLoweredNode == null ? body : (BoundStatement)usingVarLoweredNode; bool sawLambdas; bool sawLocalFunctions; @@ -1264,7 +1262,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum method, methodOrdinal, method.ContainingType, - toLower, + body, compilationState, previousSubmissionFields: previousSubmissionFields, allowOmissionOfConditionalCalls: true, diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 0b17a473c7d..c359986e29c 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1634,7 +1634,8 @@ internal enum ErrorCode WRN_CantInferNullabilityOfMethodTypeArgs = 8638, WRN_NoBestNullabilityArrayElements = 8639, ERR_UsingVarInSwitchCase = 8640, - + ERR_GoToForwardJumpOverUsingVar = 8641, + ERR_GoToBackwardJumpOverUsingVar = 8642, #endregion diagnostics introduced for C# 8.0 // Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs index d9d3cc91f51..c1bfce93e8c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs @@ -11,15 +11,18 @@ namespace Microsoft.CodeAnalysis.CSharp { internal class ControlFlowPass : AbstractFlowPass { - private readonly PooledHashSet _labelsDefined = PooledHashSet.GetInstance(); + private readonly PooledDictionary _labelsDefined = PooledDictionary.GetInstance(); private readonly PooledHashSet _labelsUsed = PooledHashSet.GetInstance(); protected bool _convertInsufficientExecutionStackExceptionToCancelledByStackGuardException = false; // By default, just let the original exception to bubble up. + private readonly ArrayBuilder<(LocalSymbol symbol, BoundBlock block)> _usingDeclarations = ArrayBuilder<(LocalSymbol, BoundBlock)>.GetInstance(); + private BoundBlock _currentBlock = null; + protected override void Free() { _labelsDefined.Free(); _labelsUsed.Free(); - + _usingDeclarations.Free(); base.Free(); } @@ -115,7 +118,7 @@ protected override ImmutableArray Scan(ref bool badRegion) { this.Diagnostics.Clear(); // clear reported diagnostics var result = base.Scan(ref badRegion); - foreach (var label in _labelsDefined) + foreach (var label in _labelsDefined.Keys) { if (!_labelsUsed.Contains(label)) { @@ -295,7 +298,7 @@ protected override void VisitFinallyBlock(BoundStatement finallyBlock, ref Local protected override void VisitLabel(BoundLabeledStatement node) { - _labelsDefined.Add(node.Label); + _labelsDefined[node.Label] = _currentBlock; base.VisitLabel(node); } @@ -310,6 +313,35 @@ public override BoundNode VisitLabeledStatement(BoundLabeledStatement node) public override BoundNode VisitGotoStatement(BoundGotoStatement node) { _labelsUsed.Add(node.Label); + + // check for illegal jumps across using declarations + var sourceLocation = node.Syntax.Location; + var sourceStart = sourceLocation.SourceSpan.Start; + var targetStart = node.Label.Locations[0].SourceSpan.Start; + + foreach (var usingDecl in _usingDeclarations) + { + var usingStart = usingDecl.symbol.Locations[0].SourceSpan.Start; + if (sourceStart < usingStart && targetStart > usingStart) + { + // No forward jumps + Diagnostics.Add(ErrorCode.ERR_GoToForwardJumpOverUsingVar, sourceLocation); + break; + } + else if(sourceStart > usingStart && targetStart < usingStart) + { + // Backwards jump, so we must have already seen the label + Debug.Assert(_labelsDefined.ContainsKey(node.Label)); + + // Error if label and using are part of the same block + if (_labelsDefined[node.Label] == usingDecl.block) + { + Diagnostics.Add(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, sourceLocation); + break; + } + } + } + return base.VisitGotoStatement(node); } @@ -347,5 +379,25 @@ protected override void VisitPatternSwitchSection(BoundPatternSwitchSection node new SourceLocation(syntax), syntax.ToString()); } } + + public override BoundNode VisitBlock(BoundBlock node) + { + var parentBlock = _currentBlock; + _currentBlock = node; + var initialUsingCount = _usingDeclarations.Count; + foreach(var local in node.Locals) + { + if (local.IsUsing) + { + _usingDeclarations.Add((local, node)); + } + } + + var result = base.VisitBlock(node); + + _usingDeclarations.Clip(initialUsingCount); + _currentBlock = parentBlock; + return result; + } } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index bca81cb3ac9..6bb125215f3 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -1437,6 +1437,15 @@ public override BoundNode VisitBlock(BoundBlock node) VisitStatementsWithLocalFunctions(node); + // any local using symbols are implicitly read at the end of the block when they get disposed + foreach (var local in node.Locals) + { + if (local.IsUsing) + { + NoteRead(local); + } + } + ReportUnusedVariables(node.Locals); ReportUnusedVariables(node.LocalFunctions); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs index 29a55531cba..9812f29edbf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs @@ -1,5 +1,6 @@ // 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; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -13,17 +14,12 @@ internal sealed partial class LocalRewriter { public override BoundNode VisitBlock(BoundBlock node) { - if (!this.Instrument || (node != _rootStatement && (node.WasCompilerGenerated || node.Syntax.Kind() != SyntaxKind.Block))) - { - return node.Update(node.Locals, node.LocalFunctions, VisitList(node.Statements)); - } - var builder = ArrayBuilder.GetInstance(); + VisitStatementSubList(builder, node.Statements); - for (int i = 0; i < node.Statements.Length; i++) + if (!this.Instrument || (node != _rootStatement && (node.WasCompilerGenerated || node.Syntax.Kind() != SyntaxKind.Block))) { - var stmt = (BoundStatement)Visit(node.Statements[i]); - if (stmt != null) builder.Add(stmt); + return node.Update(node.Locals, node.LocalFunctions, builder.ToImmutableAndFree()); } LocalSymbol synthesizedLocal; @@ -42,6 +38,65 @@ public override BoundNode VisitBlock(BoundBlock node) return new BoundBlock(node.Syntax, synthesizedLocal == null ? node.Locals : node.Locals.Add(synthesizedLocal), node.LocalFunctions, builder.ToImmutableAndFree(), node.HasErrors); } + + /// + /// Visit a partial list of statements that possibly contain using declarations + /// + /// The array builder to append statements to + /// The list of statements to visit + /// The index of the to begin visiting at + /// An of + public void VisitStatementSubList(ArrayBuilder builder, ImmutableArray statements, int startIndex = 0) + { + for (int i = startIndex; i < statements.Length; i++) + { + BoundStatement statement = VisitPossibleUsingDeclaration(statements[i], statements, i, out var replacedUsingDeclarations); + if (statement != null) + { + builder.Add(statement); + } + + if (replacedUsingDeclarations) + { + break; + } + } + } + + /// + /// Visits a node that is possibly a + /// + /// The node to visit + /// All statements in the block containing this node + /// The current statement being visited in + /// Set to true if this visited a node + /// A + /// + /// The node being visited is not necessarily equal to statements[startIndex]. + /// When traversing down a set of labels, we set node to the label.body and recurse, but statements[startIndex] still refers to the original parent label + /// as we haven't actually moved down the original statement list + /// + public BoundStatement VisitPossibleUsingDeclaration(BoundStatement node, ImmutableArray statements, int statementIndex, out bool replacedLocalDeclarations) + { + switch (node.Kind) + { + case BoundKind.LabeledStatement: + var labelStatement = (BoundLabeledStatement)node; + return MakeLabeledStatement(labelStatement, VisitPossibleUsingDeclaration(labelStatement.Body, statements, statementIndex, out replacedLocalDeclarations)); + case BoundKind.UsingLocalDeclarations: + // visit everything after this node + ArrayBuilder builder = ArrayBuilder.GetInstance(); + VisitStatementSubList(builder, statements, statementIndex + 1); + // make a using declaration with the visited statements as its body + replacedLocalDeclarations = true; + return MakeLocalUsingDeclarationStatement((BoundUsingLocalDeclarations)node, builder.ToImmutableAndFree()); + default: + replacedLocalDeclarations = false; + return (BoundStatement) Visit(node); + } + } + + public override BoundNode VisitNoOpStatement(BoundNoOpStatement node) { return (node.WasCompilerGenerated || !this.Instrument) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LabeledStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LabeledStatement.cs index a030a06b481..10c4d1e1928 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LabeledStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LabeledStatement.cs @@ -14,7 +14,11 @@ public override BoundNode VisitLabeledStatement(BoundLabeledStatement node) Debug.Assert(node != null); var rewrittenBody = (BoundStatement)Visit(node.Body); + return MakeLabeledStatement(node, rewrittenBody); + } + private BoundStatement MakeLabeledStatement(BoundLabeledStatement node, BoundStatement rewrittenBody) + { BoundStatement labelStatement = new BoundLabelStatement(node.Syntax, node.Label); if (this.Instrument) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MultipleLocalDeclarations.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MultipleLocalDeclarations.cs index e44ca51ab30..349f0f0fead 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MultipleLocalDeclarations.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MultipleLocalDeclarations.cs @@ -38,5 +38,10 @@ public override BoundNode VisitMultipleLocalDeclarations(BoundMultipleLocalDecla return null; // TODO: but what if hasErrors? Have we lost that? } } + + public override BoundNode VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node) + { + return VisitMultipleLocalDeclarations(node); + } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 53d92b84e37..8a4c70d1475 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -37,38 +37,72 @@ public override BoundNode VisitUsingStatement(BoundUsingStatement node) if (node.ExpressionOpt != null) { - return RewriteExpressionUsingStatement(node, tryBlock); + return MakeExpressionUsingStatement(node, tryBlock); } else { - Debug.Assert(node.DeclarationsOpt != null); + SyntaxToken awaitKeyword = node.Syntax.Kind() == SyntaxKind.UsingStatement ? ((UsingStatementSyntax)node.Syntax).AwaitKeyword : default; + return MakeDeclarationUsingStatement(node.Syntax, + tryBlock, + node.Locals, + node.DeclarationsOpt.LocalDeclarations, + node.IDisposableConversion, + node.DisposeMethodOpt, + node.AwaitOpt, + awaitKeyword); + } + } - SyntaxNode usingSyntax = node.Syntax; - SyntaxToken awaitKeyword = usingSyntax.Kind() == SyntaxKind.UsingStatement ? ((UsingStatementSyntax)usingSyntax).AwaitKeyword : default; - Conversion idisposableConversion = node.IDisposableConversion; - ImmutableArray declarations = node.DeclarationsOpt.LocalDeclarations; + private BoundStatement MakeDeclarationUsingStatement(SyntaxNode syntax, + BoundBlock body, + ImmutableArray locals, + ImmutableArray declarations, + Conversion iDisposableConversion, + MethodSymbol disposeMethodOpt, + AwaitableInfo awaitOpt, + SyntaxToken awaitKeyword) + { + Debug.Assert(declarations != null); - BoundBlock result = tryBlock; + BoundBlock result = body; + for (int i = declarations.Length - 1; i >= 0; i--) //NB: inner-to-outer = right-to-left + { + result = RewriteDeclarationUsingStatement(syntax, declarations[i], result, iDisposableConversion, awaitKeyword, awaitOpt, disposeMethodOpt); + } - 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, awaitKeyword, node.AwaitOpt, node.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(result)); + } - // 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( - usingSyntax, - node.Locals, - ImmutableArray.Create(result)); - } + /// + /// Lower "using var x = (expression)" to a try-finally block. + /// + private BoundStatement MakeLocalUsingDeclarationStatement(BoundUsingLocalDeclarations usingDeclarations, ImmutableArray statements) + { + SyntaxNode syntax = usingDeclarations.Syntax; + BoundBlock body = new BoundBlock(syntax, ImmutableArray.Empty, statements); + + //PROTOTYPE: need to handle await using when boundMultipleLocalDeclarations supports it + var usingStatement = MakeDeclarationUsingStatement(syntax, + body, + ImmutableArray.Empty, + usingDeclarations.LocalDeclarations, + usingDeclarations.IDisposableConversion, + usingDeclarations.DisposeMethodOpt, + awaitOpt: null, + awaitKeyword: default); + + return usingStatement; } /// /// Lower "using [await] (expression) statement" to a try-finally block. /// - private BoundBlock RewriteExpressionUsingStatement(BoundUsingStatement node, BoundBlock tryBlock) + private BoundBlock MakeExpressionUsingStatement(BoundUsingStatement node, BoundBlock tryBlock) { Debug.Assert(node.ExpressionOpt != null); Debug.Assert(node.DeclarationsOpt == null); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalUsingVarRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalUsingVarRewriter.cs deleted file mode 100644 index 2d9c0667b1a..00000000000 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalUsingVarRewriter.cs +++ /dev/null @@ -1,112 +0,0 @@ -// 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.Immutable; -using System.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Microsoft.CodeAnalysis.CSharp -{ - internal sealed class LocalUsingVarRewriter : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator - { - public static BoundNode Rewrite(BoundStatement statement) - { - var localUsingVarRewriter = new LocalUsingVarRewriter(); - return localUsingVarRewriter.Visit(statement); - } - - public override BoundNode VisitBlock(BoundBlock node) - { - ImmutableArray statements = this.VisitList(node.Statements); - for (int i = 0; i < statements.Length; i++) - { - if (statements[i] is BoundUsingLocalDeclarations localDeclaration) - { - return LowerBoundLocalDeclarationUsingVar(node, statements); - } - - } - return node; - } - - private BoundBlock LowerBoundLocalDeclarationUsingVar(BoundBlock node, ImmutableArray statements) - { - int itemCount = statements.Length; - int firstUsingIndex = 0; - - ArrayBuilder reversedStatements = ArrayBuilder.GetInstance(itemCount); - reversedStatements.AddRange(statements, itemCount); - reversedStatements.ReverseContents(); - - ArrayBuilder reversedUsingStatements = ArrayBuilder.GetInstance(); - - for (int i = 0; i < itemCount; i++) - { - if (reversedStatements[i] is BoundUsingLocalDeclarations localDeclaration && SwitchBinder.ContainsUsingVariable(reversedStatements[i])) - { - Debug.Assert(!(localDeclaration.IDisposableConversion != Conversion.NoConversion && localDeclaration.DisposeMethodOpt != default)); - - var followingStatements = GetFollowingStatements(statements, itemCount - 1 - i); - firstUsingIndex = i; - - // Append inner using variable to the following statements of the previous one - if (reversedUsingStatements.Count != 0) - { - followingStatements.Add(reversedUsingStatements[reversedUsingStatements.Count - 1]); - } - - BoundBlock innerBlock = new BoundBlock( - syntax: localDeclaration.Syntax, - locals: ImmutableArray.Empty, - statements: followingStatements.AsImmutable() - ); - - BoundUsingStatement boundUsing = new BoundUsingStatement( - syntax: localDeclaration.Syntax, - locals: ImmutableArray.Empty, - declarationsOpt: localDeclaration, - expressionOpt: null, - iDisposableConversion: localDeclaration.IDisposableConversion, - awaitOpt: null, - disposeMethodOpt: localDeclaration.DisposeMethodOpt, - body: innerBlock - ); - reversedUsingStatements.Add(boundUsing); - } - } - reversedStatements.Free(); - ArrayBuilder precedingStatements = ArrayBuilder.GetInstance(itemCount - firstUsingIndex); - for (int i = 0; i < (itemCount - 1 - firstUsingIndex); i++) - { - precedingStatements.Add(statements[i]); - } - if (reversedUsingStatements.Count != 0) - { - precedingStatements.Add(reversedUsingStatements[reversedUsingStatements.Count - 1]); - } - reversedUsingStatements.Free(); - BoundBlock outermostBlock = new BoundBlock( - syntax: node.Syntax, - locals: node.Locals, - statements: precedingStatements.ToImmutableOrEmptyAndFree()); - return outermostBlock; - } - - private ArrayBuilder GetFollowingStatements(ImmutableArray statements, int startingPoint) - { - ArrayBuilder followingStatements = ArrayBuilder.GetInstance(statements.Length); - for (int i = startingPoint + 1; i < statements.Length; i++) - { - if (SwitchBinder.ContainsUsingVariable(statements[i]) && !(statements[i] is BoundReturnStatement)) - { - break; - } - else - { - followingStatements.Add(statements[i]); - } - } - return followingStatements; - } - } -} diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 52c8e528cb2..d1e57d96df3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index b52cdf2ef96..62102c4e58c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index ecf4f60ea73..8afc099be73 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index a7a951dbb2d..013b72e7043 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index fa3123c8585..280682ee630 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 26e22df3058..368da7bfe93 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 9e3082c53ed..69185926e57 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 52d03dfed56..24ba91d144b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7be9e745680..5ac29c27e96 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index e09f2b58b20..be296b6c3d5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index f857a90422a..7aec4bd302d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 19f1b542b7e..ae09c247e52 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index db68cd40fa7..39d1c742967 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -82,6 +82,16 @@ foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance definition for '{1}'. Did you mean 'await foreach'? + + A goto cannot jump to a location before a using declaration within the same block. + A goto cannot jump to a location before a using declaration within the same block. + + + + A goto cannot jump to a location after a using declaration. + A goto cannot jump to a location after a using declaration. + + Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. Arguments with 'in' modifier cannot be used in dynamically dispatched expessions. diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingVariableTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs similarity index 59% rename from src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingVariableTests.cs rename to src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs index f2b713c1dab..807f0ae6874 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingVariableTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs @@ -3,7 +3,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { - public class CodeGenUsingVariableTests : EmitMetadataTestBase + public class CodeGenUsingDeclarationTests : EmitMetadataTestBase { [Fact] public void UsingVariableVarEmitTest() @@ -45,7 +45,7 @@ .maxstack 1 } [Fact] - public void UseUsingVariableEmitTest() + public void UsingVariableEmitTest() { string source = @" using System; @@ -131,7 +131,7 @@ .maxstack 1 } [Fact] - public void PreexistingVariablesUsingVariableEmitTest() + public void PreexistingVariablesUsingDeclarationEmitTest() { string source = @" using System; @@ -349,7 +349,276 @@ .maxstack 1 } [Fact] - public void UsingVariableUsingPatternIntersectionEmitTest() + public void AsPartOfLabelStatement() + { + string source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { Console.Write(""Dispose; "");} +} +class C2 +{ + public static void Main() + { + label1: + using C1 o1 = new C1(); + using C1 o2 = new C1(); + label2: + using C1 o3 = new C1(); + } +}"; + CompileAndVerify(source, expectedOutput: "Dispose; Dispose; Dispose; ").VerifyIL("C2.Main", @" +{ + // Code size 51 (0x33) + .maxstack 1 + .locals init (C1 V_0, //o1 + C1 V_1, //o2 + C1 V_2) //o3 + IL_0000: newobj ""C1..ctor()"" + IL_0005: stloc.0 + .try + { + IL_0006: newobj ""C1..ctor()"" + IL_000b: stloc.1 + .try + { + IL_000c: newobj ""C1..ctor()"" + IL_0011: stloc.2 + .try + { + IL_0012: leave.s IL_0032 + } + finally + { + IL_0014: ldloc.2 + IL_0015: brfalse.s IL_001d + IL_0017: ldloc.2 + IL_0018: callvirt ""void System.IDisposable.Dispose()"" + IL_001d: endfinally + } + } + finally + { + IL_001e: ldloc.1 + IL_001f: brfalse.s IL_0027 + IL_0021: ldloc.1 + IL_0022: callvirt ""void System.IDisposable.Dispose()"" + IL_0027: endfinally + } + } + finally + { + IL_0028: ldloc.0 + IL_0029: brfalse.s IL_0031 + IL_002b: ldloc.0 + IL_002c: callvirt ""void System.IDisposable.Dispose()"" + IL_0031: endfinally + } + IL_0032: ret +} +"); + } + + [Fact] + public void AsPartOfMultipleLabelStatements() + { + string source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { Console.Write(""Dispose; "");} +} +class C2 +{ + public static void Main() + { + label1: + label2: + Console.Write(""Start; ""); + label3: + label4: + label5: + label6: + using C1 o1 = new C1(); + Console.Write(""Middle1; ""); + using C1 o2 = new C1(); + Console.Write(""Middle2; ""); + label7: + using C1 o3 = new C1(); + Console.Write(""End; ""); + } +}"; + CompileAndVerify(source, expectedOutput: "Start; Middle1; Middle2; End; Dispose; Dispose; Dispose; ").VerifyIL("C2.Main", @" +{ + // Code size 91 (0x5b) + .maxstack 1 + .locals init (C1 V_0, //o1 + C1 V_1, //o2 + C1 V_2) //o3 + IL_0000: ldstr ""Start; "" + IL_0005: call ""void System.Console.Write(string)"" + IL_000a: newobj ""C1..ctor()"" + IL_000f: stloc.0 + .try + { + IL_0010: ldstr ""Middle1; "" + IL_0015: call ""void System.Console.Write(string)"" + IL_001a: newobj ""C1..ctor()"" + IL_001f: stloc.1 + .try + { + IL_0020: ldstr ""Middle2; "" + IL_0025: call ""void System.Console.Write(string)"" + IL_002a: newobj ""C1..ctor()"" + IL_002f: stloc.2 + .try + { + IL_0030: ldstr ""End; "" + IL_0035: call ""void System.Console.Write(string)"" + IL_003a: leave.s IL_005a + } + finally + { + IL_003c: ldloc.2 + IL_003d: brfalse.s IL_0045 + IL_003f: ldloc.2 + IL_0040: callvirt ""void System.IDisposable.Dispose()"" + IL_0045: endfinally + } + } + finally + { + IL_0046: ldloc.1 + IL_0047: brfalse.s IL_004f + IL_0049: ldloc.1 + IL_004a: callvirt ""void System.IDisposable.Dispose()"" + IL_004f: endfinally + } + } + finally + { + IL_0050: ldloc.0 + IL_0051: brfalse.s IL_0059 + IL_0053: ldloc.0 + IL_0054: callvirt ""void System.IDisposable.Dispose()"" + IL_0059: endfinally + } + IL_005a: ret +} +"); + } + + [Fact] + public void InsideTryCatchFinallyBlocks() + { + string source = @" +using System; +class C1 : IDisposable +{ + public string Text { get; set; } + public void Dispose() { Console.Write($""Dispose {Text}; "");} +} +class C2 +{ + public static void Main() + { + try + { + using var x = new C1() { Text = ""Try"" }; + throw new Exception(); + } + catch + { + using var x = new C1(){ Text = ""Catch"" }; + } + finally + { + using var x = new C1(){ Text = ""Finally"" }; + } + } +}"; + CompileAndVerify(source, expectedOutput: "Dispose Try; Dispose Catch; Dispose Finally; ").VerifyIL("C2.Main", @" +{ + // Code size 96 (0x60) + .maxstack 3 + .locals init (C1 V_0, //x + C1 V_1, //x + C1 V_2) //x + .try + { + .try + { + IL_0000: newobj ""C1..ctor()"" + IL_0005: dup + IL_0006: ldstr ""Try"" + IL_000b: callvirt ""void C1.Text.set"" + IL_0010: stloc.0 + .try + { + IL_0011: newobj ""System.Exception..ctor()"" + IL_0016: throw + } + finally + { + IL_0017: ldloc.0 + IL_0018: brfalse.s IL_0020 + IL_001a: ldloc.0 + IL_001b: callvirt ""void System.IDisposable.Dispose()"" + IL_0020: endfinally + } + } + catch object + { + IL_0021: pop + IL_0022: newobj ""C1..ctor()"" + IL_0027: dup + IL_0028: ldstr ""Catch"" + IL_002d: callvirt ""void C1.Text.set"" + IL_0032: stloc.1 + .try + { + IL_0033: leave.s IL_003f + } + finally + { + IL_0035: ldloc.1 + IL_0036: brfalse.s IL_003e + IL_0038: ldloc.1 + IL_0039: callvirt ""void System.IDisposable.Dispose()"" + IL_003e: endfinally + } + IL_003f: leave.s IL_005f + } + } + finally + { + IL_0041: newobj ""C1..ctor()"" + IL_0046: dup + IL_0047: ldstr ""Finally"" + IL_004c: callvirt ""void C1.Text.set"" + IL_0051: stloc.2 + .try + { + IL_0052: leave.s IL_005e + } + finally + { + IL_0054: ldloc.2 + IL_0055: brfalse.s IL_005d + IL_0057: ldloc.2 + IL_0058: callvirt ""void System.IDisposable.Dispose()"" + IL_005d: endfinally + } + IL_005e: endfinally + } + IL_005f: ret +} +"); + } + + [Fact] + public void UsingDeclarationUsingPatternIntersectionEmitTest() { var source = @" using System; @@ -456,6 +725,54 @@ .maxstack 1 }"); } + [Fact] + public void UsingDeclarationUsingPatternExtensionMethod() + { + var source = @" + using System; + class C1 + { + } + internal static class C2 + { + internal static void Dispose(this C1 c1) + { + Console.Write(""Disposed; ""); + } + } + class Program + { + static void Main(string[] args) + { + using C1 o1 = new C1(); + } + }"; + + var output = @"Disposed; "; + CompileAndVerify(source, expectedOutput: output).VerifyIL("Program.Main", @" +{ + // Code size 19 (0x13) + .maxstack 1 + .locals init (C1 V_0) //o1 + 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: call ""void C2.Dispose(C1)"" + IL_0011: endfinally + } + IL_0012: ret +} +"); + } + [Fact] public void MultipleUsingVarEmitTest() { @@ -658,5 +975,119 @@ .maxstack 1 IL_003b: ret }"); } + + [Fact] + public void JumpBackOverUsingDeclaration() + { + string source = @" +using System; +class C1 : IDisposable +{ + private string name; + public C1(string name) + { + this.name = name; + } + public void Dispose() + { + Console.WriteLine(""Disposed "" + name); + } +} +class C2 +{ + public static void Main() + { + int x = 0; + label1: + { + using C1 o1 = new C1(""first""); + if(x++ < 3) + { + goto label1; + } + } + } +}"; + var output = @"Disposed first +Disposed first +Disposed first +Disposed first"; + CompileAndVerify(source, expectedOutput: output).VerifyIL("C2.Main", @" +{ + // Code size 36 (0x24) + .maxstack 3 + .locals init (int V_0, //x + C1 V_1) //o1 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldstr ""first"" + IL_0007: newobj ""C1..ctor(string)"" + IL_000c: stloc.1 + .try + { + IL_000d: ldloc.0 + IL_000e: dup + IL_000f: ldc.i4.1 + IL_0010: add + IL_0011: stloc.0 + IL_0012: ldc.i4.3 + IL_0013: bge.s IL_0017 + IL_0015: leave.s IL_0002 + IL_0017: leave.s IL_0023 + } + finally + { + IL_0019: ldloc.1 + IL_001a: brfalse.s IL_0022 + IL_001c: ldloc.1 + IL_001d: callvirt ""void System.IDisposable.Dispose()"" + IL_0022: endfinally + } + IL_0023: ret +} +"); + } + + [Fact] + public void UsingVariableFromAwaitExpressionDisposesOnlyIfAwaitSucceeds() + { + var source = @" +using System; +using System.Threading.Tasks; + +class C2 : IDisposable +{ + public void Dispose() + { + Console.Write($""Dispose; ""); + } +} + +class C +{ + static Task GetDisposable() + { + return Task.FromResult(new C2()); + } + + static Task GetDisposableError() + { + throw null; + } + + static async Task Main() + { + try + { + using IDisposable x = await GetDisposable(); // disposed + using IDisposable y = await GetDisposableError(); // not disposed as never assigned + } + catch { } + } +} +"; + CompileAndVerify(source, expectedOutput: "Dispose; "); + } + } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs new file mode 100644 index 00000000000..e8dc71aae10 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs @@ -0,0 +1,624 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.CSharp.UnitTests; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.Semantics +{ + /// + /// Tests related to binding (but not lowering) using declarations (i.e. using var x = ...). + /// + public class UsingDeclarationTests : CompilingTestBase + { + [Fact] + public void UsingVariableIsNotReportedAsUnused() + { + var source = @" +using System; +class C +{ + static void Main() + { + using var x = (IDisposable)null; + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void DisallowGoToForwardAcrossUsingDeclarations() + { + var source = @" +using System; +class C +{ + static void Main() + { + goto label1; + using var x = (IDisposable)null; + + label1: + return; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label1; + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label1;").WithLocation(7, 9), + // (8,9): warning CS0162: Unreachable code detected + // using var x = (IDisposable)null; + Diagnostic(ErrorCode.WRN_UnreachableCode, "using").WithLocation(8, 9) + ); + } + + [Fact] + public void DisallowGoToForwardAcrossUsingDeclarationsFromLowerBlock() + { + var source = @" +using System; +class C +{ + static void Main() + { + { + goto label1; + } + using var x = (IDisposable)null; + + label1: + return; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label1; + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label1;").WithLocation(8, 13), + // (8,9): warning CS0162: Unreachable code detected + // using var x = (IDisposable)null; + Diagnostic(ErrorCode.WRN_UnreachableCode, "using").WithLocation(10, 9) + ); + } + + [Fact] + public void DisallowGoToForwardAcrossMultipleUsingDeclarationsGivesOnlyOneDiagnostic() + { + var source = @" +using System; +class C +{ + static void Main() + { + goto label1; + using var x = (IDisposable)null; + using var y = (IDisposable)null; + + label1: + return; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label1; + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label1;").WithLocation(7, 9), + // (8,9): warning CS0162: Unreachable code detected + // using var x = (IDisposable)null; + Diagnostic(ErrorCode.WRN_UnreachableCode, "using").WithLocation(8, 9) + ); + } + + [Fact] + public void DisallowMultipleGoToForwardAcrossMultipleUsingDeclarations() + { + var source = @" +using System; +class C +{ + static void Main() + { + goto label1; + using var x = (IDisposable)null; + goto label2; + using var y = (IDisposable)null; + + label1: + label2: + return; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label1; + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label1;").WithLocation(7, 9), + // (8,9): warning CS0162: Unreachable code detected + // using var x = (IDisposable)null; + Diagnostic(ErrorCode.WRN_UnreachableCode, "using").WithLocation(8, 9), + // (9,9): error CS8641: A goto target can not be after any using declarations. + // goto label2; + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label2;").WithLocation(9, 9) + ); + } + + [Fact] + public void DisallowGoToBackwardsAcrossUsingDeclarationsWhenLabelIsInTheSameScope() + { + var source = @" +using System; +class C +{ + static void Main() + { + label1: + using var x = (IDisposable)null; + + goto label1; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label1; + Diagnostic(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, "goto label1;").WithLocation(10, 9) + ); + } + + [Fact] + public void DisallowGoToBackwardsAcrossUsingDeclarationsWithMultipleLabels() + { + var source = @" +using System; +class C +{ + static void Main() + { + label1: + label2: + label3: + using var x = (IDisposable)null; + + goto label3; // disallowed + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): warning CS0164: This label has not been referenced + // label1: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "label1").WithLocation(7, 9), + // (8,9): warning CS0164: This label has not been referenced + // label2: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "label2").WithLocation(8, 9), + // (10,9): error CS8641: A goto target within the same block can not cross a using declaration. + // goto label3; // disallowed + Diagnostic(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, "goto label3;").WithLocation(12, 9) + ); + } + + [Fact] + public void DisallowGoToAcrossUsingDeclarationsComplexTest() + { + var source = @" +using System; +#pragma warning disable 162 // disable unreachable code warnings +class C +{ + static void Main() + { + label1: + { + label2: + using var a = (IDisposable)null; + goto label1; // allowed + + goto label2; // disallowed 1 + } + + label3: + using var b = (IDisposable)null; + { + goto label3; // disallowed 2 + } + + { + goto label4; // allowed + goto label5; // disallowed 3 + label4: + using var c = (IDisposable)null; + label5: + using var d = (IDisposable)null; + } + + using var e = (IDisposable)null; + label6: + { + { + goto label6; //allowed + label7: + { + label8: + using var f = (IDisposable)null; + goto label7; // allowed + { + using var g = (IDisposable)null; + goto label7; //allowed + goto label8; // disallowed 4 + } + } + } + } + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (14,13): error CS8642: A goto cannot jump to a location before a using declaration within the same block. + // goto label2; // disallowed 1 + Diagnostic(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, "goto label2;").WithLocation(14, 13), + // (20,13): error CS8642: A goto cannot jump to a location before a using declaration within the same block. + // goto label3; // disallowed 2 + Diagnostic(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, "goto label3;").WithLocation(20, 13), + // (25,13): error CS8641: A goto cannot jump to a location after a using declaration. + // goto label5; // disallowed 3 + Diagnostic(ErrorCode.ERR_GoToForwardJumpOverUsingVar, "goto label5;").WithLocation(25, 13), + // (45,25): error CS8642: A goto cannot jump to a location before a using declaration within the same block. + // goto label8; // disallowed 4 + Diagnostic(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, "goto label8;").WithLocation(45, 25) + ); + } + + [Fact] + public void AllowGoToAroundUsingDeclarations() + { + var source = @" +using System; +class C +{ + static void Main() + { + goto label1; + label1: + using var x = (IDisposable)null; + goto label2; + label2: + return; + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void AllowGoToBackwardsAcrossUsingDeclarationsWhenLabelIsInHigherScope() + { + var source = @" +using System; +class C +{ + static void Main() + { + label1: + { + using var x = (IDisposable)null; + goto label1; + } + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void AllowGoToForwardsAcrossUsingDeclarationsInALowerBlock() + { + var source = @" +using System; +class C +{ + static void Main() + { + goto label1; + { + using var x = (IDisposable)null; + } + label1: ; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,9): warning CS0162: Unreachable code detected + // using var x = (IDisposable)null; + Diagnostic(ErrorCode.WRN_UnreachableCode, "using").WithLocation(9, 13) + ); + } + + [Fact] + public void AllowGoToBackwardsAcrossUsingDeclarationsInALowerBlock() + { + var source = @" +using System; +class C +{ + static void Main() + { + label1: + { + using var x = (IDisposable)null; + } + goto label1; + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingVariableCanBeInitializedWithExistingDisposable() + { + var source = @" +using System; +class C2 +{ + public void Dispose() + { + Console.Write(""Disposed; ""); + } +} +class C +{ + static void Main() + { + var x = new C2(); + using var x2 = x; + using var x3 = x2; + x = null; + } +} +"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Disposed; Disposed; "); + } + + [Fact] + public void UsingVariableCanBeInitializedWithExistingDisposableInASingleStatement() + { + var source = @" +using System; +class C2 +{ + public void Dispose() + { + Console.Write(""Disposed; ""); + } +} +class C +{ + static void Main() + { + using C2 x = new C2(), x2 = x; + } +} +"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Disposed; Disposed; "); + } + + [Fact] + public void UsingVariableCannotBeReAssigned() + { + var source = @" +using System; +class C +{ + static void Main() + { + using var x = (IDisposable)null; + x = null; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,9): error CS1656: Cannot assign to 'x' because it is a 'using variable' + // x = null; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "x").WithArguments("x", "using variable").WithLocation(8, 9) + ); + } + + [Fact] + public void UsingVariableCannotBeUsedAsOutVariable() + { + var source = @" +using System; +class C +{ + static void Consume(out IDisposable x) + { + x = null; + } + + static void Main() + { + using var x = (IDisposable)null; + Consume(out x); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,21): error CS1657: Cannot use 'x' as a ref or out value because it is a 'using variable' + // Consume(out x); + Diagnostic(ErrorCode.ERR_RefReadonlyLocalCause, "x").WithArguments("x", "using variable").WithLocation(13, 21) + ); + } + + [Fact] + public void UsingVariableMustHaveInitializer() + { + var source = @" +using System; +class C +{ + static void Main() + { + using IDisposable x; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,27): error CS0210: You must provide an initializer in a fixed or using statement declaration + // using IDisposable x; + Diagnostic(ErrorCode.ERR_FixedMustInit, "x").WithLocation(7, 27) + ); + } + + [Fact] + public void UsingVariableFromExistingVariable() + { + var source = @" +using System; +class C +{ + static void Main() + { + var x = (IDisposable)null; + using var x2 = x; + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingVariableFromExpression() + { + var source = @" +using System; +class C +{ + static IDisposable GetDisposable() + { + return null; + } + + static void Main() + { + using IDisposable x = GetDisposable(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingVariableFromAwaitExpression() + { + var source = @" +using System; +using System.Threading.Tasks; +class C +{ + static Task GetDisposable() + { + return Task.FromResult(null); + } + + static async Task Main() + { + using IDisposable x = await GetDisposable(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void UsingVariableInSwitchCase() + { + var source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void Main() + { + int x = 5; + switch (x) + { + case 5: + using C1 o1 = new C1(); + break; + } + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (15,21): error CS8389: A using variable cannot be used directly within a switch section (consider using braces). + // using C1 o1 = new C1(); + Diagnostic(ErrorCode.ERR_UsingVarInSwitchCase, "using C1 o1 = new C1();").WithLocation(15, 17) + ); + } + + [Fact] + public void UsingVariableDiagnosticsInDeclarationAreOnlyEmittedOnce() + { + var source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void Main() + { + using var c1 = new C1(), c2 = new C2(); + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (11,15): error CS0819: Implicitly-typed variables cannot have multiple declarators + // using var c1 = new C1(), c2 = new C2(); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableMultipleDeclarator, "var c1 = new C1(), c2 = new C2()").WithLocation(11, 15) + ); + } + + [Fact] + public void UsingDeclarationWithAwaitsInAsync() + { + var source = @" +using System; +using System.Threading.Tasks; +class C2 : IDisposable +{ + public string ID { get; set; } + public void Dispose() + { + Console.Write($""Dispose {ID}; ""); + } +} +class C +{ + static async Task GetDisposable(string id) + { + await Task.Yield(); + return new C2(){ ID = id }; + } + + static async Task Main() + { + using IDisposable x = await GetDisposable(""c1""); + await Task.Yield(); + Console.Write(""after c1; ""); + using IDisposable y = await GetDisposable(""c2""); + Console.Write(""after c2; ""); + } +} +"; + var compilation = CreateCompilationWithTasksExtensions(source, options: TestOptions.DebugExe).VerifyDiagnostics(); + + CompileAndVerify(compilation, expectedOutput: "after c1; after c2; Dispose c2; Dispose c1; "); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs index d66bf71c986..798e7fb868d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingStatementTests.cs @@ -2578,57 +2578,6 @@ void M() ); } - [Fact] - public void UsingVarInSwitchCase() - { - var source = @" -using System; -class C1 : IDisposable - { - public void Dispose() { } - } - class C2 - { - public static void Main() - { - int x = 5; - switch (x) - { - case 5: - using C1 o1 = new C1(); - break; - } - } - }"; - CreateCompilation(source).VerifyDiagnostics( - // (15,21): error CS8389: A using variable cannot be used directly within a switch section (consider using braces). - // using C1 o1 = new C1(); - Diagnostic(ErrorCode.ERR_UsingVarInSwitchCase, "using C1 o1 = new C1();").WithLocation(15, 21) - ); - } - - [Fact] - public void DiagnosticsInUsingVariableDeclarationAreOnlyEmittedOnce() - { - var source = @" -using System; -class C1 : IDisposable -{ - public void Dispose() { } -} -class C2 -{ - public static void Main() - { - using var c1 = new C1(), c2 = new C2(); - } -}"; - CreateCompilation(source).VerifyDiagnostics( - // (11,15): error CS0819: Implicitly-typed variables cannot have multiple declarators - // using (var c1 = new C1(), c2 = new C2()) - Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableMultipleDeclarator, "var c1 = new C1(), c2 = new C2()").WithLocation(11, 15) - ); - } #region help method -- GitLab