未验证 提交 a22a1e0a 编写于 作者: C Chris Sienkiewicz 提交者: GitHub

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

上级 668fe835
......@@ -512,4 +512,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6F599E08-A9EA-4FAA-897F-5D824B0210E6}
EndGlobalSection
EndGlobal
\ No newline at end of file
EndGlobal
......@@ -10,7 +10,6 @@
namespace Microsoft.CodeAnalysis.CSharp {
using System;
using System.Reflection;
/// <summary>
......@@ -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 {
}
}
/// <summary>
/// Looks up a localized string similar to A goto cannot jump to a location before a using declaration within the same block..
/// </summary>
internal static string ERR_GoToBackwardJumpOverUsingVar {
get {
return ResourceManager.GetString("ERR_GoToBackwardJumpOverUsingVar", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A goto cannot jump to a location after a using declaration..
/// </summary>
internal static string ERR_GoToForwardJumpOverUsingVar {
get {
return ResourceManager.GetString("ERR_GoToForwardJumpOverUsingVar", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The non-generic {1} &apos;{0}&apos; cannot be used with type arguments.
/// </summary>
......@@ -10629,20 +10646,20 @@ internal class CSharpResources {
}
/// <summary>
/// Looks up a localized string similar to alternative interpolated verbatim strings.
/// Looks up a localized string similar to disposable.
/// </summary>
internal static string IDS_FeatureAltInterpolatedVerbatimStrings {
internal static string IDS_Disposable {
get {
return ResourceManager.GetString("IDS_FeatureAltInterpolatedVerbatimStrings", resourceCulture);
return ResourceManager.GetString("IDS_Disposable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to disposable.
/// Looks up a localized string similar to alternative interpolated verbatim strings.
/// </summary>
internal static string IDS_Disposable {
internal static string IDS_FeatureAltInterpolatedVerbatimStrings {
get {
return ResourceManager.GetString("IDS_Disposable", resourceCulture);
return ResourceManager.GetString("IDS_FeatureAltInterpolatedVerbatimStrings", resourceCulture);
}
}
......
......@@ -5634,4 +5634,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_UsingVarInSwitchCase" xml:space="preserve">
<value>A using variable cannot be used directly within a switch section (consider using braces). </value>
</data>
</root>
<data name="ERR_GoToForwardJumpOverUsingVar" xml:space="preserve">
<value>A goto cannot jump to a location after a using declaration.</value>
</data>
<data name="ERR_GoToBackwardJumpOverUsingVar" xml:space="preserve">
<value>A goto cannot jump to a location before a using declaration within the same block.</value>
</data>
</root>
\ No newline at end of file
......@@ -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,
......
......@@ -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)
......
......@@ -11,15 +11,18 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal class ControlFlowPass : AbstractFlowPass<ControlFlowPass.LocalState>
{
private readonly PooledHashSet<LabelSymbol> _labelsDefined = PooledHashSet<LabelSymbol>.GetInstance();
private readonly PooledDictionary<LabelSymbol, BoundBlock> _labelsDefined = PooledDictionary<LabelSymbol, BoundBlock>.GetInstance();
private readonly PooledHashSet<LabelSymbol> _labelsUsed = PooledHashSet<LabelSymbol>.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<PendingBranch> 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;
}
}
}
......@@ -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);
......
// 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<BoundStatement>.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);
}
/// <summary>
/// Visit a partial list of statements that possibly contain using declarations
/// </summary>
/// <param name="builder">The array builder to append statements to</param>
/// <param name="statements">The list of statements to visit</param>
/// <param name="startIndex">The index of the <paramref name="statements"/> to begin visiting at</param>
/// <returns>An <see cref="ImmutableArray{T}"/> of <see cref="BoundStatement"/></returns>
public void VisitStatementSubList(ArrayBuilder<BoundStatement> builder, ImmutableArray<BoundStatement> 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;
}
}
}
/// <summary>
/// Visits a node that is possibly a <see cref="BoundUsingLocalDeclarations"/>
/// </summary>
/// <param name="node">The node to visit</param>
/// <param name="statements">All statements in the block containing this node</param>
/// <param name="statementIndex">The current statement being visited in <paramref name="statements"/></param>
/// <param name="replacedLocalDeclarations">Set to true if this visited a <see cref="BoundUsingLocalDeclarations"/> node</param>
/// <returns>A <see cref="BoundStatement"/></returns>
/// <remarks>
/// 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
/// </remarks>
public BoundStatement VisitPossibleUsingDeclaration(BoundStatement node, ImmutableArray<BoundStatement> 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<BoundStatement> builder = ArrayBuilder<BoundStatement>.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)
......
......@@ -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)
......
......@@ -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);
}
}
}
......@@ -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<BoundLocalDeclaration> declarations = node.DeclarationsOpt.LocalDeclarations;
private BoundStatement MakeDeclarationUsingStatement(SyntaxNode syntax,
BoundBlock body,
ImmutableArray<LocalSymbol> locals,
ImmutableArray<BoundLocalDeclaration> 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<BoundStatement>(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<BoundStatement>(result));
}
/// <summary>
/// Lower "using var x = (expression)" to a try-finally block.
/// </summary>
private BoundStatement MakeLocalUsingDeclarationStatement(BoundUsingLocalDeclarations usingDeclarations, ImmutableArray<BoundStatement> statements)
{
SyntaxNode syntax = usingDeclarations.Syntax;
BoundBlock body = new BoundBlock(syntax, ImmutableArray<LocalSymbol>.Empty, statements);
//PROTOTYPE: need to handle await using when boundMultipleLocalDeclarations supports it
var usingStatement = MakeDeclarationUsingStatement(syntax,
body,
ImmutableArray<LocalSymbol>.Empty,
usingDeclarations.LocalDeclarations,
usingDeclarations.IDisposableConversion,
usingDeclarations.DisposeMethodOpt,
awaitOpt: null,
awaitKeyword: default);
return usingStatement;
}
/// <summary>
/// Lower "using [await] (expression) statement" to a try-finally block.
/// </summary>
private BoundBlock RewriteExpressionUsingStatement(BoundUsingStatement node, BoundBlock tryBlock)
private BoundBlock MakeExpressionUsingStatement(BoundUsingStatement node, BoundBlock tryBlock)
{
Debug.Assert(node.ExpressionOpt != null);
Debug.Assert(node.DeclarationsOpt == null);
......
// 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<BoundStatement> 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<BoundStatement> statements)
{
int itemCount = statements.Length;
int firstUsingIndex = 0;
ArrayBuilder<BoundStatement> reversedStatements = ArrayBuilder<BoundStatement>.GetInstance(itemCount);
reversedStatements.AddRange(statements, itemCount);
reversedStatements.ReverseContents();
ArrayBuilder<BoundUsingStatement> reversedUsingStatements = ArrayBuilder<BoundUsingStatement>.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<LocalSymbol>.Empty,
statements: followingStatements.AsImmutable()
);
BoundUsingStatement boundUsing = new BoundUsingStatement(
syntax: localDeclaration.Syntax,
locals: ImmutableArray<LocalSymbol>.Empty,
declarationsOpt: localDeclaration,
expressionOpt: null,
iDisposableConversion: localDeclaration.IDisposableConversion,
awaitOpt: null,
disposeMethodOpt: localDeclaration.DisposeMethodOpt,
body: innerBlock
);
reversedUsingStatements.Add(boundUsing);
}
}
reversedStatements.Free();
ArrayBuilder<BoundStatement> precedingStatements = ArrayBuilder<BoundStatement>.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<BoundStatement> GetFollowingStatements(ImmutableArray<BoundStatement> statements, int startingPoint)
{
ArrayBuilder<BoundStatement> followingStatements = ArrayBuilder<BoundStatement>.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;
}
}
}
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -82,6 +82,16 @@
<target state="new">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'?</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToBackwardJumpOverUsingVar">
<source>A goto cannot jump to a location before a using declaration within the same block.</source>
<target state="new">A goto cannot jump to a location before a using declaration within the same block.</target>
<note />
</trans-unit>
<trans-unit id="ERR_GoToForwardJumpOverUsingVar">
<source>A goto cannot jump to a location after a using declaration.</source>
<target state="new">A goto cannot jump to a location after a using declaration.</target>
<note />
</trans-unit>
<trans-unit id="ERR_InDynamicMethodArg">
<source>Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</source>
<target state="new">Arguments with 'in' modifier cannot be used in dynamically dispatched expessions.</target>
......
......@@ -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<IDisposable> GetDisposable()
{
return Task.FromResult<IDisposable>(new C2());
}
static Task<IDisposable> 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; ");
}
}
}
// 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
{
/// <summary>
/// Tests related to binding (but not lowering) using declarations (i.e. using var x = ...).
/// </summary>
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<IDisposable> GetDisposable()
{
return Task.FromResult<IDisposable>(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<IDisposable> 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; ");
}
}
}
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册