diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs index bd3923de178d6..143579fcb3c3b 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs @@ -266,7 +266,12 @@ private void CreateEmbeddedAttributesIfNeeded(DiagnosticBag diagnostics) { EmbeddableAttributes needsAttributes = GetNeedsGeneratedAttributes(); - if (needsAttributes == 0) + if (ShouldEmitNullablePublicOnlyAttribute() && + Compilation.CheckIfAttributeShouldBeEmbedded(EmbeddableAttributes.NullablePublicOnlyAttribute, diagnostics, Location.None)) + { + needsAttributes |= EmbeddableAttributes.NullablePublicOnlyAttribute; + } + else if (needsAttributes == 0) { return; } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 47c389c71b578..098597bb4d1d9 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -58,7 +58,7 @@ private EmbeddableAttributes GetNeedsGeneratedAttributesInternal() return (EmbeddableAttributes)_needsGeneratedAttributes | Compilation.GetNeedsGeneratedAttributes(); } - internal void SetNeedsGeneratedAttributes(EmbeddableAttributes attributes) + private void SetNeedsGeneratedAttributes(EmbeddableAttributes attributes) { Debug.Assert(!_needsGeneratedAttributes_IsFrozen); ThreadSafeFlagOperations.Set(ref _needsGeneratedAttributes, (int)attributes); @@ -1542,6 +1542,13 @@ internal virtual SynthesizedAttributeData SynthesizeNullableContextAttribute(Imm return Compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_NullableContextAttribute__ctor, arguments, isOptionalUse: true); } + internal bool ShouldEmitNullablePublicOnlyAttribute() + { + // No need to look at this.GetNeedsGeneratedAttributes() since those bits are + // only set for members generated by the rewriter which are not public. + return Compilation.GetUsesNullableAttributes() && Compilation.EmitNullablePublicOnly; + } + internal virtual SynthesizedAttributeData SynthesizeNullablePublicOnlyAttribute(ImmutableArray arguments) { // For modules, this attribute should be present. Only assemblies generate and embed this type. diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs index 365e1c270f0ad..69f16521c0910 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs @@ -28,6 +28,7 @@ public partial class CSharpCompilation /// private Symbol[] _lazyWellKnownTypeMembers; + private bool _usesNullableAttributes; private int _needsGeneratedAttributes; private bool _needsGeneratedAttributes_IsFrozen; @@ -42,12 +43,24 @@ internal EmbeddableAttributes GetNeedsGeneratedAttributes() return (EmbeddableAttributes)_needsGeneratedAttributes; } - internal void SetNeedsGeneratedAttributes(EmbeddableAttributes attributes) + private void SetNeedsGeneratedAttributes(EmbeddableAttributes attributes) { Debug.Assert(!_needsGeneratedAttributes_IsFrozen); ThreadSafeFlagOperations.Set(ref _needsGeneratedAttributes, (int)attributes); } + internal bool GetUsesNullableAttributes() + { + _needsGeneratedAttributes_IsFrozen = true; + return _usesNullableAttributes; + } + + private void SetUsesNullableAttributes() + { + Debug.Assert(!_needsGeneratedAttributes_IsFrozen); + _usesNullableAttributes = true; + } + /// /// Lookup member declaration in well known type used by this Compilation. /// @@ -456,6 +469,12 @@ private void EnsureEmbeddableAttributeExists(EmbeddableAttributes attribute, Dia { SetNeedsGeneratedAttributes(attribute); } + + if ((attribute & (EmbeddableAttributes.NullableAttribute | EmbeddableAttributes.NullableContextAttribute)) != 0 && + modifyCompilation) + { + SetUsesNullableAttributes(); + } } internal void EnsureIsReadOnlyAttributeExists(DiagnosticBag diagnostics, Location location, bool modifyCompilation) @@ -483,11 +502,6 @@ internal void EnsureNullableContextAttributeExists(DiagnosticBag diagnostics, Lo EnsureEmbeddableAttributeExists(EmbeddableAttributes.NullableContextAttribute, diagnostics, location, modifyCompilation); } - internal void EnsureNullablePublicOnlyAttributeExists(DiagnosticBag diagnostics, Location location, bool modifyCompilation) - { - EnsureEmbeddableAttributeExists(EmbeddableAttributes.NullablePublicOnlyAttribute, diagnostics, location, modifyCompilation); - } - internal bool CheckIfAttributeShouldBeEmbedded(EmbeddableAttributes attribute, DiagnosticBag diagnosticsOpt, Location locationOpt) { switch (attribute) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs index 4318e458024ac..1f507d2100750 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs @@ -255,18 +255,6 @@ internal override void ForceComplete(SourceLocation locationOpt, CancellationTok _state.SpinWaitComplete(CompletionPart.FinishValidatingReferencedAssemblies, cancellationToken); break; - case CompletionPart.StartMemberChecks: - case CompletionPart.FinishMemberChecks: - if (_state.NotePartComplete(CompletionPart.StartMemberChecks)) - { - var diagnostics = DiagnosticBag.GetInstance(); - AfterMembersChecks(diagnostics); - AddDeclarationDiagnostics(diagnostics); - diagnostics.Free(); - _state.NotePartComplete(CompletionPart.FinishMemberChecks); - } - break; - case CompletionPart.MembersCompleted: this.GlobalNamespace.ForceComplete(locationOpt, cancellationToken); @@ -533,24 +521,6 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu } } - private bool EmitNullablePublicOnlyAttribute - { - get - { - var compilation = DeclaringCompilation; - return compilation.EmitNullablePublicOnly && - compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); - } - } - - private void AfterMembersChecks(DiagnosticBag diagnostics) - { - if (EmitNullablePublicOnlyAttribute) - { - DeclaringCompilation.EnsureNullablePublicOnlyAttributeExists(diagnostics, location: NoLocation.Singleton, modifyCompilation: true); - } - } - internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) { base.AddSynthesizedAttributes(moduleBuilder, ref attributes); @@ -566,7 +536,7 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r } } - if (EmitNullablePublicOnlyAttribute) + if (moduleBuilder.ShouldEmitNullablePublicOnlyAttribute()) { var includesInternals = ImmutableArray.Create( new TypedConstant(compilation.GetSpecialType(SpecialType.System_Boolean), TypedConstantKind.Primitive, _assemblySymbol.InternalsAreVisible)); diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs index bcc0d1f85147f..78002f0a5bc21 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs @@ -154,6 +154,75 @@ static void F(object? x, object?[] y) { } Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "object?[] y").WithArguments("System.Runtime.CompilerServices.NullableAttribute", ".ctor").WithLocation(10, 30)); } + [Fact] + public void AttributeFromInternalsVisibleTo_01() + { + var sourceA = +@"using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo(""B"")] +#nullable enable +class A +{ + object? F = null; +}"; + var options = TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All); + var comp = CreateCompilation(sourceA, assemblyName: "A", options: options); + CompileAndVerify(comp, symbolValidator: m => CheckAttribute(m.GlobalNamespace.GetMember("A.F").GetAttributes().Single(), "A")); + var refA = comp.EmitToImageReference(); + + var sourceB = +@"#nullable enable +class B +{ + object? G = new A(); +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }, assemblyName: "B", options: options); + CompileAndVerify(comp, symbolValidator: m => CheckAttribute(m.GlobalNamespace.GetMember("B.G").GetAttributes().Single(), "B")); + } + + [Fact] + public void AttributeFromInternalsVisibleTo_02() + { + var sourceAttribute = +@"namespace System.Runtime.CompilerServices +{ + internal sealed class NullableAttribute : Attribute + { + public NullableAttribute(byte b) { } + public NullableAttribute(byte[] b) { } + } +}"; + var sourceA = +@"using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo(""B"")] +#nullable enable +class A +{ + object? F = null; +}"; + var options = TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All); + var comp = CreateCompilation(new[] { sourceAttribute, sourceA }, assemblyName: "A", options: options); + CompileAndVerify(comp, symbolValidator: m => CheckAttribute(m.GlobalNamespace.GetMember("A.F").GetAttributes().Single(), "A")); + var refA = comp.EmitToImageReference(); + + var sourceB = +@"#nullable enable +class B +{ + object? G = new A(); +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }, assemblyName: "B", options: options); + CompileAndVerify(comp, symbolValidator: m => CheckAttribute(m.GlobalNamespace.GetMember("B.G").GetAttributes().Single(), "A")); + } + + private static void CheckAttribute(CSharpAttributeData attribute, string assemblyName) + { + var attributeType = attribute.AttributeClass; + Assert.Equal("System.Runtime.CompilerServices", attributeType.ContainingNamespace.QualifiedName); + Assert.Equal("NullableAttribute", attributeType.Name); + Assert.Equal(assemblyName, attributeType.ContainingAssembly.Name); + } + [Fact] public void NullableAttribute_MissingByte() { diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_NullablePublicOnly.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_NullablePublicOnly.cs index 07b9a02617f5b..70b71e7d09802 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_NullablePublicOnly.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_NullablePublicOnly.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; @@ -25,12 +24,13 @@ public class B : A }"; var options = WithNonNullTypesTrue().WithMetadataImportOptions(MetadataImportOptions.All); var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullablePublicOnlyAttributeDefinition, source }; - var comp = CreateCompilation(new[] { NullablePublicOnlyAttributeDefinition, source }, options: options, parseOptions: parseOptions); - CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); - comp = CreateCompilation(new[] { NullablePublicOnlyAttributeDefinition, source }, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); - CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: true, publicDefinition: true)); } [Fact] @@ -51,10 +51,10 @@ public class B : A var parseOptions = TestOptions.Regular8; comp = CreateCompilation(source, references: new[] { ref1 }, options: options, parseOptions: parseOptions); - CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: false, includesAttributeUse: false, publicDefinition: true)); comp = CreateCompilation(source, references: new[] { ref1 }, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); - CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: false, includesAttributeUse: true, publicDefinition: true)); } [Fact] @@ -97,7 +97,7 @@ public void EmptyProject() CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); comp = CreateCompilation(source, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); - CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); } [Fact] @@ -157,7 +157,7 @@ class B : A CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); comp = CreateCompilation(source, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); - CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); } [Fact] @@ -167,6 +167,26 @@ public void NullableDisabled() @"public class A { } +public class B : A +{ +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + + var comp = CreateCompilation(source, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + + comp = CreateCompilation(source, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + } + + [Fact] + public void NullableDisabled_Annotation() + { + var source = +@"public class A +{ +} public class B : A { }"; @@ -180,20 +200,182 @@ public class B : A CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); } - private static void AssertNoNullablePublicOnlyAttribute(ModuleSymbol module) + [Fact] + public void NullableDisabled_OtherNullableAttributeDefinitions() { - AssertAttributes(module.GetAttributes()); + var source = +@"public class Program +{ +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); } - private static void AssertNullablePublicOnlyAttribute(ModuleSymbol module) + [Fact] + public void NullableEnabled_MethodBodyOnly() + { + var source0 = +@"#nullable enable +public class A +{ + public static object? F; + public static void M(object o) { } +}"; + var comp = CreateCompilation(source0); + comp.VerifyDiagnostics(); + var ref0 = comp.EmitToImageReference(); + + var source1 = +@"public class B +{ + static void Main() + { +#nullable enable + A.M(A.F); + } +}"; + comp = CreateCompilation( + source1, + references: new[] { ref0 }, + options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All), + parseOptions: TestOptions.Regular8.WithFeature("nullablePublicOnly")); + comp.VerifyDiagnostics( + // (6,13): warning CS8604: Possible null reference argument for parameter 'o' in 'void A.M(object o)'. + // A.M(A.F); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "A.F").WithArguments("o", "void A.M(object o)").WithLocation(6, 13)); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + } + + [Fact] + public void NullableEnabled_AllNullableAttributeDefinitions_01() + { + var source = +@"#nullable enable +public class Program +{ +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, NullablePublicOnlyAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); + } + + [Fact] + public void NullableEnabled_AllNullableAttributeDefinitions_02() + { + var source = +@"#nullable enable +public class Program +{ + public object F() => null!; +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, NullablePublicOnlyAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: true, publicDefinition: true)); + } + + [Fact] + public void NullableEnabled_OtherNullableAttributeDefinitions_01() + { + var source = +@"#nullable enable +public class Program +{ +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + } + + [Fact] + public void NullableEnabled_OtherNullableAttributeDefinitions_02() + { + var source = +@"#nullable enable +public class Program +{ + public object F() => null!; +}"; + var options = WithNonNullTypesFalse().WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: AssertNullablePublicOnlyAttribute); + } + + [Fact] + public void LocalFunctionReturnType_SynthesizedAttributeDefinitions() { - AssertAttributes(module.GetAttributes(), "System.Runtime.CompilerServices.NullablePublicOnlyAttribute"); + var source = +@"public class Program +{ + static void Main() + { +#nullable enable + object? L() => null; + L(); + } +}"; + var options = TestOptions.ReleaseExe.WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + + var comp = CreateCompilation(source, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); + + comp = CreateCompilation(source, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: AssertNoNullablePublicOnlyAttribute); } - private static void AssertAttributes(ImmutableArray attributes, params string[] expectedNames) + [Fact] + public void LocalFunctionReturnType_ExplicitAttributeDefinitions() { - var actualNames = attributes.Select(a => a.AttributeClass.ToTestDisplayString()).ToArray(); - AssertEx.SetEqual(actualNames, expectedNames); + var source = +@"public class Program +{ + static void Main() + { +#nullable enable + object? L() => null; + L(); + } +}"; + var options = TestOptions.ReleaseExe.WithMetadataImportOptions(MetadataImportOptions.All); + var parseOptions = TestOptions.Regular8; + CSharpTestSource sources = new[] { NullableAttributeDefinition, NullableContextAttributeDefinition, NullablePublicOnlyAttributeDefinition, source }; + + var comp = CreateCompilation(sources, options: options, parseOptions: parseOptions); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); + + comp = CreateCompilation(sources, options: options, parseOptions: parseOptions.WithFeature("nullablePublicOnly")); + CompileAndVerify(comp, symbolValidator: m => AssertNullablePublicOnlyAttribute(m, includesAttributeDefinition: true, includesAttributeUse: false, publicDefinition: true)); } [Fact] @@ -308,9 +490,9 @@ public void AttributeField() using System; using System.Linq; using System.Reflection; -class Program +public class Program { - static void Main() + public static void Main(string[] args) { var value = GetAttributeValue(typeof(Program).Assembly.Modules.First()); Console.WriteLine(value == null ? """" : value.ToString()); @@ -334,6 +516,47 @@ static void Main() CompileAndVerify(new[] { source, sourceIVTs }, parseOptions: parseOptions.WithFeature("nullablePublicOnly"), expectedOutput: "True"); } + private static void AssertNoNullablePublicOnlyAttribute(ModuleSymbol module) + { + AssertNullablePublicOnlyAttribute(module, includesAttributeDefinition: false, includesAttributeUse: false, publicDefinition: false); + } + + private static void AssertNullablePublicOnlyAttribute(ModuleSymbol module) + { + AssertNullablePublicOnlyAttribute(module, includesAttributeDefinition: true, includesAttributeUse: true, publicDefinition: false); + } + + private static void AssertNullablePublicOnlyAttribute(ModuleSymbol module, bool includesAttributeDefinition, bool includesAttributeUse, bool publicDefinition) + { + const string attributeName = "System.Runtime.CompilerServices.NullablePublicOnlyAttribute"; + var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember(attributeName); + var attribute = module.GetAttributes().SingleOrDefault(); + if (includesAttributeDefinition) + { + Assert.NotNull(type); + } + else + { + Assert.Null(type); + if (includesAttributeUse) + { + type = attribute.AttributeClass; + } + } + if (!(type is null)) + { + Assert.Equal(publicDefinition ? Accessibility.Public : Accessibility.Internal, type.DeclaredAccessibility); + } + if (includesAttributeUse) + { + Assert.Equal(type, attribute.AttributeClass); + } + else + { + Assert.Null(attribute); + } + } + private void AssertNullableAttributes(CSharpCompilation comp, string expected) { CompileAndVerify(comp, symbolValidator: module => AssertNullableAttributes(module, expected));