diff --git a/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs index df553606ed11..f44b86d0b9f2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs @@ -276,15 +276,6 @@ public sealed override bool IsExtern } } - /// - /// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. - /// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. - /// - internal sealed override ObsoleteAttributeData ObsoleteAttributeData - { - get { return null; } - } - public override ImmutableArray DeclaringSyntaxReferences { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEAssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEAssemblySymbol.cs index ba4f5fa114f7..bf58db1cbede 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEAssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEAssemblySymbol.cs @@ -66,6 +66,8 @@ internal sealed class PEAssemblySymbol : MetadataOrSourceAssemblySymbol #nullable enable private DiagnosticInfo? _lazyCachedCompilerFeatureRequiredDiagnosticInfo = CSDiagnosticInfo.EmptyErrorInfo; + + private ObsoleteAttributeData? _lazyObsoleteAttributeData = ObsoleteAttributeData.Uninitialized; #nullable disable internal PEAssemblySymbol(PEAssembly assembly, DocumentationProvider documentationProvider, bool isLinked, MetadataImportOptions importOptions) @@ -311,5 +313,31 @@ internal PEModuleSymbol PrimaryModule public override bool HasUnsupportedMetadata => GetCompilerFeatureRequiredDiagnostic()?.Code == (int)ErrorCode.ERR_UnsupportedCompilerFeature || base.HasUnsupportedMetadata; + + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + { + get + { + if (_lazyObsoleteAttributeData == ObsoleteAttributeData.Uninitialized) + { + Interlocked.CompareExchange(ref _lazyObsoleteAttributeData, computeObsoleteAttributeData(), ObsoleteAttributeData.Uninitialized); + } + + return _lazyObsoleteAttributeData; + + ObsoleteAttributeData? computeObsoleteAttributeData() + { + foreach (var attrData in GetAttributes()) + { + if (attrData.IsTargetAttribute(this, AttributeDescription.ExperimentalAttribute)) + { + return attrData.DecodeExperimentalAttribute(); + } + } + + return null; + } + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs index ce1cd6f4718e..472aa557609d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs @@ -118,6 +118,8 @@ internal enum RefSafetyRulesAttributeVersion #nullable enable private DiagnosticInfo? _lazyCachedCompilerFeatureRequiredDiagnosticInfo = CSDiagnosticInfo.EmptyErrorInfo; + + private ObsoleteAttributeData? _lazyObsoleteAttributeData = ObsoleteAttributeData.Uninitialized; #nullable disable internal PEModuleSymbol(PEAssemblySymbol assemblySymbol, PEModule module, MetadataImportOptions importOptions, int ordinal) @@ -857,5 +859,31 @@ RefSafetyRulesAttributeVersion getAttributeVersion() } } } + + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + { + get + { + if (_lazyObsoleteAttributeData == ObsoleteAttributeData.Uninitialized) + { + Interlocked.CompareExchange(ref _lazyObsoleteAttributeData, computeObsoleteAttributeData(), ObsoleteAttributeData.Uninitialized); + } + + return _lazyObsoleteAttributeData; + + ObsoleteAttributeData? computeObsoleteAttributeData() + { + foreach (var attrData in GetAttributes()) + { + if (attrData.IsTargetAttribute(this, AttributeDescription.ExperimentalAttribute)) + { + return attrData.DecodeExperimentalAttribute(); + } + } + + return null; + } + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/MissingAssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MissingAssemblySymbol.cs index 94c8e988007f..cb2d98a6fe29 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MissingAssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MissingAssemblySymbol.cs @@ -209,5 +209,8 @@ internal sealed override IEnumerable GetAllTopLevelForwardedTyp { return SpecializedCollections.EmptyEnumerable(); } + +#nullable enable + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData => null; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/MissingModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MissingModuleSymbol.cs index 48277b000f8c..4154d729b902 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MissingModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MissingModuleSymbol.cs @@ -198,6 +198,10 @@ public sealed override bool AreLocalsZeroed } internal sealed override bool UseUpdatedEscapeRules => false; + +#nullable enable + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData => null; +#nullable disable } internal sealed class MissingModuleSymbolWithName : MissingModuleSymbol diff --git a/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs index 7c9546dfd9c5..b8d9cb2016cd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ModuleSymbol.cs @@ -185,15 +185,6 @@ public sealed override bool IsExtern } } - /// - /// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. - /// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. - /// - internal sealed override ObsoleteAttributeData ObsoleteAttributeData - { - get { return null; } - } - public override ImmutableArray DeclaringSyntaxReferences { get diff --git a/src/Compilers/CSharp/Portable/Symbols/ObsoleteAttributeHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/ObsoleteAttributeHelpers.cs index f73759e6fa04..09b06ea32b6e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ObsoleteAttributeHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ObsoleteAttributeHelpers.cs @@ -4,10 +4,12 @@ #nullable disable +using System; using System.Diagnostics; using System.Reflection.Metadata; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -54,7 +56,7 @@ internal static ObsoleteAttributeData GetObsoleteDataFromMetadata(EntityHandle t /// symbol's Obsoleteness is Unknown. False, if we are certain that no symbol in the parent /// hierarchy is Obsolete. /// - private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceComplete) + private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceComplete, Func getStateFromSymbol) { while ((object)symbol != null) { @@ -73,7 +75,7 @@ private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceCompl symbol.ForceCompleteObsoleteAttribute(); } - var state = symbol.ObsoleteState; + var state = getStateFromSymbol(symbol); if (state != ThreeState.False) { return state; @@ -98,10 +100,23 @@ internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, switch (symbol.ObsoleteKind) { case ObsoleteAttributeKind.None: + if (symbol.ContainingModule.ObsoleteKind is ObsoleteAttributeKind.Experimental + || symbol.ContainingAssembly.ObsoleteKind is ObsoleteAttributeKind.Experimental) + { + return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ExperimentalState); + } + + if (symbol.ContainingModule.ObsoleteKind is ObsoleteAttributeKind.Uninitialized + || symbol.ContainingAssembly.ObsoleteKind is ObsoleteAttributeKind.Uninitialized) + { + return ObsoleteDiagnosticKind.Lazy; + } + return ObsoleteDiagnosticKind.NotObsolete; case ObsoleteAttributeKind.WindowsExperimental: - case ObsoleteAttributeKind.Experimental: return ObsoleteDiagnosticKind.Diagnostic; + case ObsoleteAttributeKind.Experimental: + return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ExperimentalState); case ObsoleteAttributeKind.Uninitialized: // If we haven't cracked attributes on the symbol at all or we haven't // cracked attribute arguments enough to be able to report diagnostics for @@ -110,18 +125,23 @@ internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, return ObsoleteDiagnosticKind.Lazy; } - switch (GetObsoleteContextState(containingMember, forceComplete)) + return getDiagnosticKind(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ObsoleteState); + + static ObsoleteDiagnosticKind getDiagnosticKind(Symbol containingMember, bool forceComplete, Func getStateFromSymbol) { - case ThreeState.False: - return ObsoleteDiagnosticKind.Diagnostic; - case ThreeState.True: - // If we are in a context that is already obsolete, there is no point reporting - // more obsolete diagnostics. - return ObsoleteDiagnosticKind.Suppressed; - default: - // If the context is unknown, then store the symbol so that we can do this check at a - // later stage - return ObsoleteDiagnosticKind.LazyPotentiallySuppressed; + switch (GetObsoleteContextState(containingMember, forceComplete, getStateFromSymbol)) + { + case ThreeState.False: + return ObsoleteDiagnosticKind.Diagnostic; + case ThreeState.True: + // If we are in a context that is already experimental/obsolete, there is no point reporting + // more experimental/obsolete diagnostics. + return ObsoleteDiagnosticKind.Suppressed; + default: + // If the context is unknown, then store the symbol so that we can do this check at a + // later stage + return ObsoleteDiagnosticKind.LazyPotentiallySuppressed; + } } } @@ -137,7 +157,7 @@ internal static DiagnosticInfo CreateObsoleteDiagnostic(Symbol symbol, BinderFla static DiagnosticInfo createObsoleteDiagnostic(Symbol symbol, BinderFlags location) { - var data = symbol.ObsoleteAttributeData; + var data = symbol.ObsoleteAttributeData ?? symbol.ContainingModule.ObsoleteAttributeData ?? symbol.ContainingAssembly.ObsoleteAttributeData; Debug.Assert(data != null); if (data == null) diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.cs index 08ea5170c67b..62a92e425e3e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.cs @@ -284,6 +284,8 @@ internal override bool GetGuidString(out string guidString) } #nullable enable + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + => _underlyingAssembly.ObsoleteAttributeData; internal override NamedTypeSymbol? TryLookupForwardedMetadataTypeWithCycleDetection(ref MetadataTypeName emittedName, ConsList? visitedAssemblies) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingModuleSymbol.cs index c6e35a0486b3..5c51325b0807 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingModuleSymbol.cs @@ -318,5 +318,9 @@ public sealed override bool AreLocalsZeroed } internal override bool UseUpdatedEscapeRules => _underlyingModule.UseUpdatedEscapeRules; + +#nullable enable + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + => _underlyingModule.ObsoleteAttributeData; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs index b7bd00f63609..381307a6082a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs @@ -11,10 +11,8 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Security.Cryptography; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -1382,7 +1380,7 @@ private void LoadAndValidateNetModuleAttributes(ref CustomAttributesBag 0; i--) { - var peModuleSymbol = (Metadata.PE.PEModuleSymbol)_modules[i]; + var peModuleSymbol = (PEModuleSymbol)_modules[i]; foreach (NamedTypeSymbol forwarded in peModuleSymbol.GetForwardedTypes()) { @@ -2553,6 +2551,11 @@ private void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArguments().AssemblyAlgorithmIdAttributeSetting = algorithmId; } + else if (attribute.IsTargetAttribute(this, AttributeDescription.ExperimentalAttribute)) + { + var obsoleteData = attribute.DecodeExperimentalAttribute(); + arguments.GetOrCreateData().ExperimentalAttributeData = obsoleteData; + } } // Checks that the integral arguments for the given well-known attribute are non-negative. @@ -2860,6 +2863,41 @@ private static string DefaultValue(TypeSymbol type) return null; } + /// + /// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. + /// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. + /// + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + { + get + { + // [assembly: Experimental] may have been specified in the assembly or one of the modules + var lazySourceAttributesBag = _lazySourceAttributesBag; + if (lazySourceAttributesBag != null && lazySourceAttributesBag.IsDecodedWellKnownAttributeDataComputed) + { + var data = (CommonAssemblyWellKnownAttributeData)lazySourceAttributesBag.DecodedWellKnownAttributeData; + if (data?.ExperimentalAttributeData is { } experimentalAttributeData) + { + return experimentalAttributeData; + } + } + + var lazyNetModuleAttributesBag = _lazyNetModuleAttributesBag; + if (lazyNetModuleAttributesBag != null && lazyNetModuleAttributesBag.IsDecodedWellKnownAttributeDataComputed) + { + var data = (CommonAssemblyWellKnownAttributeData)lazyNetModuleAttributesBag.DecodedWellKnownAttributeData; + return data?.ExperimentalAttributeData; + } + + if (GetAttributeDeclarations().IsEmpty) + { + return null; + } + + return ObsoleteAttributeData.Uninitialized; + } + } + #nullable disable internal override IEnumerable GetAllTopLevelForwardedTypes() diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs index 1c1597da1300..0552c24da89c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs @@ -531,6 +531,10 @@ protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttribut { CSharpAttributeData.DecodeSkipLocalsInitAttribute(DeclaringCompilation, ref arguments); } + else if (attribute.IsTargetAttribute(this, AttributeDescription.ExperimentalAttribute)) + { + arguments.GetOrCreateData().ExperimentalAttributeData = attribute.DecodeExperimentalAttribute(); + } } #nullable enable @@ -652,5 +656,30 @@ internal override bool UseUpdatedEscapeRules return _lazyUseUpdatedEscapeRules == ThreeState.True; } } + + /// + /// Returns data decoded from attribute or null if there is no attribute. + /// This property returns if attribute arguments haven't been decoded yet. + /// + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + { + get + { + var attributesBag = _lazyCustomAttributesBag; + if (attributesBag != null && attributesBag.IsDecodedWellKnownAttributeDataComputed) + { + var decodedData = (ModuleWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData; + return decodedData?.ExperimentalAttributeData; + } + + var attributesDeclarations = ((SourceAssemblySymbol)ContainingAssembly).GetAttributeDeclarations(); + if (attributesDeclarations.IsEmpty) + { + return null; + } + + return ObsoleteAttributeData.Uninitialized; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs index feaabedd0fa0..3e246ed64d33 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs @@ -1309,6 +1309,7 @@ internal static bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo #endregion +#nullable enable /// /// True if this symbol has been marked with the attribute. /// This property returns if the attribute hasn't been cracked yet. @@ -1331,6 +1332,26 @@ internal ThreeState ObsoleteState } } + /// + /// True if this symbol has been marked with the System.Diagnostics.CodeAnalysis.ExperimentalAttribute attribute. + /// This property returns if the attribute hasn't been cracked yet. + /// + internal ThreeState ExperimentalState + { + get + { + switch (ObsoleteKind) + { + case ObsoleteAttributeKind.Experimental: + return ThreeState.True; + case ObsoleteAttributeKind.Uninitialized: + return ThreeState.Unknown; + default: + return ThreeState.False; + } + } + } + internal ObsoleteAttributeKind ObsoleteKind { get @@ -1341,10 +1362,11 @@ internal ObsoleteAttributeKind ObsoleteKind } /// - /// Returns data decoded from attribute or null if there is no attribute. + /// Returns data decoded from /Experimental attribute or null if there is no /Experimental attribute. /// This property returns if attribute arguments haven't been decoded yet. /// - internal abstract ObsoleteAttributeData ObsoleteAttributeData { get; } + internal abstract ObsoleteAttributeData? ObsoleteAttributeData { get; } +#nullable disable /// /// Returns true and a from the first on the symbol, diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs index cede809659f7..b48bc91efbdc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs @@ -932,15 +932,18 @@ private bool ValidateAttributeUsage( } /// - /// Ensure that attributes are bound and the ObsoleteState of this symbol is known. + /// Ensure that attributes are bound and the ObsoleteState/ExperimentalState of this symbol is known. /// internal void ForceCompleteObsoleteAttribute() { - if (this.ObsoleteState == ThreeState.Unknown) + if (this.ObsoleteKind == ObsoleteAttributeKind.Uninitialized) { this.GetAttributes(); } Debug.Assert(this.ObsoleteState != ThreeState.Unknown, "ObsoleteState should be true or false now."); + Debug.Assert(this.ExperimentalState != ThreeState.Unknown, "ExperimentalState should be true or false now."); + + this.ContainingSymbol?.ForceCompleteObsoleteAttribute(); } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index fbbac6fdabdc..a27698822c9b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -2060,7 +2060,7 @@ internal static bool IsWellKnownINumberBaseType(this TypeSymbol type) IsContainedInNamespace(type, "System", "Numerics"); } - private static bool IsWellKnownDiagnosticsCodeAnalysisTopLevelType(this TypeSymbol typeSymbol) + internal static bool IsWellKnownDiagnosticsCodeAnalysisTopLevelType(this TypeSymbol typeSymbol) => typeSymbol.ContainingType is null && IsContainedInNamespace(typeSymbol, "System", "Diagnostics", "CodeAnalysis"); private static bool IsContainedInNamespace(this TypeSymbol typeSymbol, string outerNS, string midNS, string? innerNS = null) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/ExperimentalAttributeTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/ExperimentalAttributeTests.cs index 1274cf88ad88..ccc7900586c8 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/ExperimentalAttributeTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/ExperimentalAttributeTests.cs @@ -4,9 +4,13 @@ #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; @@ -63,9 +67,8 @@ public static void M() { } } [Fact] - public void OnAssembly() + public void OnAssembly_UsedFromSource() { - // Ignored on assemblies var libSrc = """ [assembly: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")] public class C @@ -77,18 +80,815 @@ 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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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): warning 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) + ); + } + + [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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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): warning 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) + ); + } + + [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): warning 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), + // (1,1): warning 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) + ); + } + + [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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + + 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( + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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), + // 0.cs(1,1): warning 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) + ); + } + + [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): warning 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), + // 0.cs(8,9): warning 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) + ); + } + + [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(); + } +} - var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() }); +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): warning 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) + ); + } + + [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 OnModule() + public void OnOverride_ExperimentalFromAssembly_UsedFromMetadata() { - // Ignored on modules var libSrc = """ -[module: System.Diagnostics.CodeAnalysis.Experimental("DiagID1")] +[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( + // 0.cs(1,1): warning 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), + // 0.cs(1,24): warning 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) + ); + } + + [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() { } } @@ -98,8 +898,148 @@ public static void M() { } C.M(); """; - var comp = CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() }); + 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): warning 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), + // (1,9): warning 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) + ); + + var derived = comp.GetTypeByMetadataName("Derived"); + Assert.IsType(derived); + Assert.IsType(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(derived); + Assert.IsType(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): warning 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), + // (1,9): warning 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) + ); + + var derived = comp.GetTypeByMetadataName("Derived"); + Assert.IsType(derived); + Assert.IsType(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(derived); + Assert.IsType(derived.ContainingModule); + Assert.Equal(ObsoleteAttributeKind.None, derived.ContainingModule.ObsoleteKind); } [Theory, CombinatorialData] @@ -959,7 +1899,7 @@ void M2() [Theory, CombinatorialData] public void InExperimentalMethod(bool inSource) { - // Diagnostics for [Experimental] are not suppressed in [Experimental] members + // Diagnostics for [Experimental] are suppressed in [Experimental] context var libSrc = """ public class C { @@ -983,11 +1923,130 @@ void M2() ? CreateCompilation(new[] { src, libSrc, experimentalAttributeSrc }) : CreateCompilation(src, references: new[] { CreateCompilation(new[] { libSrc, experimentalAttributeSrc }).EmitToImageReference() }); - comp.VerifyDiagnostics( - // (6,9): warning 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) - ); + 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] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MockAssemblySymbol.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MockAssemblySymbol.cs index b6b446e4af80..730107de9ffe 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MockAssemblySymbol.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MockAssemblySymbol.cs @@ -122,5 +122,9 @@ internal override IEnumerable GetAllTopLevelForwardedTypes() { throw new NotImplementedException(); } + +#nullable enable + internal sealed override ObsoleteAttributeData? ObsoleteAttributeData + => null; } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAssemblyWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAssemblyWellKnownAttributeData.cs index 166e8a073382..2cf541a5e19c 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAssemblyWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAssemblyWellKnownAttributeData.cs @@ -8,6 +8,7 @@ using System.Reflection; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; +using System.Diagnostics; namespace Microsoft.CodeAnalysis { @@ -443,5 +444,27 @@ public HashSet ForwardedTypes } } #endregion + + #region ExperimentalAttribute + private ObsoleteAttributeData _experimentalAttributeData = ObsoleteAttributeData.Uninitialized; + public ObsoleteAttributeData ExperimentalAttributeData + { + get + { + VerifySealed(expected: true); + return _experimentalAttributeData.IsUninitialized ? null : _experimentalAttributeData; + } + set + { + VerifySealed(expected: false); + Debug.Assert(value != null); + Debug.Assert(!value.IsUninitialized); + Debug.Assert(value.Kind == ObsoleteAttributeKind.Experimental); + + _experimentalAttributeData = value; + SetDataStored(); + } + } + #endregion } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs index 646706093ac2..1ceb8f4f4e46 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs @@ -264,7 +264,7 @@ internal ObsoleteAttributeData DecodeObsoleteAttribute(ObsoleteAttributeKind kin } } - private ObsoleteAttributeData DecodeExperimentalAttribute() + internal ObsoleteAttributeData DecodeExperimentalAttribute() { // ExperimentalAttribute(string diagnosticId) Debug.Assert(this.CommonConstructorArguments.Length == 1); diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonModuleWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonModuleWellKnownAttributeData.cs index ffb1915e34dd..6f70dff95d90 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonModuleWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonModuleWellKnownAttributeData.cs @@ -64,5 +64,27 @@ internal static bool IsValidCharSet(CharSet value) return value >= Cci.Constants.CharSet_None && value <= Cci.Constants.CharSet_Auto; } #endregion + + #region ExperimentalAttribute + private ObsoleteAttributeData _experimentalAttributeData = ObsoleteAttributeData.Uninitialized; + public ObsoleteAttributeData ExperimentalAttributeData + { + get + { + VerifySealed(expected: true); + return _experimentalAttributeData.IsUninitialized ? null : _experimentalAttributeData; + } + set + { + VerifySealed(expected: false); + Debug.Assert(value != null); + Debug.Assert(!value.IsUninitialized); + Debug.Assert(value.Kind == ObsoleteAttributeKind.Experimental); + + _experimentalAttributeData = value; + SetDataStored(); + } + } + #endregion } } diff --git a/src/Compilers/Test/Utilities/VisualBasic/CompilationTestUtils.vb b/src/Compilers/Test/Utilities/VisualBasic/CompilationTestUtils.vb index a39b2dd93158..410cdd9e098b 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/CompilationTestUtils.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/CompilationTestUtils.vb @@ -33,6 +33,19 @@ Friend Module CompilationUtils Return CreateEmptyCompilation(source, references, options, parseOptions, assemblyName) End Function + Public Function CreateCompilationWithIdentity( + identity As AssemblyIdentity, + source As BasicTestSource, + Optional references As IEnumerable(Of MetadataReference) = Nothing, + Optional targetFramework As TargetFramework = TargetFramework.StandardAndVBRuntime) As VisualBasicCompilation + + Dim c = CreateCompilation(source, references, assemblyName:=identity.Name) + Assert.NotNull(c.Assembly) ' force creation Of SourceAssemblySymbol + DirectCast(c.Assembly, SourceAssemblySymbol).m_lazyIdentity = identity + + Return c + End Function + Public Function CreateEmptyCompilation( source As BasicTestSource, Optional references As IEnumerable(Of MetadataReference) = Nothing, diff --git a/src/Compilers/Test/Utilities/VisualBasic/MockSymbols.vb b/src/Compilers/Test/Utilities/VisualBasic/MockSymbols.vb index 28cdd322f7ce..ebbfcf124a72 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/MockSymbols.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/MockSymbols.vb @@ -747,6 +747,13 @@ Friend Class MockModuleSymbol Public Overrides Function GetMetadata() As ModuleMetadata Return Nothing End Function + + Friend NotOverridable Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return Nothing + End Get + End Property + End Class Friend Class MockAssemblySymbol @@ -861,4 +868,11 @@ Friend Class MockAssemblySymbol Public Overrides Function GetMetadata() As AssemblyMetadata Return Nothing End Function + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return Nothing + End Get + End Property + End Class diff --git a/src/Compilers/VisualBasic/Portable/Symbols/AssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/AssemblySymbol.vb index 09e42621e0c1..74da0a0cb68e 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/AssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/AssemblySymbol.vb @@ -238,16 +238,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property - ''' - ''' Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. - ''' This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. - ''' - Friend NotOverridable Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData - Get - Return Nothing - End Get - End Property - ''' ''' Lookup a top level type referenced from metadata, names should be ''' compared case-sensitively. diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEAssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEAssemblySymbol.vb index 9306bafa7931..5a8a17aab70d 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEAssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEAssemblySymbol.vb @@ -68,6 +68,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Private _lazyCachedCompilerFeatureRequiredDiagnosticInfo As DiagnosticInfo = ErrorFactory.EmptyDiagnosticInfo + Private _lazyObsoleteAttributeData As ObsoleteAttributeData = ObsoleteAttributeData.Uninitialized + Friend Sub New(assembly As PEAssembly, documentationProvider As DocumentationProvider, isLinked As Boolean, importOptions As MetadataImportOptions) Debug.Assert(assembly IsNot Nothing) @@ -286,5 +288,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Return MyBase.HasUnsupportedMetadata End Get End Property + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + If _lazyObsoleteAttributeData Is ObsoleteAttributeData.Uninitialized Then + Interlocked.CompareExchange(_lazyObsoleteAttributeData, ComputeObsoleteAttributeData(), ObsoleteAttributeData.Uninitialized) + End If + + Return _lazyObsoleteAttributeData + End Get + End Property + + Private Function ComputeObsoleteAttributeData() As ObsoleteAttributeData + For Each attrData In GetAttributes() + If attrData.IsTargetAttribute(Me, AttributeDescription.ExperimentalAttribute) Then + Return attrData.DecodeExperimentalAttribute() + End If + Next + + Return Nothing + End Function + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEModuleSymbol.vb index e6af8729c47d..40b385c28a0b 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEModuleSymbol.vb @@ -81,6 +81,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Private _lazyCachedCompilerFeatureRequiredDiagnosticInfo As DiagnosticInfo = ErrorFactory.EmptyDiagnosticInfo + Private _lazyObsoleteAttributeData As ObsoleteAttributeData = ObsoleteAttributeData.Uninitialized + Friend Sub New(assemblySymbol As PEAssemblySymbol, [module] As PEModule, importOptions As MetadataImportOptions, ordinal As Integer) Me.New(DirectCast(assemblySymbol, AssemblySymbol), [module], importOptions, ordinal) Debug.Assert(ordinal >= 0) @@ -507,5 +509,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Return MyBase.HasUnsupportedMetadata End Get End Property + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + If _lazyObsoleteAttributeData Is ObsoleteAttributeData.Uninitialized Then + Interlocked.CompareExchange(_lazyObsoleteAttributeData, ComputeObsoleteAttributeData(), ObsoleteAttributeData.Uninitialized) + End If + + Return _lazyObsoleteAttributeData + End Get + End Property + + Private Function ComputeObsoleteAttributeData() As ObsoleteAttributeData + For Each attrData In GetAttributes() + If attrData.IsTargetAttribute(Me, AttributeDescription.ExperimentalAttribute) Then + Return attrData.DecodeExperimentalAttribute() + End If + Next + + Return Nothing + End Function + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MissingAssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MissingAssemblySymbol.vb index c6a1d7d5a493..8e84e812e0bf 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MissingAssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MissingAssemblySymbol.vb @@ -165,6 +165,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Public Overrides Function GetMetadata() As AssemblyMetadata Return Nothing End Function + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return Nothing + End Get + End Property + End Class ''' diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MissingModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MissingModuleSymbol.vb index 5c62aa12a50d..1d88e088a130 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MissingModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MissingModuleSymbol.vb @@ -171,6 +171,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Public Overrides Function GetMetadata() As ModuleMetadata Return Nothing End Function + + Friend NotOverridable Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return Nothing + End Get + End Property + End Class Friend Class MissingModuleSymbolWithName diff --git a/src/Compilers/VisualBasic/Portable/Symbols/ModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/ModuleSymbol.vb index 2753e72de51d..3dcb5d16caef 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/ModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/ModuleSymbol.vb @@ -293,16 +293,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' Friend MustOverride ReadOnly Property MightContainExtensionMethods As Boolean - ''' - ''' Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. - ''' This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. - ''' - Friend NotOverridable Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData - Get - Return Nothing - End Get - End Property - #Region "IModuleSymbol" Private ReadOnly Property IModuleSymbol_GlobalNamespace As INamespaceSymbol Implements IModuleSymbol.GlobalNamespace Get diff --git a/src/Compilers/VisualBasic/Portable/Symbols/ObsoleteAttributeHelpers.vb b/src/Compilers/VisualBasic/Portable/Symbols/ObsoleteAttributeHelpers.vb index e8367ab52beb..a365857816ec 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/ObsoleteAttributeHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/ObsoleteAttributeHelpers.vb @@ -46,13 +46,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' symbol's Obsoleteness is Unknown. False, if we are certain that no symbol in the parent ''' hierarchy is Obsolete. ''' - Private Shared Function GetObsoleteContextState(symbol As Symbol, forceComplete As Boolean) As ThreeState + Private Shared Function GetObsoleteContextState(symbol As Symbol, forceComplete As Boolean, getStateFromSymbol As Func(Of Symbol, ThreeState)) As ThreeState While symbol IsNot Nothing If forceComplete Then symbol.ForceCompleteObsoleteAttribute() End If - Dim state = symbol.ObsoleteState + Dim state = getStateFromSymbol(symbol) If state <> ThreeState.False Then Return state End If @@ -74,9 +74,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Select Case symbol.ObsoleteKind Case ObsoleteAttributeKind.None + Dim moduleObsoleteKind As ObsoleteAttributeKind? = symbol.ContainingModule?.ObsoleteKind + Dim assemblyObsoleteKind As ObsoleteAttributeKind? = symbol.ContainingAssembly?.ObsoleteKind + + If moduleObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Experimental OrElse + assemblyObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Experimental Then + + Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.ExperimentalState) + End If + + If moduleObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Uninitialized OrElse + assemblyObsoleteKind = Global.Microsoft.CodeAnalysis.ObsoleteAttributeKind.Uninitialized Then + + Return ObsoleteDiagnosticKind.Lazy + End If + Return ObsoleteDiagnosticKind.NotObsolete - Case ObsoleteAttributeKind.WindowsExperimental, ObsoleteAttributeKind.Experimental + Case ObsoleteAttributeKind.WindowsExperimental Return ObsoleteDiagnosticKind.Diagnostic + Case ObsoleteAttributeKind.Experimental + Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.ExperimentalState) Case ObsoleteAttributeKind.Uninitialized ' If we haven't cracked attributes on the symbol at all or we haven't ' cracked attribute arguments enough to be able to report diagnostics for @@ -85,12 +102,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Return ObsoleteDiagnosticKind.Lazy End Select - Select Case GetObsoleteContextState(context, forceComplete) + Return GetDiagnosticKind(context, forceComplete, getStateFromSymbol:=Function(s) s.ObsoleteState) + End Function + + Private Shared Function GetDiagnosticKind(containingMember As Symbol, forceComplete As Boolean, getStateFromSymbol As Func(Of Symbol, ThreeState)) As ObsoleteDiagnosticKind + + Select Case GetObsoleteContextState(containingMember, forceComplete, getStateFromSymbol) Case ThreeState.False Return ObsoleteDiagnosticKind.Diagnostic Case ThreeState.True - ' If we are in a context that is already obsolete, there is no point reporting - ' more obsolete diagnostics. + ' If we are in a context that is already experimental/obsolete, there is no point reporting + ' more experimental/obsolete diagnostics. Return ObsoleteDiagnosticKind.Suppressed Case Else ' If the context is unknown, then store the symbol so that we can do this check at a @@ -104,7 +126,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' the ObsoleteAttribute's arguments. ''' Friend Shared Function CreateObsoleteDiagnostic(symbol As Symbol) As DiagnosticInfo - Dim data = symbol.ObsoleteAttributeData + Dim data = If(If(symbol.ObsoleteAttributeData, symbol.ContainingModule.ObsoleteAttributeData), symbol.ContainingAssembly.ObsoleteAttributeData) + Debug.Assert(data IsNot Nothing) If data Is Nothing Then diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.vb index 00d063d90fb9..20100cef60f0 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingAssemblySymbol.vb @@ -268,5 +268,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Public Overrides Function GetMetadata() As AssemblyMetadata Return _underlyingAssembly.GetMetadata() End Function + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return _underlyingAssembly.ObsoleteAttributeData + End Get + End Property + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingModuleSymbol.vb index ed1cd2fc5246..99987031c11f 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingModuleSymbol.vb @@ -290,5 +290,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Public Overrides Function GetMetadata() As ModuleMetadata Return _underlyingModule.GetMetadata() End Function + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Return _underlyingModule.ObsoleteAttributeData + End Get + End Property + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb index 7e522d31b6a9..7842f5985eb7 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb @@ -5,10 +5,7 @@ Imports System.Collections.Concurrent Imports System.Collections.Immutable Imports System.Reflection -Imports System.Reflection.Metadata -Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices -Imports System.Security.Cryptography Imports System.Threading Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Symbols @@ -1092,6 +1089,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols arguments.GetOrCreateData(Of CommonAssemblyWellKnownAttributeData)().RuntimeCompatibilityWrapNonExceptionThrows = True ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.DebuggableAttribute) Then arguments.GetOrCreateData(Of CommonAssemblyWellKnownAttributeData)().HasDebuggableAttribute = True + ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.ExperimentalAttribute) Then + arguments.GetOrCreateData(Of CommonAssemblyWellKnownAttributeData)().ExperimentalAttributeData = attrData.DecodeExperimentalAttribute() Else Dim signature As Integer = attrData.GetTargetAttributeSignatureIndex(Me, AttributeDescription.AssemblyAlgorithmIdAttribute) @@ -1780,5 +1779,30 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Return _compilation End Get End Property + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + ' may have been specified in the assembly or one of the modules + Dim attributesBag As CustomAttributesBag(Of VisualBasicAttributeData) = Me._lazySourceAttributesBag + If attributesBag IsNot Nothing AndAlso attributesBag.IsDecodedWellKnownAttributeDataComputed Then + Dim experimentalData = DirectCast(attributesBag.DecodedWellKnownAttributeData, CommonAssemblyWellKnownAttributeData)?.ExperimentalAttributeData + If experimentalData IsNot Nothing Then + Return experimentalData + End If + End If + + attributesBag = Me._lazyNetModuleAttributesBag + If attributesBag IsNot Nothing AndAlso attributesBag.IsDecodedWellKnownAttributeDataComputed Then + Return DirectCast(attributesBag.DecodedWellKnownAttributeData, CommonAssemblyWellKnownAttributeData)?.ExperimentalAttributeData + End If + + If GetAttributeDeclarations().IsEmpty Then + Return Nothing + End If + + Return ObsoleteAttributeData.Uninitialized + End Get + End Property + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb index e208ec8ce966..e151cdefa8ad 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb @@ -1116,6 +1116,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.DebuggableAttribute) Then arguments.GetOrCreateData(Of CommonModuleWellKnownAttributeData).HasDebuggableAttribute = True + ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.ExperimentalAttribute) Then + arguments.GetOrCreateData(Of CommonModuleWellKnownAttributeData).ExperimentalAttributeData = attrData.DecodeObsoleteAttribute(ObsoleteAttributeKind.Experimental) End If MyBase.DecodeWellKnownAttribute(arguments) @@ -1220,5 +1222,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Public Overrides Function GetMetadata() As ModuleMetadata Return Nothing End Function + + Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData + Get + Dim attributesBag As CustomAttributesBag(Of VisualBasicAttributeData) = Me._lazyCustomAttributesBag + If attributesBag IsNot Nothing AndAlso attributesBag.IsDecodedWellKnownAttributeDataComputed Then + Return DirectCast(attributesBag.DecodedWellKnownAttributeData, CommonModuleWellKnownAttributeData)?.ExperimentalAttributeData + End If + + Dim mergedAttributes = DirectCast(Me.ContainingAssembly, SourceAssemblySymbol).GetAttributeDeclarations() + If mergedAttributes.IsEmpty Then + Return Nothing + End If + + Return ObsoleteAttributeData.Uninitialized + End Get + End Property + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb index 9771862391d5..e5b8015696ee 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb @@ -456,6 +456,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + ''' + ''' True if this symbol has been marked with the Experimental attribute. + ''' This property returns Unknown if the Experimental attribute hasn't been cracked yet. + ''' + Friend ReadOnly Property ExperimentalState As ThreeState + Get + Select Case ObsoleteKind + Case ObsoleteAttributeKind.Experimental + Return ThreeState.True + Case ObsoleteAttributeKind.Uninitialized + Return ThreeState.Unknown + Case Else + Return ThreeState.False + End Select + End Get + End Property + Friend ReadOnly Property ObsoleteKind As ObsoleteAttributeKind Get Dim data = Me.ObsoleteAttributeData @@ -464,7 +481,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Property ''' - ''' Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute. + ''' Returns data decoded from Obsolete/Experimental attribute or null if there is no Obsolete/Experimental attribute. ''' This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet. ''' Friend MustOverride ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Symbol_Attributes.vb b/src/Compilers/VisualBasic/Portable/Symbols/Symbol_Attributes.vb index 924870153f8c..9ac12b073750 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Symbol_Attributes.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Symbol_Attributes.vb @@ -619,13 +619,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Sub ''' - ''' Ensure that attributes are bound and the ObsoleteState of this symbol is known. + ''' Ensure that attributes are bound and the ObsoleteState/ExperimentalState of this symbol is known. ''' Friend Sub ForceCompleteObsoleteAttribute() - If Me.ObsoleteState = ThreeState.Unknown Then + If Me.ObsoleteKind = ObsoleteAttributeKind.Uninitialized Then Me.GetAttributes() End If Debug.Assert(Me.ObsoleteState <> ThreeState.Unknown, "ObsoleteState should be true or false now.") + Debug.Assert(Me.ExperimentalState <> ThreeState.Unknown, "ExperimentalState should be true or false now.") + + Me.ContainingSymbol?.ForceCompleteObsoleteAttribute() End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb b/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb index a41614fd8878..62999f544783 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb @@ -5,12 +5,14 @@ Imports System.Collections.Immutable Imports System.IO Imports System.Reflection +Imports System.Resources Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities @@ -5067,7 +5069,7 @@ DiagID1: 'C' is for evaluation purposes only and is subject to change or removal ~ ]]>) - Dim diag = Comp.GetDiagnostics().Single() + Dim diag = comp.GetDiagnostics().Single() Assert.Equal("DiagID1", diag.Id) Assert.Equal(ERRID.WRN_Experimental, diag.Code) Assert.Equal("https://example.org/DiagID1", diag.Descriptor.HelpLinkUri) @@ -5111,5 +5113,550 @@ DiagID1: 'C' is for evaluation purposes only and is subject to change or removal Assert.Equal("https://example.org/DiagID1", diag.Descriptor.HelpLinkUri) End Sub + + Public Sub OnAssembly_UsedFromSource() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + + Dim src = + + + +Class C +End Class + +Class D + Sub M(c As C) + End Sub +End Class +]]> + + + + Dim comp = CreateCompilation(src, references:={attrComp.EmitToImageReference()}) + comp.AssertNoDiagnostics() + + Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind) + End Sub + + + Public Sub OnAssembly_UsedFromMetadata() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim libSrc = + + + +Public Class C +End Class +]]> + + + Dim libComp = CreateCompilation(libSrc, references:={attrRef}) + Dim libRef = libComp.EmitToImageReference() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={libRef, attrRef}) + + comp.AssertTheseDiagnostics( +) + + Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind) + + Dim diag = comp.GetDiagnostics().Single() + Assert.Equal("DiagID1", diag.Id) + Assert.Equal(ERRID.WRN_Experimental, diag.Code) + Assert.Equal("https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(BC42380)", diag.Descriptor.HelpLinkUri) + End Sub + + + Public Sub OnAssembly_UsedFromMetadata_ObsoleteType() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim libSrc = + + + + +Public Class C +End Class +]]> + + + Dim libComp = CreateCompilation(libSrc, references:={attrRef}) + Dim libRef = libComp.EmitToImageReference() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={libRef, attrRef}) + + comp.AssertTheseDiagnostics( +) + + Assert.Equal(ObsoleteAttributeKind.Obsolete, comp.GetTypeByMetadataName("C").ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind) + End Sub + + + Public Sub OnAssembly_CompiledIntoModule() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim libSrc = + + + +Public Class C + Public Shared Sub M() + End Sub +End Class +]]> + + + + Dim moduleComp = CreateCompilation(libSrc, options:=TestOptions.ReleaseModule, references:={attrRef}) + moduleComp.VerifyDiagnostics() + Dim moduleRef = moduleComp.EmitToImageReference() + + Dim libSrc2 = + + + + + + Dim assemblyComp = CreateCompilation(libSrc2, references:={moduleRef, attrRef}) + assemblyComp.VerifyDiagnostics() + Dim assemblyRef = assemblyComp.EmitToImageReference() + + Dim src = + + + + + + ' Since the module is referenced but not linked, we also need it here, but as + ' a result the diagnostics are suppressed + Dim comp = CreateCompilation(src, references:={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) + End Sub + + + Public Sub RetargetingAssembly_Experimental() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim retargetedCode = + + + + + + Dim originalC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode) + Dim retargetedC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode) + + Dim derivedSrc = + + + +Public Class Derived + Inherits C +End Class +]]> + + + + Dim derivedComp = CreateCompilation(derivedSrc, references:={originalC.ToMetadataReference(), attrRef}) + derivedComp.AssertNoDiagnostics() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference(), attrRef}) + comp.AssertTheseDiagnostics() + + Dim derivedType = comp.GetTypeByMetadataName("Derived") + Assert.IsType(Of RetargetingNamedTypeSymbol)(derivedType) + Assert.IsType(Of RetargetingAssemblySymbol)(derivedType.ContainingAssembly) + Assert.Equal(ObsoleteAttributeKind.Experimental, derivedType.ContainingAssembly.ObsoleteKind) + End Sub + + + Public Sub RetargetingAssembly_NotExperimental() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim retargetedCode = + + + + + + Dim originalC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode) + Dim retargetedC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode) + + Dim derivedSrc = + + + + + + Dim derivedComp = CreateCompilation(derivedSrc, references:={originalC.ToMetadataReference(), attrRef}) + derivedComp.AssertNoDiagnostics() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference(), attrRef}) + derivedComp.AssertNoDiagnostics() + + Dim derivedType = comp.GetTypeByMetadataName("Derived") + Assert.IsType(Of RetargetingNamedTypeSymbol)(derivedType) + Assert.IsType(Of RetargetingAssemblySymbol)(derivedType.ContainingAssembly) + Assert.Equal(ObsoleteAttributeKind.None, derivedType.ContainingAssembly.ObsoleteKind) + End Sub + + + Public Sub RetargetingModule_Experimental() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim retargetedCode = + + + + + + Dim originalC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode) + Dim retargetedC = CreateCompilationWithIdentity(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode) + + Dim derivedSrc = + + + +Public Class Derived + Inherits C +End Class +]]> + + + + Dim derivedComp = CreateCompilation(derivedSrc, references:={originalC.ToMetadataReference(), attrRef}) + derivedComp.AssertNoDiagnostics() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference(), attrRef}) + comp.AssertTheseDiagnostics() + + Dim derivedType = comp.GetTypeByMetadataName("Derived") + Assert.IsType(Of RetargetingNamedTypeSymbol)(derivedType) + Assert.IsType(Of RetargetingModuleSymbol)(derivedType.ContainingModule) + Assert.Equal(ObsoleteAttributeKind.Experimental, derivedType.ContainingModule.ObsoleteKind) + End Sub + + + Public Sub MissingAssemblyAndModule() + + Dim missingSrc = + + + + + + Dim missingRef = CreateCompilation(missingSrc, assemblyName:="missing").EmitToImageReference() + + Dim libSrc = + + + + + + Dim libRef = CreateCompilation(libSrc, references:={missingRef}).EmitToImageReference() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={libRef}) + + comp.AssertTheseDiagnostics() + + Dim missingType = comp.GlobalNamespace.GetTypeMember("C").BaseTypeNoUseSiteDiagnostics + Assert.True(TypeOf missingType.ContainingAssembly Is MissingAssemblySymbol) + Assert.Equal(ObsoleteAttributeKind.None, missingType.ContainingAssembly.ObsoleteKind) + Assert.True(TypeOf missingType.ContainingModule Is MissingModuleSymbol) + Assert.Equal(ObsoleteAttributeKind.None, missingType.ContainingModule.ObsoleteKind) + End Sub + + + Public Sub Cycle() + + Dim src = + + + +Namespace System.Diagnostics.CodeAnalysis + + Public Class ExperimentalAttribute + Inherits System.Attribute + + Public Sub New(diagnosticId As String) + End Sub + + Public Property UrlFormat As String + End Class +End Namespace +]]> + + + + Dim comp = CreateCompilation(src) + comp.AssertNoDiagnostics() + End Sub + + + Public Sub OnModule_UsedFromMetadata() + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim libSrc = + + + +Public Class C + Public Shared Sub M() + End Sub +End Class +]]> + + + + Dim libComp = CreateCompilation(libSrc, references:={attrRef}) + Dim libRef = libComp.EmitToImageReference() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={libRef, attrRef}) + + comp.AssertTheseDiagnostics( +) + + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.None, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind) + + For Each diag In comp.GetDiagnostics() + Assert.Equal("DiagID1", diag.Id) + Assert.Equal(ERRID.WRN_Experimental, diag.Code) + Assert.Equal("https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(BC42380)", diag.Descriptor.HelpLinkUri) + Next + End Sub + + + Public Sub OnModuleAndAssembly() + ' Prefer reporting the module-level diagnostic + Dim attrComp = CreateCSharpCompilation(experimentalAttributeCSharpSrc) + Dim attrRef = attrComp.EmitToImageReference() + + Dim libSrc = + + + + +Public Class C + Public Shared Sub M() + End Sub +End Class +]]> + + + + Dim libComp = CreateCompilation(libSrc, references:={attrRef}) + Dim libRef = libComp.EmitToImageReference() + + Dim src = + + + + + + Dim comp = CreateCompilation(src, references:={libRef, attrRef}) + + comp.AssertTheseDiagnostics( +) + + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingModule.ObsoleteKind) + Assert.Equal(ObsoleteAttributeKind.Experimental, comp.GetTypeByMetadataName("C").ContainingAssembly.ObsoleteKind) + + For Each diag In comp.GetDiagnostics() + Assert.Equal("DiagModule", diag.Id) + Next + End Sub + End Class End Namespace