|
// 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;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests;
// These tests cover the handling of System.Diagnostics.CodeAnalysis.ExperimentalAttribute.
public class ExperimentalAttributeTests : CSharpTestBase
{
private const string experimentalAttributeSrc = """
#nullable enable
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId) { }
public string? UrlFormat { get; set; }
}
}
""";
private const string DefaultHelpLinkUri = "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS9204)";
[Theory, CombinatorialData]
public void Simple(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Fact]
public void OnAssembly_UsedFromSource()
{
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
}
[Fact]
public void OnAssembly_UsedFromMetadata()
{
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
// Note: the assembly-level [Experimental] is equivalent to marking every type and member as experimental,
// whereas a type-level [Experimental] is not equivalent to marking every nested type and member as experimental.
comp.VerifyDiagnostics(
// (1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
foreach (var diag in comp.GetDiagnostics())
{
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
}
[Fact]
public void OnAssembly_DefinedInMetadata_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
}
[Fact]
public void OnAssembly_DefinedInMetadata_UsedFromMetadata()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
foreach (var diag in comp.GetDiagnostics())
{
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
}
[Fact]
public void OnAssembly_DefinedInMetadata_UsedFromMetadata_ObsoleteType()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
[System.Obsolete("error", true)]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
Assert.Equal(ObsoleteAttributeKind.Obsolete, comp.GetTypeByMetadataName("C").ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error CS0619: 'C' is obsolete: 'error'
// C.M();
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "error").WithLocation(1, 1),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Fact]
public void OnAssembly_DefinedInMetadata_AppliedWithinModule_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc1 = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
""";
var moduleComp = CreateCompilation(libSrc1, options: TestOptions.DebugModule, references: new[] { attrRef });
var moduleRef = moduleComp.EmitToImageReference();
var libSrc = """
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef, moduleRef });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
}
[Fact]
public void OnAssembly_DefinedInMetadata_AppliedWithinModule_UsedFromMetadata()
{
// An assembly-level [Experimental] compiled into a module applies to the entire assembly
// the module gets compiled into
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc1 = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
""";
var moduleComp = CreateCompilation(libSrc1, options: TestOptions.DebugModule, references: new[] { attrRef });
var moduleRef = moduleComp.EmitToImageReference();
var libSrc = """
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef, moduleRef }).EmitToImageReference(), attrRef });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
foreach (var diag in comp.GetDiagnostics())
{
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
}
[Fact]
public void OnAssembly_ObsoleteType()
{
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
[System.Obsolete("error", true)]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Obsolete, comp.GetTypeByMetadataName("C").ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error CS0619: 'C' is obsolete: 'error'
// C.M();
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "error").WithLocation(1, 1),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Fact]
public void OnType_ObsoleteType()
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
[System.Obsolete("error", true)]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
Assert.Equal(ObsoleteAttributeKind.Obsolete, comp.GetTypeByMetadataName("C").ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error CS0619: 'C' is obsolete: 'error'
// C.M();
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "error").WithLocation(1, 1)
);
}
[Fact]
public void OnModule()
{
var libSrc = """
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Fact]
public void OnModule_DefinedInMetadata_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
}
[Fact]
public void OnModule_DefinedInMetadata_UsedFromMetadata()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
foreach (var diag in comp.GetDiagnostics())
{
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
}
[Fact]
public void OnModuleAndAssembly_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagAssembly")]
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagModule")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
}
[Fact]
public void OnModuleAndAssembly_UsedFromMetadata()
{
// Prefer reporting the module-level diagnostic
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagAssembly")]
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagModule")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
comp.VerifyDiagnostics(
// (1,1): error DiagModule: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagModule", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagModule: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagModule", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
foreach (var diag in comp.GetDiagnostics())
{
Assert.Equal("DiagModule", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
}
[Fact]
public void OnAssembly_CompiledIntoModule()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("AssemblyDiagSetInModule")]
public class C
{
public static void M() { }
}
""";
var moduleComp = CreateCompilation(libSrc, options: TestOptions.ReleaseModule, references: new[] { attrRef });
moduleComp.VerifyDiagnostics();
var moduleRef = moduleComp.EmitToImageReference();
var libSrc2 = """
public class D
{
public static void M()
{
C.M();
}
}
""";
var assemblyComp = CreateCompilation(libSrc2, references: new[] { moduleRef, attrRef });
assemblyComp.VerifyDiagnostics();
var assemblyRef = assemblyComp.EmitToImageReference();
var src = """
C.M();
D.M();
""";
// Since the module is referenced but not linked, we also need it here, but as
// a result the diagnostics are suppressed
var comp = CreateCompilation(src, references: new[] { assemblyRef, moduleRef, attrRef });
comp.VerifyDiagnostics();
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("D").ContainingModule.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("D").ContainingAssembly.ObsoleteKind);
}
[Fact]
public void OnTypeAndMethodAndAssembly_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("IGNORED")]
[System.Diagnostics.CodeAnalysis.Experimental("DiagType")]
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagMethod")]
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef });
comp.VerifyDiagnostics();
var c = comp.GetTypeByMetadataName("C");
Assert.Equal(ObsoleteAttributeKind.Experimental, c.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, c.ContainingAssembly.ObsoleteKind);
var m = comp.GetMember("C.M");
Assert.Equal(ObsoleteAttributeKind.Experimental, m.ObsoleteKind);
}
[Fact]
public void OnTypeAndMethodAndAssembly_UsedFromMetadata()
{
// Prefer reporting the type-level and method-level diagnostic
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("IGNORED")]
[System.Diagnostics.CodeAnalysis.Experimental("DiagType")]
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagMethod")]
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
comp.VerifyDiagnostics(
// (1,1): error DiagType: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagType", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagMethod: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagMethod", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
var c = comp.GetTypeByMetadataName("C");
Assert.Equal(ObsoleteAttributeKind.Experimental, c.ObsoleteKind);
Assert.Equal(ObsoleteAttributeKind.Experimental, c.ContainingAssembly.ObsoleteKind);
var m = comp.GetMember("C.M");
Assert.Equal(ObsoleteAttributeKind.Experimental, m.ObsoleteKind);
}
[Fact]
public void OnTypeAndAssembly_UsedFromSource()
{
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagAssembly")]
[System.Diagnostics.CodeAnalysis.Experimental("DiagType")]
public class C
{
public class Nested
{
public static void M() { }
}
}
""";
var src = """
C.Nested.M();
""";
var comp = CreateCompilation(new[] { src, libSrc }, references: new[] { attrRef });
comp.VerifyDiagnostics();
}
[Fact]
public void OnTypeAndAssembly_UsedFromMetadata()
{
// Prefer reporting the type-level and method-level diagnostic
var attrComp = CreateCompilation(experimentalAttributeSrc);
var attrRef = attrComp.EmitToImageReference();
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagAssembly")]
[System.Diagnostics.CodeAnalysis.Experimental("DiagType")]
public class C
{
public class Nested
{
public static void M() { }
}
}
""";
var src = """
C.Nested.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { attrRef }).EmitToImageReference(), attrRef });
comp.VerifyDiagnostics(
// (1,1): error DiagType: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.Nested.M();
Diagnostic("DiagType", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagAssembly: 'C.Nested' is for evaluation purposes only and is subject to change or removal in future updates.
// C.Nested.M();
Diagnostic("DiagAssembly", "C.Nested").WithArguments("C.Nested").WithLocation(1, 1).WithWarningAsError(true),
// (1,1): error DiagAssembly: 'C.Nested.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.Nested.M();
Diagnostic("DiagAssembly", "C.Nested.M()").WithArguments("C.Nested.M()").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnOverridden(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("Diag")]
public virtual void M() { }
}
""";
var src = """
public class Derived : C
{
public override void M() { }
public void M2()
{
base.M();
M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(7,9): error Diag: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// base.M();
Diagnostic("Diag", "base.M()").WithArguments("C.M()").WithLocation(7, 9).WithWarningAsError(true),
// 0.cs(8,9): error Diag: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// M();
Diagnostic("Diag", "M()").WithArguments("C.M()").WithLocation(8, 9).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnOverride(bool inSource)
{
var libSrc = """
public class C
{
public virtual void M() { }
}
""";
var src = """
public class Derived : C
{
[System.Diagnostics.CodeAnalysis.Experimental("Diag")]
public override void M() { }
public void M2()
{
base.M();
M();
}
}
public class DerivedDerived : Derived
{
public void M3()
{
base.M(); // 1
M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(17,9): error Diag: 'Derived.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// base.M(); // 1
Diagnostic("Diag", "base.M()").WithArguments("Derived.M()").WithLocation(17, 9).WithWarningAsError(true)
);
}
[Fact]
public void OnOverride_ExperimentalFromAssembly_UsedFromSource()
{
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("Diag")]
public class C
{
public virtual void M() { }
}
""";
var src = """
public class Derived : C { public override void M() { } }
""";
var comp = CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc });
comp.VerifyDiagnostics();
}
[Fact]
public void OnOverride_ExperimentalFromAssembly_UsedFromMetadata()
{
var libSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("Diag")]
public class C
{
public virtual void M() { }
}
""";
var src = """
public class Derived : C { public override void M() { } }
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
// CONSIDER narrowing the location on constructor initializer obsolete/experimental attributes
comp.VerifyDiagnostics(
// (1,1): error Diag: 'C.C()' is for evaluation purposes only and is subject to change or removal in future updates.
// public class Derived : C { public override void M() { } }
Diagnostic("Diag", "public class Derived : C { public override void M() { } }").WithArguments("C.C()").WithLocation(1, 1).WithWarningAsError(true),
// (1,24): error Diag: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// public class Derived : C { public override void M() { } }
Diagnostic("Diag", "C").WithArguments("C").WithLocation(1, 24).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnExplicitMethodImplementation(bool inSource)
{
var libSrc = """
public interface I
{
[System.Diagnostics.CodeAnalysis.Experimental("Diag")]
public void M();
}
""";
var src = """
public class C : I
{
void I.M() { }
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void OnExplicitMethodImplementation_Obsolete(bool inSource)
{
var libSrc = """
public interface I
{
[System.Obsolete("message", true)]
public void M();
}
""";
var src = """
public class C : I
{
void I.M() { }
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Fact]
public void MissingAssemblyAndModule()
{
var missingRef = CreateCompilation("public class Base { }", assemblyName: "missing").EmitToImageReference();
var libSrc = """
public class C : Base
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { CreateCompilation(libSrc, references: new[] { missingRef }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,3): error CS0012: The type 'Base' is defined in an assembly that is not referenced. You must add a reference to assembly 'missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
// C.M();
Diagnostic(ErrorCode.ERR_NoTypeDef, "M").WithArguments("Base", "missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 3)
);
var missingType = comp.GlobalNamespace.GetTypeMember("C").BaseTypeNoUseSiteDiagnostics;
Assert.True(missingType.ContainingAssembly is MissingAssemblySymbol);
Assert.Equal(ObsoleteAttributeKind.None, missingType.ContainingAssembly.ObsoleteKind);
Assert.True(missingType.ContainingModule is MissingModuleSymbol);
Assert.Equal(ObsoleteAttributeKind.None, missingType.ContainingModule.ObsoleteKind);
}
[Fact]
public void RetargetingAssembly_Experimental()
{
var attrRef = CreateCompilation(experimentalAttributeSrc).EmitToImageReference();
var retargetedCode = """
public class C { }
""";
var originalC = CreateCompilation(new AssemblyIdentity("Ret", new Version(1, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var retargetedC = CreateCompilation(new AssemblyIdentity("Ret", new Version(2, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var derivedSrc = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class Derived : C { }
""";
var derivedComp = CreateCompilation(derivedSrc, new[] { originalC.ToMetadataReference(), attrRef }, targetFramework: TargetFramework.Standard);
derivedComp.VerifyDiagnostics();
var comp = CreateCompilation("_ = new Derived();", new[] { derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference() }, targetFramework: TargetFramework.Standard);
comp.VerifyDiagnostics(
// (1,5): error DiagID1: 'Derived.Derived()' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = new Derived();
Diagnostic("DiagID1", "new Derived()").WithArguments("Derived.Derived()").WithLocation(1, 5).WithWarningAsError(true),
// (1,9): error DiagID1: 'Derived' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = new Derived();
Diagnostic("DiagID1", "Derived").WithArguments("Derived").WithLocation(1, 9).WithWarningAsError(true)
);
var derived = comp.GetTypeByMetadataName("Derived");
Assert.IsType<RetargetingNamedTypeSymbol>(derived);
Assert.IsType<RetargetingAssemblySymbol>(derived.ContainingAssembly);
Assert.Equal(ObsoleteAttributeKind.Experimental, derived.ContainingAssembly.ObsoleteKind);
}
[Fact]
public void RetargetingAssembly_NotExperimental()
{
var attrRef = CreateCompilation(experimentalAttributeSrc).EmitToImageReference();
var retargetedCode = """
public class C { }
""";
var originalC = CreateCompilation(new AssemblyIdentity("Ret", new Version(1, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var retargetedC = CreateCompilation(new AssemblyIdentity("Ret", new Version(2, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var derivedSrc = """
public class Derived : C { }
""";
var derivedComp = CreateCompilation(derivedSrc, new[] { originalC.ToMetadataReference(), attrRef }, targetFramework: TargetFramework.Standard);
derivedComp.VerifyDiagnostics();
var comp = CreateCompilation("_ = new Derived();", new[] { derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference() }, targetFramework: TargetFramework.Standard);
comp.VerifyDiagnostics();
var derived = comp.GetTypeByMetadataName("Derived");
Assert.IsType<RetargetingNamedTypeSymbol>(derived);
Assert.IsType<RetargetingAssemblySymbol>(derived.ContainingAssembly);
Assert.Equal(ObsoleteAttributeKind.None, derived.ContainingAssembly.ObsoleteKind);
}
[Fact]
public void RetargetingModule_Experimental()
{
var attrRef = CreateCompilation(experimentalAttributeSrc).EmitToImageReference();
var retargetedCode = """
public class C { }
""";
var originalC = CreateCompilation(new AssemblyIdentity("Ret", new Version(1, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var retargetedC = CreateCompilation(new AssemblyIdentity("Ret", new Version(2, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var @base = """
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class Derived : C { }
""";
var derivedComp = CreateCompilation(@base, new[] { originalC.ToMetadataReference(), attrRef }, targetFramework: TargetFramework.Standard);
derivedComp.VerifyDiagnostics();
var comp = CreateCompilation("_ = new Derived();", new[] { derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference() }, targetFramework: TargetFramework.Standard);
comp.VerifyDiagnostics(
// (1,5): error DiagID1: 'Derived.Derived()' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = new Derived();
Diagnostic("DiagID1", "new Derived()").WithArguments("Derived.Derived()").WithLocation(1, 5).WithWarningAsError(true),
// (1,9): error DiagID1: 'Derived' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = new Derived();
Diagnostic("DiagID1", "Derived").WithArguments("Derived").WithLocation(1, 9).WithWarningAsError(true)
);
var derived = comp.GetTypeByMetadataName("Derived");
Assert.IsType<RetargetingNamedTypeSymbol>(derived);
Assert.IsType<RetargetingModuleSymbol>(derived.ContainingModule);
Assert.Equal(ObsoleteAttributeKind.Experimental, derived.ContainingModule.ObsoleteKind);
}
[Fact]
public void RetargetingModule_NotExperimental()
{
var attrRef = CreateCompilation(experimentalAttributeSrc).EmitToImageReference();
var retargetedCode = """
public class C { }
""";
var originalC = CreateCompilation(new AssemblyIdentity("Ret", new Version(1, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var retargetedC = CreateCompilation(new AssemblyIdentity("Ret", new Version(2, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences);
var @base = """
public class Derived : C { }
""";
var derivedComp = CreateCompilation(@base, new[] { originalC.ToMetadataReference(), attrRef }, targetFramework: TargetFramework.Standard);
derivedComp.VerifyDiagnostics();
var comp = CreateCompilation("_ = new Derived();", new[] { derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference() }, targetFramework: TargetFramework.Standard);
comp.VerifyDiagnostics();
var derived = comp.GetTypeByMetadataName("Derived");
Assert.IsType<RetargetingNamedTypeSymbol>(derived);
Assert.IsType<RetargetingModuleSymbol>(derived.ContainingModule);
Assert.Equal(ObsoleteAttributeKind.None, derived.ContainingModule.ObsoleteKind);
}
[Theory, CombinatorialData]
public void OnStruct(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public struct S
{
public static void M() { }
}
""";
var src = """
S.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'S' is for evaluation purposes only and is subject to change or removal in future updates.
// S.M();
Diagnostic("DiagID1", "S").WithArguments("S").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void OnEnum(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public enum E { }
""";
var src = """
E e = default;
e.ToString();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'E' is for evaluation purposes only and is subject to change or removal in future updates.
// E e = default;
Diagnostic("DiagID1", "E").WithArguments("E").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void OnConstructor(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public C() { }
}
""";
var src = """
_ = new C();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,5): error DiagID1: 'C.C()' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = new C();
Diagnostic("DiagID1", "new C()").WithArguments("C.C()").WithLocation(1, 5).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void OnMethod(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void OnProperty(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static int P => 0;
}
""";
var src = """
_ = C.P;
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,5): error DiagID1: 'C.P' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = C.P;
Diagnostic("DiagID1", "C.P").WithArguments("C.P").WithLocation(1, 5).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void OnField(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static int field = 0;
}
""";
var src = """
_ = C.field;
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,5): error DiagID1: 'C.field' is for evaluation purposes only and is subject to change or removal in future updates.
// _ = C.field;
Diagnostic("DiagID1", "C.field").WithArguments("C.field").WithLocation(1, 5).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnEvent(bool inSource)
{
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static event System.Action Event;
static void M()
{
Event();
}
}
""";
var src = """
C.Event += () => { };
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C.Event' is for evaluation purposes only and is subject to change or removal in future updates.
// C.Event += () => { };
Diagnostic("DiagID1", "C.Event").WithArguments("C.Event").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnInterface(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public interface I
{
void M();
}
""";
var src = """
I i = null;
i.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'I' is for evaluation purposes only and is subject to change or removal in future updates.
// I i = null;
Diagnostic("DiagID1", "I").WithArguments("I").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnDelegate(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public delegate void D();
""";
var src = """
D d = null;
d();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'D' is for evaluation purposes only and is subject to change or removal in future updates.
// D d = null;
Diagnostic("DiagID1", "D").WithArguments("D").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void OnParameter(bool inSource)
{
// Ignored on parameters
var libSrc = """
public class C
{
public static void M([System.Diagnostics.CodeAnalysis.Experimental("DiagID1")] int i) { }
}
""";
var src = """
C.M(42);
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void OnReturnValue(bool inSource)
{
// Ignored on return value
var libSrc = """
public class C
{
[return: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static int M() => 0;
}
""";
var src = """
_ = C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void OnTypeParameter(bool inSource)
{
// Ignored on type parameters
var libSrc = """
public class C<[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")] T> { }
""";
var src = """
C<int> c = null;
c.ToString();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Fact]
public void NullDiagnosticId()
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental(null)]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error CS9204: 'C' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// C.M();
Diagnostic(ErrorCode.WRN_Experimental, "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// 1.cs(1,47): error CS9208: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental(null)]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, "null").WithLocation(1, 47)
);
}
[Fact]
public void MissingDiagnosticIdArgument()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental()]
public class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(2,2): error CS7036: There is no argument given that corresponds to the required parameter 'diagnosticId' of 'ExperimentalAttribute.ExperimentalAttribute(string)'
// [System.Diagnostics.CodeAnalysis.Experimental()]
Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "System.Diagnostics.CodeAnalysis.Experimental()").WithArguments("diagnosticId", "System.Diagnostics.CodeAnalysis.ExperimentalAttribute.ExperimentalAttribute(string)").WithLocation(2, 2)
);
}
[Fact]
public void IntegerDiagnosticIdArgument()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental(42)]
public class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(3,47): error CS1503: Argument 1: cannot convert from 'int' to 'string'
// [System.Diagnostics.CodeAnalysis.Experimental(42)]
Diagnostic(ErrorCode.ERR_BadArgType, "42").WithArguments("1", "int", "string").WithLocation(3, 47)
);
}
[Fact]
public void MultipleArguments()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("DiagID", "other")]
public class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(3,2): error CS1729: 'ExperimentalAttribute' does not contain a constructor that takes 2 arguments
// [System.Diagnostics.CodeAnalysis.Experimental("DiagID", "other")]
Diagnostic(ErrorCode.ERR_BadCtorArgCount, @"System.Diagnostics.CodeAnalysis.Experimental(""DiagID"", ""other"")").WithArguments("System.Diagnostics.CodeAnalysis.ExperimentalAttribute", "2").WithLocation(3, 2)
);
}
[Fact]
public void DiagnosticIdWithTrailingNewline()
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("Diag\n")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error Diag\n: 'C' is for evaluation purposes only and is subject to change or removal in future updates.Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag\n", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// 1.cs(1,47): error CS9208: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental("Diag\n")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, @"""Diag\n""").WithLocation(1, 47)
);
}
[Fact]
public void DiagnosticIdWithTrailingNewline_WithSuppression()
{
var src = """
#pragma warning disable CS9204
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("Diag\n")]
public class C
{
public static void M() { }
}
""";
Assert.Equal((ErrorCode)9204, ErrorCode.WRN_Experimental);
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(2,1): error Diag\n: 'C' is for evaluation purposes only and is subject to change or removal in future updates.Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag\n", "C").WithArguments("C").WithLocation(2, 1).WithWarningAsError(true),
// 0.cs(4,47): error CS9211: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental("Diag\n")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, @"""Diag\n""").WithLocation(4, 47)
);
}
[Fact]
public void DiagnosticIdWithNewline()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("Diag\n01")]
public class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error Diag\n01: 'C' is for evaluation purposes only and is subject to change or removal in future updates.Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag\n01", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// 0.cs(3,47): error CS9208: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental("Diag\n01")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, @"""Diag\n01""").WithLocation(3, 47)
);
}
[Theory, CombinatorialData]
public void WhitespaceDiagnosticId([CombinatorialValues("\"\"", "\" \"", "\"\\n\"")] string whitespace)
{
var libSrc = $$"""
[System.Diagnostics.CodeAnalysis.Experimental({{whitespace}})]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = CreateCompilation(new CSharpTestSource[] { (src, "0.cs"), libSrc, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error CS9204: 'C' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// C.M();
Diagnostic(ErrorCode.WRN_Experimental, "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// (1,47): error CS9208: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental(" ")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, whitespace).WithLocation(1, 47)
);
}
[Fact]
public void WhitespaceDiagnosticId_WithSuppression()
{
var src = """
#pragma warning disable CS9204
C.M();
[System.Diagnostics.CodeAnalysis.Experimental(" ")]
public class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new CSharpTestSource[] { (src, "0.cs"), experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(4,47): error CS9211: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental(" ")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, @""" """).WithLocation(4, 47)
);
}
[Fact]
public void SpacedDiagnosticId()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("Diag 01")]
class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error Diag 01: 'C' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag 01", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true),
// 0.cs(3,47): error CS9208: The diagnosticId argument to the 'Experimental' attribute must be a valid identifier
// [System.Diagnostics.CodeAnalysis.Experimental("Diag 01")]
Diagnostic(ErrorCode.ERR_InvalidExperimentalDiagID, @"""Diag 01""").WithLocation(3, 47)
);
}
[Fact]
public void SpacedDiagnosticId_WithSecondArgument()
{
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("Diag 01", "other")]
class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(3,2): error CS1729: 'ExperimentalAttribute' does not contain a constructor that takes 2 arguments
// [System.Diagnostics.CodeAnalysis.Experimental("Diag 01", "other")]
Diagnostic(ErrorCode.ERR_BadCtorArgCount, @"System.Diagnostics.CodeAnalysis.Experimental(""Diag 01"", ""other"")").WithArguments("System.Diagnostics.CodeAnalysis.ExperimentalAttribute", "2").WithLocation(3, 2)
);
}
[Fact]
public void SpacedDiagnosticId_Metadata()
{
var il = """
.class public auto ansi beforefieldinit C
extends [mscorlib]System.Object
{
.custom instance void System.Diagnostics.CodeAnalysis.ExperimentalAttribute::.ctor(string) = { string('Diag 01') }
.method public hidebysig static void M () cil managed
{
IL_0000: ret
}
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
}
}
.class public auto ansi sealed beforefieldinit System.Diagnostics.CodeAnalysis.ExperimentalAttribute
extends [mscorlib]System.Attribute
{
.method public hidebysig specialname rtspecialname instance void .ctor ( string diagnosticId ) cil managed
{
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Attribute::.ctor()
IL_0006: ret
}
}
""";
var comp = CreateCompilationWithIL("C.M();", il);
comp.VerifyDiagnostics(
// (1,1): error Diag 01: 'C' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag 01", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var src = """
// Cannot be suppressed
#pragma disable warning Diag 01
C.M();
""";
comp = CreateCompilationWithIL(src, il);
comp.VerifyDiagnostics(
// (2,9): warning CS1633: Unrecognized #pragma directive
// #pragma disable warning Diag 01
Diagnostic(ErrorCode.WRN_IllegalPragma, "disable").WithLocation(2, 9),
// (3,1): error Diag 01: 'C' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// C.M();
Diagnostic("Diag 01", "C").WithArguments("C").WithLocation(3, 1).WithWarningAsError(true)
);
}
[Fact]
public void BadAttribute_IntParameter()
{
// In source, if the attribute is improperly declared, but with the right number of parameters, we still recognize it
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental(42)]
class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(int diagnosticId)
{
}
}
}
""";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (1,1): error CS9204: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic(ErrorCode.WRN_Experimental, "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Fact]
public void BadAttribute_IntParameter_Metadata()
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental(42)]
public class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(int diagnosticId)
{
}
}
}
""";
var libComp = CreateCompilation(libSrc);
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { libComp.EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Fact]
public void BadAttribute_TwoStringParameters()
{
// If the attribute is improperly declared, with a wrong number of parameters, we ignore it
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("ignored", "ignored")]
class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId, string urlFormat)
{
}
}
}
""";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics();
}
[Fact]
public void BadAttribute_IntUrlFormatProperty_Metadata()
{
// A "UrlFormat" property with a type other than 'string' is ignored
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID", UrlFormat = 42)]
public class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId) { }
public int UrlFormat { get; set; }
}
}
""";
var libComp = CreateCompilation(libSrc);
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { libComp.EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error DiagID: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Fact]
public void BadAttribute_UrlFormatField_Metadata()
{
// A field named "UrlFormat" is ignored
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID", UrlFormat = "hello")]
public class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId) { }
public string UrlFormat = "hello";
}
}
""";
var libComp = CreateCompilation(libSrc);
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { libComp.EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error DiagID: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Fact]
public void BadAttribute_OtherProperty_Metadata()
{
// A property that isn't named "UrlFormat" is ignored
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID", NotUrlFormat = "hello")]
public class C
{
public static void M() { }
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId) { }
public string NotUrlFormat { get; set; }
}
}
""";
var libComp = CreateCompilation(libSrc);
var src = """
C.M();
""";
var comp = CreateCompilation(src, references: new[] { libComp.EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error DiagID: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Fact]
public void UrlFormat()
{
// Combine the DiagnosticId with the UrlFormat if present
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1", UrlFormat = "https://example.org/{0}")]
class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates. (https://example.org/DiagID1)
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal("https://example.org/DiagID1", diag.Descriptor.HelpLinkUri);
}
[Fact]
public void BadUrlFormat()
{
// We use a default help URL if the UrlFormat is improper
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1", UrlFormat = "https://example.org/{0}{1}")]
class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Theory, CombinatorialData]
public void EmptyUrlFormat(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1", UrlFormat = "")]
public class C
{
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal("", diag.Descriptor.HelpLinkUri);
}
[Fact]
public void NullUrlFormat()
{
// We use a default help URL if the UrlFormat is improper
var src = """
C.M();
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1", UrlFormat = null)]
class C
{
public static void M() { }
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C").WithArguments("C").WithLocation(1, 1).WithWarningAsError(true)
);
var diag = comp.GetDiagnostics().Single();
Assert.Equal("DiagID1", diag.Id);
Assert.Equal(ErrorCode.WRN_Experimental, (ErrorCode)diag.Code);
Assert.Equal(DefaultHelpLinkUri, diag.Descriptor.HelpLinkUri);
}
[Fact]
public void FullyQualified()
{
var src = """
N.C.M();
namespace N
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
class C
{
public static void M() { }
}
}
""";
var comp = CreateCompilation(new[] { src, experimentalAttributeSrc });
comp.VerifyDiagnostics(
// 0.cs(1,1): error DiagID1: 'N.C' is for evaluation purposes only and is subject to change or removal in future updates.
// N.C.M();
Diagnostic("DiagID1", "N.C").WithArguments("N.C").WithLocation(1, 1).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void Suppressed(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
public static void M() { }
}
""";
var src = """
#pragma warning disable DiagID1
C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void InObsoleteMethod(bool inSource)
{
// Diagnostics for [Experimental] are not suppressed in [Obsolete] members
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
class D
{
[System.Obsolete("obsolete", true)]
void M2()
{
C.M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// 0.cs(6,9): error DiagID1: 'C.M()' is for evaluation purposes only and is subject to change or removal in future updates.
// C.M();
Diagnostic("DiagID1", "C.M()").WithArguments("C.M()").WithLocation(6, 9).WithWarningAsError(true)
);
}
[Theory, CombinatorialData]
public void InExperimentalMethod(bool inSource)
{
// Diagnostics for [Experimental] are suppressed in [Experimental] context
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
class D
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID2")]
void M2()
{
C.M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void InExperimentalType(bool inSource)
{
// Diagnostics for [Experimental] are suppressed in [Experimental] context
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID2")]
class D
{
void M2()
{
C.M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void InExperimentalNestedType(bool inSource)
{
// Diagnostics for [Experimental] are suppressed in [Experimental] context
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID2")]
class D
{
class Nested
{
void M2()
{
C.M();
}
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void InExperimentalModule(bool inSource)
{
// Diagnostics for [Experimental] are suppressed in [Experimental] context
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID2")]
class D
{
void M2()
{
C.M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void InExperimentalAssembly(bool inSource)
{
// Diagnostics for [Experimental] are suppressed in [Experimental] context
var libSrc = """
public class C
{
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public static void M() { }
}
""";
var src = """
[assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID2")]
class D
{
void M2()
{
C.M();
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics();
}
[Theory, CombinatorialData]
public void WithObsolete(bool inSource)
{
var libSrc = """
[System.Obsolete("error", true)]
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
public class C
{
}
""";
var src = """
class D
{
void M(C c)
{
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (3,12): error CS0619: 'C' is obsolete: 'error'
// void M(C c)
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "error").WithLocation(3, 12)
);
}
[Theory, CombinatorialData]
public void WithObsolete_ReverseOrder(bool inSource)
{
var libSrc = """
[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]
[System.Obsolete("error", true)]
public class C
{
}
""";
var src = """
class D
{
void M(C c)
{
}
}
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (3,12): error CS0619: 'C' is obsolete: 'error'
// void M(C c)
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "error").WithLocation(3, 12)
);
}
public enum ObsoleteKind
{
Deprecated,
Obsolete,
WindowsExperimental,
Experimental
}
[Theory, CombinatorialData]
public void PriorityOrder(ObsoleteKind one, ObsoleteKind other, bool inSource)
{
if (one == other) return;
var oneAttr = getAttribute(one);
var otherAttr = getAttribute(other);
var libSrc = $$"""
{{oneAttr}}
{{otherAttr}}
public class C { }
""";
var src = """
class D
{
void M(C c) { }
}
""";
var libsSrc = new CSharpTestSource[] { libSrc, experimentalAttributeSrc,
AttributeTests_WindowsExperimental.DeprecatedAttributeSource,
AttributeTests_WindowsExperimental.ExperimentalAttributeSource };
var comp = inSource
? CreateCompilation(libsSrc.Append(src).ToArray())
: CreateCompilation(src, references: new[] { CreateCompilation(libsSrc).EmitToImageReference() });
comp.VerifyDiagnostics(getExpectedDiagnostic(getImportantObsoleteKind(one, other)));
return;
static ObsoleteKind getImportantObsoleteKind(ObsoleteKind one, ObsoleteKind other)
{
return one < other ? one : other;
}
static DiagnosticDescription getExpectedDiagnostic(ObsoleteKind kind)
{
return kind switch
{
ObsoleteKind.Deprecated =>
// (3,12): warning CS0618: 'C' is obsolete: 'DEPRECATED'
// void M(C c) { }
Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "C").WithArguments("C", "DEPRECATED").WithLocation(3, 12),
ObsoleteKind.Obsolete =>
// (3,12): error CS0619: 'C' is obsolete: 'OBSOLETE'
// void M(C c) { }
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C").WithArguments("C", "OBSOLETE").WithLocation(3, 12),
ObsoleteKind.WindowsExperimental =>
// (3,12): warning CS8305: 'C' is for evaluation purposes only and is subject to change or removal in future updates.
// void M(C c) { }
Diagnostic(ErrorCode.WRN_WindowsExperimental, "C").WithArguments("C").WithLocation(3, 12),
// ObsoleteKind.Experimental comes last and always loses
_ => throw new NotSupportedException()
};
}
static string getAttribute(ObsoleteKind kind) => kind switch
{
ObsoleteKind.Deprecated => """[Windows.Foundation.Metadata.Deprecated("DEPRECATED", Windows.Foundation.Metadata.DeprecationType.Deprecate, 0)]""",
ObsoleteKind.Obsolete => """[System.Obsolete("OBSOLETE", true)]""",
ObsoleteKind.WindowsExperimental => "[Windows.Foundation.Metadata.Experimental]",
ObsoleteKind.Experimental => """[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]""",
_ => throw new NotSupportedException()
};
}
[Theory, CombinatorialData]
public void PriorityOrder_OnEvent(bool inSource, bool reversed)
{
var oneAttr = """[System.Obsolete("OBSOLETE", true)]""";
var otherAttr = """[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]""";
var libSrc = $$"""
public class C
{
{{(reversed ? otherAttr : oneAttr)}}
{{(reversed ? oneAttr : otherAttr)}}
public static event System.Action Event;
static void M()
{
Event();
}
}
""";
var src = """
C.Event += () => { };
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error CS0619: 'C.Event' is obsolete: 'OBSOLETE'
// C.Event += () => { };
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C.Event").WithArguments("C.Event", "OBSOLETE").WithLocation(1, 1)
);
}
[Theory, CombinatorialData]
public void PriorityOrder_OnField(bool inSource, bool reversed)
{
var oneAttr = """[System.Obsolete("OBSOLETE", true)]""";
var otherAttr = """[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]""";
var libSrc = $$"""
public class C
{
{{(reversed ? otherAttr : oneAttr)}}
{{(reversed ? oneAttr : otherAttr)}}
public static int field = 0;
}
""";
var src = """
_ = C.field;
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,5): error CS0619: 'C.field' is obsolete: 'OBSOLETE'
// _ = C.field;
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C.field").WithArguments("C.field", "OBSOLETE").WithLocation(1, 5)
);
}
[Theory, CombinatorialData]
public void PriorityOrder_OnMethod(bool inSource, bool reversed)
{
var oneAttr = """[System.Obsolete("OBSOLETE", true)]""";
var otherAttr = """[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]""";
var libSrc = $$"""
public class C
{
{{(reversed ? otherAttr : oneAttr)}}
{{(reversed ? oneAttr : otherAttr)}}
public static void M() { }
}
""";
var src = """
C.M();
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,1): error CS0619: 'C.M()' is obsolete: 'OBSOLETE'
// C.M();
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C.M()").WithArguments("C.M()", "OBSOLETE").WithLocation(1, 1)
);
}
[Theory, CombinatorialData]
public void PriorityOrder_OnProperty(bool inSource, bool reversed)
{
var oneAttr = """[System.Obsolete("OBSOLETE", true)]""";
var otherAttr = """[System.Diagnostics.CodeAnalysis.Experimental("DiagID1")]""";
var libSrc = $$"""
public class C
{
{{(reversed ? otherAttr : oneAttr)}}
{{(reversed ? oneAttr : otherAttr)}}
public static int P => 0;
}
""";
var src = """
_ = C.P;
""";
var comp = inSource
? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc })
: CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() });
comp.VerifyDiagnostics(
// (1,5): error CS0619: 'C.P' is obsolete: 'OBSOLETE'
// _ = C.P;
Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "C.P").WithArguments("C.P", "OBSOLETE").WithLocation(1, 5)
);
}
}
|