File: CodeGen\CodeGenUsingDeclarationTests.cs
Web Access
Project: ..\..\..\src\Compilers\CSharp\Test\Emit\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
    public class CodeGenUsingDeclarationTests : EmitMetadataTestBase
    {
        [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)]
        public void UsingVariableVarEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void Dispose() { }
}
class C2
{
    public static void Main()
    {
        using var c1 = new C1(); 
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       19 (0x13)
  .maxstack  1
  .locals init (C1 V_0) //c1
  // sequence point: using var c1 = new C1();
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    // sequence point: }
    IL_0006:  leave.s    IL_0012
  }
  finally
  {
    // sequence point: <hidden>
    IL_0008:  ldloc.0
    IL_0009:  brfalse.s  IL_0011
    IL_000b:  ldloc.0
    IL_000c:  callvirt   ""void System.IDisposable.Dispose()""
    // sequence point: <hidden>
    IL_0011:  endfinally
  }
  // sequence point: }
  IL_0012:  ret
}", sequencePoints: "C2.Main", source: source);
        }
 
        [Fact]
        public void UsingVariableEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void Method1() { }
    public void Dispose() { }
}
class C2
{
    public static void Main()
    {
        using var c1 = new C1(); 
        c1.Method1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init (C1 V_0) //c1
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  ldloc.0
    IL_0007:  callvirt   ""void C1.Method1()""
    IL_000c:  leave.s    IL_0018
  }
  finally
  {
    IL_000e:  ldloc.0
    IL_000f:  brfalse.s  IL_0017
    IL_0011:  ldloc.0
    IL_0012:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0017:  endfinally
  }
  IL_0018:  ret
}");
        }
 
        [Fact]
        public void UsingVariableTypedVariable()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void Method1() { }
    public void Dispose() { }
}
class C2
{
    public static void Main()
    {
        using C1 c1 = new C1(); 
        c1.Method1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init (C1 V_0) //c1
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  ldloc.0
    IL_0007:  callvirt   ""void C1.Method1()""
    IL_000c:  leave.s    IL_0018
  }
  finally
  {
    IL_000e:  ldloc.0
    IL_000f:  brfalse.s  IL_0017
    IL_0011:  ldloc.0
    IL_0012:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0017:  endfinally
  }
  IL_0018:  ret
}");
        }
 
        [Fact]
        public void PreexistingVariablesUsingDeclarationEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void Dispose() { }
    public void Method1() { }
}
class C2
{
    public static void Main()
    {
        C1 c0 = new C1();
        c0.Method1();
        using var c1 = new C1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       29 (0x1d)
  .maxstack  1
  .locals init (C1 V_0) //c1
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  callvirt   ""void C1.Method1()""
  IL_000a:  newobj     ""C1..ctor()""
  IL_000f:  stloc.0
  .try
  {
    IL_0010:  leave.s    IL_001c
  }
  finally
  {
    IL_0012:  ldloc.0
    IL_0013:  brfalse.s  IL_001b
    IL_0015:  ldloc.0
    IL_0016:  callvirt   ""void System.IDisposable.Dispose()""
    IL_001b:  endfinally
  }
  IL_001c:  ret
}");
        }
 
        [Fact]
        public void TwoUsingVarsInARow()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void M() { } 
    public void Dispose() { }
}
class C2
{
    public static void Main()                                                                                                           
    {
        using C1 o1 = new C1();
        using C1 o2 = new C1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       35 (0x23)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  newobj     ""C1..ctor()""
    IL_000b:  stloc.1
    .try
    {
      IL_000c:  leave.s    IL_0022
    }
    finally
    {
      IL_000e:  ldloc.1
      IL_000f:  brfalse.s  IL_0017
      IL_0011:  ldloc.1
      IL_0012:  callvirt   ""void System.IDisposable.Dispose()""
      IL_0017:  endfinally
    }
  }
  finally
  {
    IL_0018:  ldloc.0
    IL_0019:  brfalse.s  IL_0021
    IL_001b:  ldloc.0
    IL_001c:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0021:  endfinally
  }
  IL_0022:  ret
}");
        }
 
        [Fact]
        public void UsingVarSandwich()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void M() { } 
    public void Dispose() { }
}
class C2
{
    public static void Main()                                                                                                           
    {
        using C1 o1 = new C1();
        o1.M();
        using C1 o2 = new C1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       41 (0x29)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  ldloc.0
    IL_0007:  callvirt   ""void C1.M()""
    IL_000c:  newobj     ""C1..ctor()""
    IL_0011:  stloc.1
    .try
    {
      IL_0012:  leave.s    IL_0028
    }
    finally
    {
      IL_0014:  ldloc.1
      IL_0015:  brfalse.s  IL_001d
      IL_0017:  ldloc.1
      IL_0018:  callvirt   ""void System.IDisposable.Dispose()""
      IL_001d:  endfinally
    }
  }
  finally
  {
    IL_001e:  ldloc.0
    IL_001f:  brfalse.s  IL_0027
    IL_0021:  ldloc.0
    IL_0022:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0027:  endfinally
  }
  IL_0028:  ret
}");
        }
 
        [Fact]
        public void InsideOfUsingVarInCorrectOrder()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void M() { } 
    public void Dispose() { }
}
class C2
{
    public static void Main()                                                                                                           
    {
        using C1 o1 = new C1();
        using C1 o2 = new C1();
        o2.M();
        o1.M();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       47 (0x2f)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  newobj     ""C1..ctor()""
    IL_000b:  stloc.1
    .try
    {
      IL_000c:  ldloc.1
      IL_000d:  callvirt   ""void C1.M()""
      IL_0012:  ldloc.0
      IL_0013:  callvirt   ""void C1.M()""
      IL_0018:  leave.s    IL_002e
    }
    finally
    {
      IL_001a:  ldloc.1
      IL_001b:  brfalse.s  IL_0023
      IL_001d:  ldloc.1
      IL_001e:  callvirt   ""void System.IDisposable.Dispose()""
      IL_0023:  endfinally
    }
  }
  finally
  {
    IL_0024:  ldloc.0
    IL_0025:  brfalse.s  IL_002d
    IL_0027:  ldloc.0
    IL_0028:  callvirt   ""void System.IDisposable.Dispose()""
    IL_002d:  endfinally
  }
  IL_002e:  ret
}");
        }
 
        [Fact]
        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 InsideTryCatchFinallyBlocksAsync()
        {
            string source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    public string Text { get; set; }
 
    public C1(string text)
    {
        Text = text;
        Console.WriteLine($""Created {Text}"");
    }
 
    public ValueTask DisposeAsync()
    {
        Console.WriteLine($""Dispose Async {Text}"");
        return new ValueTask(Task.CompletedTask);
    }
}
class C2
{
    public static async Task Main()                                                                                                           
    {
        try
        {
            await using var x = new C1(""Try"");
            throw new Exception();
        }
        catch
        {
            await using var x =  new C1(""Catch"");
        }
        finally
        {
            await using var x = new C1(""Finally"");
        }
    }
}";
            string expectedOutput = @"
Created Try
Dispose Async Try
Created Catch
Dispose Async Catch
Created Finally
Dispose Async Finally
";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
 
            CompileAndVerify(compilation, expectedOutput: expectedOutput);
        }
 
        [Fact]
        public void UsingDeclarationUsingPatternIntersectionEmitTest()
        {
            var source = @"
    using System;
    ref struct S1
    {
        public void M()
        {
            Console.WriteLine(""This method has run."");
        }
        public void Dispose()
        {
            Console.WriteLine(""This object has been properly disposed."");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            using S1 s1 = new S1();
            s1.M();
        }
    }";
 
            var output = @"This method has run.
This object has been properly disposed.";
            CompileAndVerify(source, expectedOutput: output).VerifyIL("Program.Main", @"
{
  // Code size       26 (0x1a)
  .maxstack  1
  .locals init (S1 V_0) //s1
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S1""
  .try
  {
    IL_0008:  ldloca.s   V_0
    IL_000a:  call       ""void S1.M()""
    IL_000f:  leave.s    IL_0019
  }
  finally
  {
    IL_0011:  ldloca.s   V_0
    IL_0013:  call       ""void S1.Dispose()""
    IL_0018:  endfinally
  }
  IL_0019:  ret
}
");
        }
 
        [Fact]
        public void UsingVariableUsingPatternIntersectionTwoDisposeMethodsEmitTest()
        {
            var source = @"
    using System;
    class C1 : IDisposable
    {
        public void M()
        {
            Console.WriteLine(""This method has run."");
        }
        public void Dispose()
        {
            Console.WriteLine(""This object has been disposed by C1.Dispose()."");
        }
        void IDisposable.Dispose()
        {
            Console.WriteLine(""This object has been disposed by IDisposable.Dispose()."");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            using C1 o1 = new C1();
            o1.M();
        }
    }";
 
            var output = @"This method has run.
This object has been disposed by IDisposable.Dispose().";
            CompileAndVerify(source, expectedOutput: output).VerifyIL("Program.Main", @"
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init (C1 V_0) //o1
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  ldloc.0
    IL_0007:  callvirt   ""void C1.M()""
    IL_000c:  leave.s    IL_0018
  }
  finally
  {
    IL_000e:  ldloc.0
    IL_000f:  brfalse.s  IL_0017
    IL_0011:  ldloc.0
    IL_0012:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0017:  endfinally
  }
  IL_0018:  ret
}");
        }
 
        [Fact]
        public void UsingDeclarationUsingPatternExtensionMethod()
        {
            var source = @"
    using System;
    ref struct S1
    {
    }
    internal static class C2
    {
        internal static void Dispose(this S1 s1)
        {
            Console.Write(""Disposed; "");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            using S1 s1 = new S1();
        }
    }";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (17,13): error CS1674: 'S1': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
                //             using S1 s1 = new S1();
                Diagnostic(ErrorCode.ERR_NoConvToIDisp, "using S1 s1 = new S1();").WithArguments("S1").WithLocation(17, 13)
                );
        }
 
        [Fact]
        public void MultipleUsingVarEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    public void Dispose() { }
}
class C2
{
    public static void Main()                                                                                                           
    {
        using C1 o1 = new C1(), o2 = new C1();
    }
}";
            CompileAndVerify(source).VerifyIL("C2.Main", @"
{
  // Code size       35 (0x23)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  newobj     ""C1..ctor()""
    IL_000b:  stloc.1
    .try
    {
      IL_000c:  leave.s    IL_0022
    }
    finally
    {
      IL_000e:  ldloc.1
      IL_000f:  brfalse.s  IL_0017
      IL_0011:  ldloc.1
      IL_0012:  callvirt   ""void System.IDisposable.Dispose()""
      IL_0017:  endfinally
    }
  }
  finally
  {
    IL_0018:  ldloc.0
    IL_0019:  brfalse.s  IL_0021
    IL_001b:  ldloc.0
    IL_001c:  callvirt   ""void System.IDisposable.Dispose()""
    IL_0021:  endfinally
  }
  IL_0022:  ret
}");
        }
 
        [Fact]
        public void MultipleUsingVarPrecedingCodeEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    private string name;
    public C1(string name)
    {
        this.name = name;
        Console.WriteLine(""Object "" + name + "" has been created."");
    }
    public void M() { } 
    public void Dispose()
    {
        Console.WriteLine(""Object "" + name + "" has been disposed."");
    }
}
class C2
{
    public static void Main()                                                                                                           
    {
        C1 o0 = new C1(""first"");
        o0.M();
        using C1 o1 = new C1(""second""), o2 = new C1(""third"");
    }
}";
            var output = @"Object first has been created.
Object second has been created.
Object third has been created.
Object third has been disposed.
Object second has been disposed.";
            CompileAndVerify(source, expectedOutput: output).VerifyIL("C2.Main", @"
{
  // Code size       60 (0x3c)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  ldstr      ""first""
  IL_0005:  newobj     ""C1..ctor(string)""
  IL_000a:  callvirt   ""void C1.M()""
  IL_000f:  ldstr      ""second""
  IL_0014:  newobj     ""C1..ctor(string)""
  IL_0019:  stloc.0
  .try
  {
    IL_001a:  ldstr      ""third""
    IL_001f:  newobj     ""C1..ctor(string)""
    IL_0024:  stloc.1
    .try
    {
      IL_0025:  leave.s    IL_003b
    }
    finally
    {
      IL_0027:  ldloc.1
      IL_0028:  brfalse.s  IL_0030
      IL_002a:  ldloc.1
      IL_002b:  callvirt   ""void System.IDisposable.Dispose()""
      IL_0030:  endfinally
    }
  }
  finally
  {
    IL_0031:  ldloc.0
    IL_0032:  brfalse.s  IL_003a
    IL_0034:  ldloc.0
    IL_0035:  callvirt   ""void System.IDisposable.Dispose()""
    IL_003a:  endfinally
  }
  IL_003b:  ret
}");
        }
 
        [Fact]
        public void MultipleUsingVarFollowingCodeEmitTest()
        {
            string source = @"
using System;
class C1 : IDisposable
{
    private string name;
    public C1(string name)
    {
        this.name = name;
        Console.WriteLine(""Object "" + name + "" has been created."");
    }
    public void M() { } 
    public void Dispose()
    {
        Console.WriteLine(""Object "" + name + "" has been disposed."");
    }
}
class C2
{
    public static void Main()                                                                                                           
    {
        using C1 o1 = new C1(""first""), o2 = new C1(""second"");
        C1 o0 = new C1(""third"");
        o0.M();
    }
}";
            var output = @"Object first has been created.
Object second has been created.
Object third has been created.
Object second has been disposed.
Object first has been disposed.";
            CompileAndVerify(source, expectedOutput: output).VerifyIL("C2.Main", @"
{
  // Code size       60 (0x3c)
  .maxstack  1
  .locals init (C1 V_0, //o1
                C1 V_1) //o2
  IL_0000:  ldstr      ""first""
  IL_0005:  newobj     ""C1..ctor(string)""
  IL_000a:  stloc.0
  .try
  {
    IL_000b:  ldstr      ""second""
    IL_0010:  newobj     ""C1..ctor(string)""
    IL_0015:  stloc.1
    .try
    {
      IL_0016:  ldstr      ""third""
      IL_001b:  newobj     ""C1..ctor(string)""
      IL_0020:  callvirt   ""void C1.M()""
      IL_0025:  leave.s    IL_003b
    }
    finally
    {
      IL_0027:  ldloc.1
      IL_0028:  brfalse.s  IL_0030
      IL_002a:  ldloc.1
      IL_002b:  callvirt   ""void System.IDisposable.Dispose()""
      IL_0030:  endfinally
    }
  }
  finally
  {
    IL_0031:  ldloc.0
    IL_0032:  brfalse.s  IL_003a
    IL_0034:  ldloc.0
    IL_0035:  callvirt   ""void System.IDisposable.Dispose()""
    IL_003a:  endfinally
  }
  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; ");
        }
 
        [Fact]
        public void UsingDeclarationAsync()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    public ValueTask DisposeAsync() 
    { 
        Console.WriteLine(""Dispose async"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c = new C1();
    }
}";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
 
            CompileAndVerify(compilation, expectedOutput: "Dispose async");
        }
 
        [Fact]
        public void UsingDeclarationAsyncExplicit()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    ValueTask IAsyncDisposable.DisposeAsync() 
    { 
        Console.WriteLine(""Dispose async"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c = new C1();
    }
}";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
 
            CompileAndVerify(compilation, expectedOutput: "Dispose async");
        }
 
        [Fact]
        public void UsingDeclarationAsyncWithMultipleDeclarations()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    string text;
 
    public C1(string text)
    {
        this.text = text;
        Console.WriteLine($""Created {text}"");
    }
 
    public ValueTask DisposeAsync() 
    { 
        Console.WriteLine($""Dispose async {text}"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c = new C1(""first""), c2 = new C1(""second""), c3 = new C1(""third"");
        Console.WriteLine(""After declarations"");
    }
}";
            string expectedOutput = @"
Created first
Created second
Created third
After declarations
Dispose async third
Dispose async second
Dispose async first
";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
            CompileAndVerify(compilation, expectedOutput: expectedOutput);
        }
 
        [Fact]
        public void UsingDeclarationAsyncWithMultipleInARow()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    string text;
 
    public C1(string text)
    {
        this.text = text;
        Console.WriteLine($""Created {text}"");
    }
 
    public ValueTask DisposeAsync() 
    { 
        Console.WriteLine($""Dispose async {text}"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c1 = new C1(""first"");
        await using C1 c2 = new C1(""second"");
        await using C1 c3 = new C1(""third"");
        Console.WriteLine(""After declarations"");
    }
}";
            string expectedOutput = @"
Created first
Created second
Created third
After declarations
Dispose async third
Dispose async second
Dispose async first
";
 
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
            CompileAndVerify(compilation, expectedOutput: expectedOutput);
        }
 
        [Fact]
        public void UsingDeclarationWithNull()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IDisposable
{
    public void Dispose() 
    {
        Console.Write(""Dispose; "");
    }
}
 
 class C2 : IAsyncDisposable
{
    public ValueTask DisposeAsync() 
    { 
        System.Console.WriteLine(""Dispose async"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C3
{
    static async Task Main()
    {
        using C1 c1 = null; 
        await using C2 c2 = null;
        Console.Write(""After declarations; "");
    }
}";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe).VerifyDiagnostics();
 
            CompileAndVerify(compilation, expectedOutput: "After declarations; ");
        }
 
        [Fact]
        public void UsingDeclarationAsyncMissingValueTask()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1 : IAsyncDisposable
{
    public ValueTask DisposeAsync() 
    { 
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c1 = new C1();
    }
}";
 
            var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition });
            comp.MakeTypeMissing(WellKnownType.System_Threading_Tasks_ValueTask);
            comp.VerifyDiagnostics(
                // (16,9): error CS0518: Predefined type 'System.Threading.Tasks.ValueTask' is not defined or imported
                //         await using C1 c1 = new C1();
                Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "await").WithArguments("System.Threading.Tasks.ValueTask").WithLocation(16, 9)
                );
        }
 
        [Fact]
        public void UsingDeclarationAsync_WithOptionalParameter()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1
{
    public ValueTask DisposeAsync(int i = 1) 
    { 
        Console.WriteLine($""Dispose async {i}"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c = new C1();
    }
}";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe);
 
            CompileAndVerify(compilation, expectedOutput: "Dispose async 1");
        }
 
        [Fact]
        public void UsingDeclarationAsync_WithParamsParameter()
        {
            var source = @"
using System;
using System.Threading.Tasks;
class C1
{
    public ValueTask DisposeAsync(params object[] o) 
    { 
        Console.WriteLine($""Dispose async {o.Length}"");
        return new ValueTask(Task.CompletedTask);
    }
}
 
class C2
{
    static async Task Main()
    {
        await using C1 c = new C1();
    }
}";
            var compilation = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe);
 
            CompileAndVerify(compilation, expectedOutput: "Dispose async 0");
        }
    }
}