File: Symbols\DestructorTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Symbol\Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Symbol.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 System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols
{
    public class DestructorTests : CSharpTestBase
    {
        [Fact]
        public void Error_Parameters()
        {
            var source = @"
class C
{
    ~C(int x) {}
}";
            // This is a parse error.
            var comp = CreateCompilation(source);
            Assert.NotEmpty(comp.GetParseDiagnostics());
        }
 
        [Fact]
        public void Error_ReturnValue()
        {
            var source = @"
class C
{
    ~C() { return 1; }
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (4,12): error CS0127: Since 'C.~C()' returns void, a return keyword must not be followed by an object expression
                Diagnostic(ErrorCode.ERR_RetNoObjectRequired, "return").WithArguments("C.~C()"));
        }
 
        [Fact]
        public void Error_Duplicate()
        {
            var source = @"
class Q
{
    class C
    {
        ~C() { }
        ~C() { }
    }
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (7,10): error CS0111: Type 'Q.C' already defines a member called '~C' with the same parameter types
                Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "C").WithArguments("~C", "Q.C"));
        }
 
        [Fact]
        public void Error_InsideStructOrInterface()
        {
            var source = @"
struct S
{
    ~S() { }
}
 
interface I
{
    ~I();
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (4,6): error CS0575: Only class types can contain destructors
                //     ~S() { }
                Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "S").WithLocation(4, 6),
                // (9,6): error CS0501: 'I.~I()' must declare a body because it is not marked abstract, extern, or partial
                //     ~I();
                Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "I").WithArguments("I.~I()").WithLocation(9, 6),
                // (9,6): error CS0575: Only class types can contain destructors
                //     ~I();
                Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "I").WithLocation(9, 6));
        }
 
        [Fact]
        public void Error_Modifiers()
        {
            var source = @"
class C1
{
    public ~C1() { }
}
 
class C2
{
    virtual ~C2() { }
}
 
class C3
{
    override ~C3() { }
}
 
abstract class C4
{
    abstract ~C4();
}
 
class C5
{
    new ~C5() { }
}
 
class C6
{
    static ~C6() { }
}
 
class C7
{
    extern ~C7();
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (4,13): error CS0106: The modifier 'public' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C1").WithArguments("public"),
                // (9,14): error CS0106: The modifier 'virtual' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C2").WithArguments("virtual"),
                // (14,15): error CS0106: The modifier 'override' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C3").WithArguments("override"),
                // (19,15): error CS0106: The modifier 'abstract' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C4").WithArguments("abstract"),
                // (24,10): error CS0106: The modifier 'new' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C5").WithArguments("new"),
                // (29,13): error CS0106: The modifier 'static' is not valid for this item
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "C6").WithArguments("static"),
                // (34,13): warning CS0626: Method, operator, or accessor 'C7.~C7()' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
                Diagnostic(ErrorCode.WRN_ExternMethodNoImplementation, "C7").WithArguments("C7.~C7()"));
        }
 
        [WorkItem(528912, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528912")]
        [Fact]
        public void InvokeFinalize()
        {
            var source = @"
using System;
 
class A
{
    ~A()
    {
        Finalize(); //CS0245
        Action a = Finalize;
    }
}
 
class B : A
{
    void Goo()
    {
        this.Finalize(); //CS0245
    }
}
 
class C
{
    public void Finalize()
    {
        Finalize();
        Action a = Finalize;
    }
}
 
class D : C
{
    void Goo()
    {
        Finalize();
        Action a = Finalize;
    }
}
 
class E
{
    protected virtual void Finalize() { }
}
 
class F : E
{
    ~F()
    {
        Finalize(); //CS0245 in Roslyn 
        Action a = Finalize;
    }
}
 
class G : F
{
    void Goo()
    {
        Finalize(); //CS0245 in Roslyn 
        Action a = Finalize;
    }
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (23,17): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (41,28): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (8,9): error CS0245: Destructors and object.Finalize cannot be called directly. Consider calling IDisposable.Dispose if available.
                Diagnostic(ErrorCode.ERR_CallingFinalizeDeprecated, "Finalize()"),
                // (17,9): error CS0245: Destructors and object.Finalize cannot be called directly. Consider calling IDisposable.Dispose if available.
                Diagnostic(ErrorCode.ERR_CallingFinalizeDeprecated, "this.Finalize()"),
 
                // These occur in Roslyn, but not dev11, because Roslyn makes F.Finalize a runtime finalizer.
 
                // (48,9): error CS0245: Destructors and object.Finalize cannot be called directly. Consider calling IDisposable.Dispose if available.
                Diagnostic(ErrorCode.ERR_CallingFinalizeDeprecated, "Finalize()"),
                // (57,9): error CS0245: Destructors and object.Finalize cannot be called directly. Consider calling IDisposable.Dispose if available.
                Diagnostic(ErrorCode.ERR_CallingFinalizeDeprecated, "Finalize()"));
        }
 
        [WorkItem(528912, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528912")]
        [Fact]
        public void InvokeBaseFinalize()
        {
            var source = @"
using System;
 
class A
{
    ~A()
    {
        base.Finalize(); //CS0250
        Action a = base.Finalize;
    }
}
 
class B : A
{
    void Goo()
    {
        base.Finalize(); //CS0250
    }
}
 
class C
{
    public void Finalize()
    {
        base.Finalize(); //CS0250
        Action a = base.Finalize;
    }
}
 
class D : C
{
    void Goo()
    {
        base.Finalize();
        Action a = base.Finalize;
    }
}
 
class E
{
    protected virtual void Finalize() { }
}
 
class F : E
{
    ~F()
    {
        base.Finalize();
        Action a = base.Finalize;
    }
}
 
class G : F
{
    void Goo()
    {
        base.Finalize();
        Action a = base.Finalize;
    }
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (23,17): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (41,28): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (8,9): error CS0250: Do not directly call your base type Finalize method. It is called automatically from your destructor.
                Diagnostic(ErrorCode.ERR_CallingBaseFinalizeDeprecated, "base.Finalize()"),
                // (17,9): error CS0250: Do not directly call your base type Finalize method. It is called automatically from your destructor.
                Diagnostic(ErrorCode.ERR_CallingBaseFinalizeDeprecated, "base.Finalize()"),
                // (25,9): error CS0250: Do not directly call your base type Finalize method. It is called automatically from your destructor.
                Diagnostic(ErrorCode.ERR_CallingBaseFinalizeDeprecated, "base.Finalize()"),
 
                // This is new in Roslyn.  It is reported because F.Finalize is now a runtime finalizer.
 
                // (57,9): error CS0250: Do not directly call your base type Finalize method. It is called automatically from your destructor.
                Diagnostic(ErrorCode.ERR_CallingBaseFinalizeDeprecated, "base.Finalize()"));
        }
 
        [Fact]
        public void OverrideFinalize()
        {
            // These are cases where overriding Finalize was okay in dev11.  Error cases are in SymbolErrorTests.
            var source = @"
class A
{
    protected virtual void Finalize() { }
}
 
class B : A
{
    protected override void Finalize() { }
}
 
class C : A
{
    ~C() { }
}
 
class D : C
{
    protected override void Finalize() { }
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (4,28): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (9,29): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (19,29): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
 
                // This is new in Roslyn.  It is reported because C.Finalize is now a runtime finalizer.
 
                // (19,29): error CS0249: Do not override object.Finalize. Instead, provide a destructor.
                Diagnostic(ErrorCode.ERR_OverrideFinalizeDeprecated, "Finalize"));
        }
 
        [Fact]
        public void ImplementFinalize()
        {
            // Note: this would work if the accessibility was public
            var source = @"
interface I
{
    void Finalize();
}
 
class C : I
{
    ~C() { }
}
";
 
            CreateCompilation(source).VerifyDiagnostics(
                // (4,10): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                //     void Finalize();
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize").WithLocation(4, 10),
                // (7,11): error CS0737: 'C' does not implement interface member 'I.Finalize()'. 'C.~C()' cannot implement an interface member because it is not public.
                // class C : I
                Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberNotPublic, "I").WithArguments("C", "I.Finalize()", "C.~C()").WithLocation(7, 11));
        }
 
        [WorkItem(528912, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528912")]
        [Fact]
        public void DestructorDelegate()
        {
            var source = @"
class C
{
    static void Main()
    {
        C c = new C();
        System.Action a = c.Finalize;
        a();
    }
}
";
            CreateCompilation(source).VerifyDiagnostics();
        }
 
        [Fact]
        public void SemanticModel()
        {
            var source = @"
class C
{
    ~C() { this.Finalize(); } //NOTE: not legal to call Finalize, but we still want semantic info
}
";
 
            var compilation = (Compilation)CreateCompilation(source);
 
            var destructor = compilation.GlobalNamespace.GetMember<INamedTypeSymbol>("C").GetMember<IMethodSymbol>(WellKnownMemberNames.DestructorName);
            Assert.Equal(MethodKind.Destructor, destructor.MethodKind);
            Assert.Equal(WellKnownMemberNames.DestructorName, destructor.Name);
 
            var tree = compilation.SyntaxTrees.Single();
            var model = compilation.GetSemanticModel(tree);
 
            var destructorDecl = tree.GetCompilationUnitRoot().DescendantNodes().OfType<DestructorDeclarationSyntax>().Single();
 
            var declaredSymbol = model.GetDeclaredSymbol(destructorDecl);
            Assert.NotNull(declaredSymbol);
            Assert.Equal(destructor, declaredSymbol);
 
            var finalizeSyntax = tree.GetCompilationUnitRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().Single().Name;
            Assert.Equal(WellKnownMemberNames.DestructorName, finalizeSyntax.ToString());
 
            var info = model.GetSymbolInfo(finalizeSyntax);
            Assert.NotEqual(default, info);
            Assert.Equal(destructor, info.Symbol);
 
            var lookupSymbols = model.LookupSymbols(finalizeSyntax.SpanStart, name: WellKnownMemberNames.DestructorName);
            Assert.Equal(2, lookupSymbols.Length); // Also includes object.Finalize
            Assert.Contains(destructor, lookupSymbols);
        }
 
        [Fact]
        public void KeywordIdentifier()
        {
            var source = @"
class @ref
{
    ~@ref() { }
}";
            CreateCompilation(source).VerifyDiagnostics();
        }
 
        [WorkItem(546830, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546830")]
        [Fact]
        public void OverrideSealedFinalizer()
        {
            var il = @"
.class public auto ansi Base
       extends [mscorlib]System.Object
{
  .method public specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
 
  .method family strict virtual final instance void 
          Finalize() cil managed
  {
    ret
  }
}
";
 
            var source = @"
class Derived : Base
{
    ~Derived() { }
}";
            CreateCompilationWithILAndMscorlib40(source, il).VerifyDiagnostics(
                // (4,6): error CS0239: 'Derived.~Derived()': cannot override inherited member 'Base.~Base()' because it is sealed
                //     ~Derived() { }
                Diagnostic(ErrorCode.ERR_CantOverrideSealed, "Derived").WithArguments("Derived.~Derived()", "Base.~Base()"));
        }
 
        [WorkItem(546830, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546830")]
        [Fact]
        public void OverrideNewslotVirtualFinalFinalizer()
        {
            var il = @"
.class public auto ansi Base
       extends [mscorlib]System.Object
{
  .method public specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
 
  .method family strict virtual newslot final instance void 
          Finalize() cil managed
  {
    ret
  }
}
";
 
            var source = @"
class Derived : Base
{
    ~Derived() { }
}";
 
            // BREAK: Dev11 doesn't report this error, but it does generate code that won't run,
            // so this change is reasonable.
            CreateCompilationWithILAndMscorlib40(source, il).VerifyDiagnostics(
                // (4,6): error CS0239: 'Derived.~Derived()': cannot override inherited member 'Base.Finalize()' because it is sealed
                //     ~Derived() { }
                Diagnostic(ErrorCode.ERR_CantOverrideSealed, "Derived").WithArguments("Derived.~Derived()", "Base.Finalize()"));
        }
 
        [WorkItem(528903, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528903")]
        [Fact]
        public void AbstractFinalize()
        {
            var vb = @"
Public MustInherit Class Base
    Protected MustOverride Overrides Sub Finalize()
End Class
";
 
            var source = @"
class Derived : Base
{
    ~Derived() { }
}";
 
            var vbRef = CreateVisualBasicCompilation("VB", vb).EmitToImageReference();
 
            // In dev11, compilation succeeded, but the finalizer would fail at runtime when it made
            // a non-virtual call to the abstract method Base.Finalize.
            CreateCompilation(source, new[] { vbRef }).VerifyDiagnostics(
                // (2,7): error CS0534: 'Derived' does not implement inherited abstract member 'Base.~Base()'
                // class Derived : Base
                Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "Derived").WithArguments("Derived", "Base.~Base()"));
        }
 
        [WorkItem(647933, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/647933")]
        [Fact]
        public void ConditionalAttributeOnDestructor()
        {
            var source = @"
using System.Diagnostics;
public class Test 
{
    [Conditional(""Debug"")]
    ~Test(){}
    public static int Main()
    {
        return 1;
    }
}
";
 
            CreateCompilation(source).VerifyDiagnostics(
                // (5,6): error CS0577: The Conditional attribute is not valid on 'Test.~Test()' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation
                //     [Conditional("Debug")]
                Diagnostic(ErrorCode.ERR_ConditionalOnSpecialMethod, @"Conditional(""Debug"")").WithArguments("Test.~Test()"));
        }
    }
}