From 475701ec848faf822aba1b63fe04aef022dee455 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Tue, 17 May 2022 16:31:52 -0700 Subject: [PATCH 1/2] Emit CompilerFeatureRequired for ref structs when present. --- .../Symbols/Metadata/PE/PEModuleSymbol.cs | 16 +- .../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 5 +- .../Symbols/Metadata/PE/PEParameterSymbol.cs | 2 + .../Symbols/Source/SourceNamedTypeSymbol.cs | 27 +-- .../Attributes/AttributeTests_IsByRefLike.cs | 164 ++++++++++++------ .../Symbol/Symbols/MissingSpecialMember.cs | 2 + .../Core/Portable/WellKnownMember.cs | 2 + .../Core/Portable/WellKnownMembers.cs | 9 + src/Compilers/Core/Portable/WellKnownTypes.cs | 3 + .../WellKnownTypeValidationTests.vb | 13 +- 10 files changed, 169 insertions(+), 74 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs index c7aba5ec51747..6a27957a301ac 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs @@ -303,7 +303,7 @@ internal ImmutableArray GetCustomAttributesForToken(EntityH out CustomAttributeHandle filteredOutAttribute1, AttributeDescription filterOut1) { - return GetCustomAttributesForToken(token, out filteredOutAttribute1, filterOut1, out _, default, out _, default, out _, default); + return GetCustomAttributesForToken(token, out filteredOutAttribute1, filterOut1, out _, default, out _, default, out _, default, out _, default); } /// @@ -318,12 +318,15 @@ internal ImmutableArray GetCustomAttributesForToken(EntityH out CustomAttributeHandle filteredOutAttribute3, AttributeDescription filterOut3, out CustomAttributeHandle filteredOutAttribute4, - AttributeDescription filterOut4) + AttributeDescription filterOut4, + out CustomAttributeHandle filteredOutAttribute5, + AttributeDescription filterOut5) { filteredOutAttribute1 = default; filteredOutAttribute2 = default; filteredOutAttribute3 = default; filteredOutAttribute4 = default; + filteredOutAttribute5 = default; ArrayBuilder customAttributesBuilder = null; try @@ -357,6 +360,12 @@ internal ImmutableArray GetCustomAttributesForToken(EntityH continue; } + if (matchesFilter(customAttributeHandle, filterOut5)) + { + filteredOutAttribute5 = customAttributeHandle; + continue; + } + if (customAttributesBuilder == null) { customAttributesBuilder = ArrayBuilder.GetInstance(); @@ -438,7 +447,8 @@ internal ImmutableArray GetCustomAttributesFilterCompilerAt filteredOutAttribute2: out CustomAttributeHandle isReadOnlyAttribute, filterOut2: AttributeDescription.IsReadOnlyAttribute, filteredOutAttribute3: out _, filterOut3: default, - filteredOutAttribute4: out _, filterOut4: default); + filteredOutAttribute4: out _, filterOut4: default, + filteredOutAttribute5: out _, filterOut5: default); foundExtension = !extensionAttribute.IsNil; foundReadOnly = !isReadOnlyAttribute.IsNil; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 74dfe4e540be5..13ba87bed8907 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -679,7 +679,10 @@ public override ImmutableArray GetAttributes() IsReadOnly ? AttributeDescription.IsReadOnlyAttribute : default, out _, // Filter out [IsByRefLike] - IsRefLikeType ? AttributeDescription.IsByRefLikeAttribute : default); + IsRefLikeType ? AttributeDescription.IsByRefLikeAttribute : default, + out _, + // Filter out [CompilerFeatureRequired] + (IsRefLikeType && DeriveCompilerFeatureRequiredDiagnostic() is null) ? AttributeDescription.CompilerFeatureRequiredAttribute : default); ImmutableInterlocked.InterlockedInitialize(ref uncommon.lazyCustomAttributes, loadedCustomAttributes); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index 2cd953f24778c..5d01ddf3fd327 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -997,6 +997,8 @@ public override ImmutableArray GetAttributes() out isReadOnlyAttribute, filterIsReadOnlyAttribute ? AttributeDescription.IsReadOnlyAttribute : default, out _, + default, + out _, default); if (!paramArrayAttribute.IsNil || !constantAttribute.IsNil) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index ce90ac3dff23d..3bdcb5d8c4653 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -1557,17 +1557,24 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r var obsoleteData = ObsoleteAttributeData; Debug.Assert(obsoleteData != ObsoleteAttributeData.Uninitialized, "getting synthesized attributes before attributes are decoded"); - // If user specified an Obsolete attribute, we cannot emit ours. - // NB: we do not check the kind of deprecation. - // we will not emit Obsolete even if Deprecated or Experimental was used. - // we do not want to get into a scenario where different kinds of deprecation are combined together. - // - if (obsoleteData == null && !this.IsRestrictedType(ignoreSpanLikeTypes: true)) + if (!this.IsRestrictedType(ignoreSpanLikeTypes: true)) { - AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_ObsoleteAttribute__ctor, - ImmutableArray.Create( - new TypedConstant(compilation.GetSpecialType(SpecialType.System_String), TypedConstantKind.Primitive, PEModule.ByRefLikeMarker), // message - new TypedConstant(compilation.GetSpecialType(SpecialType.System_Boolean), TypedConstantKind.Primitive, true)), // error=true + // If user specified an Obsolete attribute, we cannot emit ours. + // NB: we do not check the kind of deprecation. + // we will not emit Obsolete even if Deprecated or Experimental was used. + // we do not want to get into a scenario where different kinds of deprecation are combined together. + // + if (obsoleteData == null) + { + AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_ObsoleteAttribute__ctor, + ImmutableArray.Create( + new TypedConstant(compilation.GetSpecialType(SpecialType.System_String), TypedConstantKind.Primitive, PEModule.ByRefLikeMarker), // message + new TypedConstant(compilation.GetSpecialType(SpecialType.System_Boolean), TypedConstantKind.Primitive, true)), // error=true + isOptionalUse: true)); + } + + AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + ImmutableArray.Create(new TypedConstant(compilation.GetSpecialType(SpecialType.System_String), TypedConstantKind.Primitive, nameof(CompilerFeatureRequiredFeatures.RefStructs))), isOptionalUse: true)); } } diff --git a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_IsByRefLike.cs b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_IsByRefLike.cs index 4fc664c0be588..61a7964dcfdd9 100644 --- a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_IsByRefLike.cs +++ b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_IsByRefLike.cs @@ -21,8 +21,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class AttributeTests_IsByRefLike : CSharpTestBase { - [Fact] - public void IsByRefLikeIsWrittenToMetadata_SameAssembly() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_SameAssembly(bool includeCompilerFeatureRequired) { var text = @" namespace System.Runtime.CompilerServices @@ -38,32 +39,34 @@ public ref struct S1 {} void validate(ModuleSymbol module) { var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); var peModule = (PEModuleSymbol)module; Assert.True(peModule.Module.HasIsByRefLikeAttribute(((PENamedTypeSymbol)type).Handle)); AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsByRefLikeAttribute, Accessibility.Public); } - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: validate); + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: validate); } - [Fact] - public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGenerated() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGenerated(bool includeCompilerFeatureRequired) { var text = @" ref struct S1{} "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("S1"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } - [Fact] - public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedNested() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedNested(bool includeCompilerFeatureRequired) { var text = @" class Test @@ -72,15 +75,16 @@ public ref struct S1 {} } "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } - [Fact] - public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedGeneric() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedGeneric(bool includeCompilerFeatureRequired) { var text = @" class Test @@ -92,18 +96,19 @@ public ref struct S1 {} void validate(ModuleSymbol module) { var type = module.ContainingAssembly.GetTypeByMetadataName("Test+S1`1"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); var peModule = (PEModuleSymbol)module; Assert.True(peModule.Module.HasIsByRefLikeAttribute(((PENamedTypeSymbol)type).Handle)); AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsByRefLikeAttribute, Accessibility.Internal); } - CompileAndVerify(text, symbolValidator: validate); + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, symbolValidator: validate); } - [Fact] - public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedNestedInGeneric() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_NeedsToBeGeneratedNestedInGeneric(bool includeCompilerFeatureRequired) { var text = @" class Test @@ -112,15 +117,16 @@ public ref struct S1 {} } "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("Test`1").GetTypeMember("S1"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } - [Fact] - public void IsByRefLikeIsWrittenToMetadata_DifferentAssembly() + [Theory] + [CombinatorialData] + public void IsByRefLikeIsWrittenToMetadata_DifferentAssembly(bool includeCompilerFeatureRequired) { var codeA = @" namespace System.Runtime.CompilerServices @@ -128,7 +134,7 @@ namespace System.Runtime.CompilerServices public class IsByRefLikeAttribute : System.Attribute { } }"; - var referenceA = CreateCompilation(codeA).VerifyDiagnostics().ToMetadataReference(); + var referenceA = CreateCompilation(new[] { codeA, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }).VerifyDiagnostics().ToMetadataReference(); var codeB = @" class Test @@ -141,8 +147,8 @@ public ref struct S1 {} { var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); - AssertReferencedIsByRefLike(type); - AssertNoIsByRefLikeAttributeExists(module.ContainingAssembly); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); + AssertNoIsByRefLikeAttributeOrCompilerFeatureRequiredAttributeExists(module.ContainingAssembly, includeCompilerFeatureRequired); }); } @@ -436,7 +442,7 @@ public ref struct S1{} var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); AssertReferencedIsByRefLike(type); - AssertNoIsByRefLikeAttributeExists(module.ContainingAssembly); + AssertNoIsByRefLikeAttributeOrCompilerFeatureRequiredAttributeExists(module.ContainingAssembly, hasCompilerFeatureRequired: false); }); } @@ -609,7 +615,7 @@ public void M(Test p) void symbolValidator(ModuleSymbol module) { // No attribute is copied - AssertNoIsByRefLikeAttributeExists(module.ContainingAssembly); + AssertNoIsByRefLikeAttributeOrCompilerFeatureRequiredAttributeExists(module.ContainingAssembly, hasCompilerFeatureRequired: false); var type = module.ContainingAssembly.GetTypeByMetadataName("Test"); @@ -642,8 +648,9 @@ public ref struct S1{} ); } - [Fact] - public void IsByRefLikeObsolete() + [Theory] + [CombinatorialData] + public void IsByRefLikeObsolete(bool includeCompilerFeatureRequired) { var text = @" namespace System.Runtime.CompilerServices @@ -672,12 +679,14 @@ void validate(ModuleSymbol module) if (module is PEModuleSymbol peModule) { - Assert.True(peModule.Module.HasIsByRefLikeAttribute(((PENamedTypeSymbol)type).Handle)); + var peType = (PENamedTypeSymbol)type; + Assert.True(peModule.Module.HasIsByRefLikeAttribute(peType.Handle)); AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsByRefLikeAttribute, Accessibility.Public); + AssertHasCompilerFeatureRequired(includeCompilerFeatureRequired, peType, peModule, new MetadataDecoder(peModule)); } }; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: validate, sourceSymbolValidator: validate); + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: validate, sourceSymbolValidator: validate); } [Fact] @@ -707,8 +716,9 @@ public class ObsoleteAttribute{} }); } - [Fact] - public void IsByRefLikeDeprecated() + [Theory] + [CombinatorialData] + public void IsByRefLikeDeprecated(bool includeCompilerFeatureRequired) { var text = @" using System; @@ -742,7 +752,7 @@ public ref struct S1 {} } "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); Assert.True(type.IsRefLikeType); @@ -750,11 +760,15 @@ public ref struct S1 {} var attribute = type.GetAttributes().Single(); Assert.Equal("Windows.Foundation.Metadata.DeprecatedAttribute", attribute.AttributeClass.ToDisplayString()); Assert.Equal(42u, attribute.ConstructorArguments.ElementAt(2).Value); + + var peModule = (PEModuleSymbol)module; + AssertHasCompilerFeatureRequired(includeCompilerFeatureRequired, (PENamedTypeSymbol)type, peModule, new MetadataDecoder(peModule)); }); } - [Fact] - public void IsByRefLikeDeprecatedAndObsolete() + [Theory] + [CombinatorialData] + public void IsByRefLikeDeprecatedAndObsolete(bool includeCompilerFeatureRequired) { var text = @" using System; @@ -789,7 +803,7 @@ public ref struct S1 {} } "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("Test").GetTypeMember("S1"); Assert.True(type.IsRefLikeType); @@ -802,6 +816,9 @@ public ref struct S1 {} var attribute = attributes[0]; Assert.Equal("System.ObsoleteAttribute", attribute.AttributeClass.ToDisplayString()); Assert.Equal(0, attribute.ConstructorArguments.Count()); + + var peModule = (PEModuleSymbol)module; + AssertHasCompilerFeatureRequired(includeCompilerFeatureRequired, (PENamedTypeSymbol)type, peModule, new MetadataDecoder(peModule)); }); } @@ -845,15 +862,16 @@ public ref struct S2 {} ); } - [Fact] - public void ObsoleteHasErrorEqualsTrue() + [Theory] + [CombinatorialData] + public void ObsoleteHasErrorEqualsTrue(bool includeCompilerFeatureRequired) { var text = @"public ref struct S {}"; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("S"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } @@ -929,8 +947,9 @@ void Method() } [WorkItem(22198, "https://github.com/dotnet/roslyn/issues/22198")] - [Fact] - public void SpecialTypes_CorLib() + [Theory] + [CombinatorialData] + public void SpecialTypes_CorLib(bool includeCompilerFeatureRequired) { var source1 = @" @@ -955,28 +974,44 @@ public ref struct RuntimeArgumentHandle { } public ref struct NotTypedReference { } }"; - var compilation1 = CreateEmptyCompilation(source1, assemblyName: GetUniqueName()); + + var compilerFeatureRequiredAttribute = includeCompilerFeatureRequired ? + """ + namespace System.Runtime.CompilerServices + { + public class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + public string FeatureName { get; } + } + } + """ : ""; + var compilation1 = CreateEmptyCompilation(new[] { source1, compilerFeatureRequiredAttribute }, assemblyName: GetUniqueName()); // PEVerify: Type load failed. CompileAndVerify(compilation1, verify: Verification.FailsPEVerify, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("System.TypedReference"); - AssertReferencedIsByRefLike(type, hasObsolete: false); + AssertReferencedIsByRefLike(type, hasObsolete: false, hasCompilerFeatureRequired: includeCompilerFeatureRequired); type = module.ContainingAssembly.GetTypeByMetadataName("System.ArgIterator"); - AssertReferencedIsByRefLike(type, hasObsolete: false); + AssertReferencedIsByRefLike(type, hasObsolete: false, hasCompilerFeatureRequired: includeCompilerFeatureRequired); type = module.ContainingAssembly.GetTypeByMetadataName("System.RuntimeArgumentHandle"); - AssertReferencedIsByRefLike(type, hasObsolete: false); + AssertReferencedIsByRefLike(type, hasObsolete: false, hasCompilerFeatureRequired: includeCompilerFeatureRequired); // control case. Not a special type. type = module.ContainingAssembly.GetTypeByMetadataName("System.NotTypedReference"); - AssertReferencedIsByRefLike(type, hasObsolete: true); + AssertReferencedIsByRefLike(type, hasObsolete: true, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } - [Fact] - public void SpecialTypes_NotCorLib() + [Theory] + [CombinatorialData] + public void SpecialTypes_NotCorLib(bool includeCompilerFeatureRequired) { var text = @" namespace System @@ -985,24 +1020,25 @@ public ref struct TypedReference { } } "; - CompileAndVerify(text, verify: Verification.Passes, symbolValidator: module => + CompileAndVerify(new[] { text, GetCompilerFeatureRequiredAttributeText(includeCompilerFeatureRequired) }, verify: Verification.Passes, symbolValidator: module => { var type = module.ContainingAssembly.GetTypeByMetadataName("System.TypedReference"); - AssertReferencedIsByRefLike(type); + AssertReferencedIsByRefLike(type, hasCompilerFeatureRequired: includeCompilerFeatureRequired); }); } - private static void AssertReferencedIsByRefLike(TypeSymbol type, bool hasObsolete = true) + private static void AssertReferencedIsByRefLike(TypeSymbol type, bool hasObsolete = true, bool hasCompilerFeatureRequired = false) { var peType = (PENamedTypeSymbol)type; Assert.True(peType.IsRefLikeType); - // there is no [Obsolete] or [IsByRef] attribute returned + // there is no [Obsolete], [IsByRef], or [CompilerFeatureRequired] attribute returned Assert.Empty(peType.GetAttributes()); var peModule = (PEModuleSymbol)peType.ContainingModule; - var obsoleteAttribute = peModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(peType.Handle, new MetadataDecoder(peModule), ignoreByRefLikeMarker: false, ignoreRequiredMemberMarker: false); + var decoder = new MetadataDecoder(peModule); + var obsoleteAttribute = peModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(peType.Handle, decoder, ignoreByRefLikeMarker: false, ignoreRequiredMemberMarker: false); if (hasObsolete) { @@ -1014,6 +1050,18 @@ private static void AssertReferencedIsByRefLike(TypeSymbol type, bool hasObsolet { Assert.Null(obsoleteAttribute); } + + AssertHasCompilerFeatureRequired(hasCompilerFeatureRequired, peType, peModule, decoder); + } + + private static void AssertHasCompilerFeatureRequired(bool hasCompilerFeatureRequired, PENamedTypeSymbol peType, PEModuleSymbol peModule, MetadataDecoder decoder) + { + var compilerFeatureRequiredToken = peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peType.Handle, decoder, CompilerFeatureRequiredFeatures.RefStructs); + Assert.Null(compilerFeatureRequiredToken); + + compilerFeatureRequiredToken = peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peType.Handle, decoder, CompilerFeatureRequiredFeatures.None); + var shouldHaveMarker = hasCompilerFeatureRequired && !peType.IsRestrictedType(ignoreSpanLikeTypes: true); + Assert.Equal(shouldHaveMarker ? nameof(CompilerFeatureRequiredFeatures.RefStructs) : null, compilerFeatureRequiredToken); } private static void AssertNotReferencedIsByRefLikeAttribute(ImmutableArray attributes) @@ -1024,10 +1072,12 @@ private static void AssertNotReferencedIsByRefLikeAttribute(ImmutableArray hasAttribute ? CompilerFeatureRequiredAttribute : ""; } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 9374505c903c6..20dfbd45a9480 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -613,6 +613,7 @@ public void AllWellKnownTypes() case WellKnownType.System_Runtime_CompilerServices_RequiredMemberAttribute: case WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute: case WellKnownType.System_MemoryExtensions: + case WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute: // Not yet in the platform. continue; case WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation: @@ -974,6 +975,7 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T: case WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T: case WellKnownMember.System_MemoryExtensions__AsSpan_String: + case WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor: // Not yet in the platform. continue; case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile: diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 8a50dce64c0d8..80c00c7785dde 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -525,6 +525,8 @@ internal enum WellKnownMember System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T, System_MemoryExtensions__AsSpan_String, + System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + Count // Remember to update the AllWellKnownTypeMembers tests when making changes here diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index b782d27481c30..b03330bcf7040 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3606,6 +3606,14 @@ static WellKnownMembers() 1, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor + (byte)MemberFlags.Constructor, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -4057,6 +4065,7 @@ static WellKnownMembers() "SequenceEqual", // System_MemoryExtensions__SequenceEqual_Span_T "SequenceEqual", // System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T "AsSpan", // System_MemoryExtensions__AsSpan_String + ".ctor", // System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute_ctor }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 000e85d88ffb4..292597a71b9e0 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -321,6 +321,8 @@ internal enum WellKnownType System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, System_MemoryExtensions, + System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, + NextAvailable, // Remember to update the AllWellKnownTypes tests when making changes here } @@ -633,6 +635,7 @@ internal static class WellKnownTypes "System.Runtime.CompilerServices.RequiredMemberAttribute", "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", "System.MemoryExtensions", + "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", }; private static readonly Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index b8139677aa6a1..d75a38fcc3944 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -547,7 +547,8 @@ End Namespace WellKnownType.System_Runtime_CompilerServices_DefaultInterpolatedStringHandler, WellKnownType.System_Runtime_CompilerServices_RequiredMemberAttribute, WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, - WellKnownType.System_MemoryExtensions + WellKnownType.System_MemoryExtensions, + WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute ' Not available on all platforms. Continue For Case WellKnownType.ExtSentinel @@ -616,7 +617,9 @@ End Namespace WellKnownType.System_Runtime_CompilerServices_DefaultInterpolatedStringHandler, WellKnownType.System_Runtime_CompilerServices_RequiredMemberAttribute, WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, - WellKnownType.System_MemoryExtensions + WellKnownType.System_MemoryExtensions, + WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute + ' Not available on all platforms. Continue For Case WellKnownType.ExtSentinel @@ -709,7 +712,8 @@ End Namespace WellKnownMember.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor, WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T, WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T, - WellKnownMember.System_MemoryExtensions__AsSpan_String + WellKnownMember.System_MemoryExtensions__AsSpan_String, + WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor ' Not available yet, but will be in upcoming release. Continue For Case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, @@ -858,7 +862,8 @@ End Namespace WellKnownMember.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor, WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T, WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T, - WellKnownMember.System_MemoryExtensions__AsSpan_String + WellKnownMember.System_MemoryExtensions__AsSpan_String, + WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor ' Not available yet, but will be in upcoming release. Continue For Case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, From ab2c3a89051041ac6fe93d32ebf1a5e3ae1bb359 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Tue, 17 May 2022 17:26:51 -0700 Subject: [PATCH 2/2] Support emitting CompilerFeatureRequiredAttribute for contructors of types with required members. --- .../Symbols/Metadata/PE/PEMethodSymbol.cs | 22 ++++- .../Symbols/Metadata/PE/PEModuleSymbol.cs | 9 -- .../CSharp/Portable/Symbols/MethodSymbol.cs | 6 +- .../Symbols/Source/SourceConstructorSymbol.cs | 3 - .../Source/SourceMemberContainerSymbol.cs | 6 ++ .../Symbol/Symbols/RequiredMembersTests.cs | 92 ++++++++++++++----- 6 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs index 0a55b53a0fc3f..618a0cc4cf66f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs @@ -952,14 +952,26 @@ public override ImmutableArray GetAttributes() ? _packedFlags.IsReadOnly : IsValidReadOnlyTarget; + bool checkForRequiredMembers = this.ShouldCheckRequiredMembers() && this.ContainingType.HasAnyRequiredMembers; + bool isExtensionMethod = false; bool isReadOnly = false; - if (checkForExtension || checkForIsReadOnly) + if (checkForExtension || checkForIsReadOnly || checkForRequiredMembers) { - containingPEModuleSymbol.LoadCustomAttributesFilterCompilerAttributes(_handle, - ref attributeData, - out isExtensionMethod, - out isReadOnly); + attributeData = containingPEModuleSymbol.GetCustomAttributesForToken(_handle, + filteredOutAttribute1: out CustomAttributeHandle extensionAttribute, + filterOut1: AttributeDescription.CaseSensitiveExtensionAttribute, + filteredOutAttribute2: out CustomAttributeHandle isReadOnlyAttribute, + filterOut2: AttributeDescription.IsReadOnlyAttribute, + filteredOutAttribute3: out _, + filterOut3: (checkForRequiredMembers && DeriveCompilerFeatureRequiredDiagnostic() is null) ? AttributeDescription.CompilerFeatureRequiredAttribute : default, + filteredOutAttribute4: out _, + filterOut4: (checkForRequiredMembers && ObsoleteAttributeData is null) ? AttributeDescription.ObsoleteAttribute : default, + filteredOutAttribute5: out _, + filterOut5: default); + + isExtensionMethod = !extensionAttribute.IsNil; + isReadOnly = !isReadOnlyAttribute.IsNil; } else { diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs index 6a27957a301ac..ff1f0d5a8cc13 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEModuleSymbol.cs @@ -283,15 +283,6 @@ internal void LoadCustomAttributes(EntityHandle token, ref ImmutableArray customAttributes, - out bool foundExtension, - out bool foundReadOnly) - { - var loadedCustomAttributes = GetCustomAttributesFilterCompilerAttributes(token, out foundExtension, out foundReadOnly); - ImmutableInterlocked.InterlockedInitialize(ref customAttributes, loadedCustomAttributes); - } - internal void LoadCustomAttributesFilterExtensions(EntityHandle token, ref ImmutableArray customAttributes) { diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index b32dbc1a67287..1822ceb31f07b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -1219,15 +1219,19 @@ protected static void AddRequiredMembersMarkerAttributes(ref ArrayBuilder? references = null, CSharpParseOptions? parseOptions = null, CSharpCompilationOptions? options = null, string? assemblyName = null, TargetFramework targetFramework = TargetFramework.Standard) - => CreateCompilation(new[] { source, RequiredMemberAttribute, SetsRequiredMembersAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework); + => CreateCompilation(new[] { source, RequiredMemberAttribute, SetsRequiredMembersAttribute, CompilerFeatureRequiredAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework); private Compilation CreateVisualBasicCompilationWithRequiredMembers(string source) => CreateVisualBasicCompilation(new[] { source, RequiredMemberAttributeVB }); @@ -89,18 +89,35 @@ private static void AssertTypeRequiredMembersInvariants(ModuleSymbol module, Nam { Assert.True(type.HasAnyRequiredMembers); + var peModule = module as PEModuleSymbol; foreach (var ctor in type.GetMembers().Where(m => m is MethodSymbol { MethodKind: MethodKind.Constructor })) { var ctorAttributes = ctor.GetAttributes(); - if (ctorAttributes.Any(attr => attr.AttributeClass.ToTestDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute")) - { - Assert.DoesNotContain(ctorAttributes, attr => attr.AttributeClass.ToTestDisplayString() == "System.ObsoleteAttribute"); - } - else if (module is not SourceModuleSymbol) + + // Attributes should be filtered out when loaded from metadata, and are only added during emit in source + Assert.DoesNotContain(ctorAttributes, attr => attr.AttributeClass.ToTestDisplayString() is "System.ObsoleteAttribute" or "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute"); + + if (peModule is not null) { - Assert.Contains(ctorAttributes, attr => - attr.AttributeConstructor.ToTestDisplayString() == "System.ObsoleteAttribute..ctor(System.String message, System.Boolean error)" - && attr.ConstructorArguments.ToArray() is [{ ValueInternal: PEModule.RequiredMembersMarker }, { ValueInternal: true }]); + var peMethod = (PEMethodSymbol)ctor; + var decoder = new MetadataDecoder(peModule, peMethod); + var obsoleteAttribute = peModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(peMethod.Handle, decoder, ignoreByRefLikeMarker: false, ignoreRequiredMemberMarker: false); + string? unsupportedCompilerFeatureToken = peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peMethod.Handle, decoder, CompilerFeatureRequiredFeatures.None); + + if (ctorAttributes.Any(attr => attr.AttributeClass.ToTestDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute")) + { + Assert.Null(obsoleteAttribute); + Assert.Null(unsupportedCompilerFeatureToken); + } + else + { + Assert.NotNull(obsoleteAttribute); + Assert.Equal(PEModule.RequiredMembersMarker, obsoleteAttribute.Message); + Assert.True(obsoleteAttribute.IsError); + + Assert.Equal(nameof(CompilerFeatureRequiredFeatures.RequiredMembers), unsupportedCompilerFeatureToken); + Assert.Null(peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peMethod.Handle, decoder, CompilerFeatureRequiredFeatures.RequiredMembers)); + } } } } @@ -386,12 +403,14 @@ class required {} [Fact] public void MissingRequiredMemberAttribute() { - var comp = CreateCompilation(@" + var comp = CreateCompilationWithRequiredMembers(@" class C { public required int I { get; set; } }"); + comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_RequiredMemberAttribute); + // (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor' // class C var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7); @@ -402,25 +421,56 @@ class C [Fact] public void MissingRequiredMemberAttributeCtor() { - var comp = CreateCompilation(@" + var comp = CreateCompilationWithRequiredMembers(@" class C { public required int I { get; set; } } +"); -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class RequiredMemberAttribute : Attribute + comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_RequiredMemberAttribute__ctor); + + // (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor' + // class C + var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7); + comp.VerifyDiagnostics(expected); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact] + public void MissingCompilerFeatureRequiredAttribute() { - public RequiredMemberAttribute(int i) {} + var comp = CreateCompilationWithRequiredMembers(@" +class C +{ + public required int I { get; set; } +}"); + + comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute); + + // (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute..ctor' + // class C + var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", ".ctor").WithLocation(2, 7); + + comp.VerifyDiagnostics(expected); + comp.VerifyEmitDiagnostics(expected); } + + [Fact] + public void MissingCompilerFeatureRequiredAttributeCtor() + { + var comp = CreateCompilationWithRequiredMembers(@" +class C +{ + public required int I { get; set; } } "); - // (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor' + comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor); + + // (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute..ctor' // class C - var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7); + var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", ".ctor").WithLocation(2, 7); comp.VerifyDiagnostics(expected); comp.VerifyEmitDiagnostics(expected); } @@ -3116,7 +3166,7 @@ public class RequiredMemberAttribute : Attribute } "; - var comp = CreateCompilation(code); + var comp = CreateCompilation(new[] { code, CompilerFeatureRequiredAttribute }); comp.VerifyDiagnostics( // (4,2): error CS9506: Required member 'RequiredMemberAttribute.P' must be set in the object initializer or attribute constructor. // [RequiredMember] @@ -3140,7 +3190,7 @@ public class RequiredMemberAttribute : Attribute } "; - var comp = CreateCompilation(code); + var comp = CreateCompilation(new[] { code, CompilerFeatureRequiredAttribute }); comp.VerifyDiagnostics( // (6,6): error CS9506: Required member 'RequiredMemberAttribute.P' must be set in the object initializer or attribute constructor. // [RequiredMember] @@ -3183,7 +3233,7 @@ static void M() } "; - var comp = CreateCompilation(new[] { code, RequiredMemberAttribute }); + var comp = CreateCompilation(new[] { code, RequiredMemberAttribute, CompilerFeatureRequiredAttribute }); comp.VerifyDiagnostics(); }