File: Attributes\AttributeTests_NullableContext.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Emit3\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    public class AttributeTests_NullableContext : CSharpTestBase
    {
        [Fact]
        public void EmptyProject()
        {
            var source = @"";
            var comp = CreateCompilation(source);
            var expected =
@"";
            AssertNullableAttributes(comp, expected);
        }
 
        [Fact]
        public void ExplicitAttribute_FromSource()
        {
            var source =
@"#nullable enable
public class Program
{
    public object F(object arg) => arg;
}";
            var comp = CreateCompilation(new[] { NullableContextAttributeDefinition, source });
            var expected =
@"Program
    [NullableContext(1)] System.Object! F(System.Object! arg)
        System.Object! arg
";
            AssertNullableAttributes(comp, expected);
        }
 
        [Fact]
        public void ExplicitAttribute_FromMetadata()
        {
            var comp = CreateCompilation(NullableContextAttributeDefinition);
            comp.VerifyDiagnostics();
            var ref0 = comp.EmitToImageReference();
 
            var source =
@"#nullable enable
public class Program
{
    public object F(object arg) => arg;
}";
            comp = CreateCompilation(source, references: new[] { ref0 });
            var expected =
@"Program
    [NullableContext(1)] System.Object! F(System.Object! arg)
        System.Object! arg
";
            AssertNullableAttributes(comp, expected);
        }
 
        [Fact]
        public void ExplicitAttribute_MissingSingleByteConstructor()
        {
            var source1 =
@"namespace System.Runtime.CompilerServices
{
    public sealed class NullableContextAttribute : Attribute
    {
    }
}";
            var source2 =
@"public class Program
{
    public object F(object arg) => arg;
}";
 
            // C#7
            var comp = CreateCompilation(new[] { source1, source2 }, parseOptions: TestOptions.Regular7);
            comp.VerifyEmitDiagnostics();
 
            // C#8, nullable disabled
            comp = CreateCompilation(new[] { source1, source2 }, options: WithNullableDisable());
            comp.VerifyEmitDiagnostics();
 
            // C#8, nullable enabled
            comp = CreateCompilation(new[] { source1, source2 }, options: WithNullableEnable());
            comp.VerifyEmitDiagnostics(
                // (3,19): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.NullableContextAttribute..ctor'
                //     public object F(object arg) => arg;
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "F").WithArguments("System.Runtime.CompilerServices.NullableContextAttribute", ".ctor").WithLocation(3, 19));
        }
 
        [Fact]
        public void ExplicitAttribute_ReferencedInSource()
        {
            var sourceAttribute =
@"namespace System.Runtime.CompilerServices
{
    internal class NullableContextAttribute : System.Attribute
    {
        internal NullableContextAttribute(byte b) { }
    }
}";
            var source =
@"#pragma warning disable 169
using System.Runtime.CompilerServices;
[assembly: NullableContext(0)]
[module: NullableContext(0)]
[NullableContext(0)]
class Program
{
    [NullableContext(0)]object F;
    [NullableContext(0)]static object M1() => throw null;
    [return: NullableContext(0)]static object M2() => throw null;
    static void M3([NullableContext(0)]object arg) { }
}";
 
            // C#7
            var comp = CreateCompilation(new[] { sourceAttribute, source }, parseOptions: TestOptions.Regular7);
            verifyDiagnostics(comp);
 
            // C#8
            comp = CreateCompilation(new[] { sourceAttribute, source });
            verifyDiagnostics(comp);
 
            static void verifyDiagnostics(CSharpCompilation comp)
            {
                comp.VerifyDiagnostics(
                    // (4,10): error CS8335: Do not use 'System.Runtime.CompilerServices.NullableContextAttribute'. This is reserved for compiler usage.
                    // [module: NullableContext(0)]
                    Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "NullableContext(0)").WithArguments("System.Runtime.CompilerServices.NullableContextAttribute").WithLocation(4, 10),
                    // (5,2): error CS8335: Do not use 'System.Runtime.CompilerServices.NullableContextAttribute'. This is reserved for compiler usage.
                    // [NullableContext(0)]
                    Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "NullableContext(0)").WithArguments("System.Runtime.CompilerServices.NullableContextAttribute").WithLocation(5, 2),
                    // (9,6): error CS8335: Do not use 'System.Runtime.CompilerServices.NullableContextAttribute'. This is reserved for compiler usage.
                    //     [NullableContext(0)]static object M1() => throw null;
                    Diagnostic(ErrorCode.ERR_ExplicitReservedAttr, "NullableContext(0)").WithArguments("System.Runtime.CompilerServices.NullableContextAttribute").WithLocation(9, 6));
            }
        }
 
        [Fact]
        public void ExplicitAttribute_WithNullableContext()
        {
            var sourceAttribute =
@"#nullable enable
namespace System.Runtime.CompilerServices
{
    public sealed class NullableContextAttribute : Attribute
    {
        private object _f1;
        private object _f2;
        private object _f3;
        public NullableContextAttribute(byte b)
        {
        }
    }
}";
            var comp = CreateCompilation(sourceAttribute);
            var ref0 = comp.EmitToImageReference();
            var expected =
@"[NullableContext(1)] [Nullable(0)] System.Runtime.CompilerServices.NullableContextAttribute
    NullableContextAttribute(System.Byte b)
        System.Byte b
";
            AssertNullableAttributes(comp, expected);
 
            var source =
@"#nullable enable
public class Program
{
    private object _f1;
    private object _f2;
    private object _f3;
}";
            comp = CreateCompilation(source, references: new[] { ref0 });
            expected =
@"[NullableContext(1)] [Nullable(0)] Program
    Program()
";
            AssertNullableAttributes(comp, expected);
        }
 
        [Fact]
        [WorkItem(36934, "https://github.com/dotnet/roslyn/issues/36934")]
        public void AttributeUsage()
        {
            var source =
@"#nullable enable
public class Program
{
    private object _f1;
    private object _f2;
    private object _f3;
}";
            var comp = CreateCompilation(source, options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All));
            CompileAndVerify(comp, symbolValidator: module =>
            {
                var attributeType = module.GlobalNamespace.GetMember<NamedTypeSymbol>("System.Runtime.CompilerServices.NullableContextAttribute");
                AttributeUsageInfo attributeUsage = attributeType.GetAttributeUsageInfo();
                Assert.False(attributeUsage.Inherited);
                Assert.False(attributeUsage.AllowMultiple);
                Assert.True(attributeUsage.HasValidAttributeTargets);
                var expectedTargets = AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Struct;
                Assert.Equal(expectedTargets, attributeUsage.ValidTargets);
            });
        }
 
        [Fact]
        public void MissingAttributeUsageAttribute()
        {
            var source =
@"#pragma warning disable 169
#pragma warning disable 414
#nullable enable
public class Program
{
    private object _f1 = null!;
    private object _f2 = null!;
    private object _f3 = null!;
}";
 
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute());
            comp.MakeTypeMissing(WellKnownType.System_AttributeUsageAttribute);
            comp.VerifyEmitDiagnostics(
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute..ctor'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", ".ctor").WithLocation(1, 1),
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute.AllowMultiple'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", "AllowMultiple").WithLocation(1, 1),
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute.Inherited'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", "Inherited").WithLocation(1, 1),
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute..ctor'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", ".ctor").WithLocation(1, 1),
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute.AllowMultiple'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", "AllowMultiple").WithLocation(1, 1),
                // error CS0656: Missing compiler required member 'System.AttributeUsageAttribute.Inherited'
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.AttributeUsageAttribute", "Inherited").WithLocation(1, 1));
        }
 
        /// <summary>
        /// Module-level NullableContextAttribute is ignored.
        /// </summary>
        [Fact]
        public void ExplicitAttribute_Module()
        {
            var source0 =
@"namespace System.Runtime.CompilerServices
{
    public sealed class NullableContextAttribute : Attribute
    {
        public NullableContextAttribute(byte b) { }
    }
}";
            var comp0 = CreateCompilation(source0);
            var ref0 = comp0.EmitToImageReference();
 
            var source1 =
@"Imports System.Runtime.CompilerServices
<Module: NullableContext(2)>
Public Class A
    Public Shared FA As Object
End Class
<NullableContext(2)>
Public Class B
    Public Shared FB As Object
End Class";
            var comp1 = CreateVisualBasicCompilation(source1, referencedAssemblies: TargetFrameworkUtil.GetReferences(TargetFramework.Standard).Concat(ref0));
            var ref1 = comp1.EmitToImageReference();
 
            var source2 =
@"#nullable enable
class Program
{
    static void Main()
    {
        A.FA.ToString();
        B.FB.ToString(); // warning
    }
}";
            var comp2 = CreateCompilation(source2, references: new[] { ref1 });
            comp2.VerifyDiagnostics(
                // (7,9): warning CS8602: Dereference of a possibly null reference.
                //         B.FB.ToString(); // warning
                Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "B.FB").WithLocation(7, 9));
        }
 
        [Fact]
        public void AttributeField()
        {
            var source =
@"#nullable enable
using System;
using System.Linq;
public class A
{
    private object _f1;
    private object _f2;
    private object _f3;
}
public class B
{
    private object? _f1;
    private object? _f2;
    private object? _f3;
}
class Program
{
    static void Main()
    {
        Console.WriteLine(GetAttributeValue(typeof(A)));
        Console.WriteLine(GetAttributeValue(typeof(B)));
    }
    static byte GetAttributeValue(Type type)
    {
        var attribute = type.GetCustomAttributes(false).Single(a => a.GetType().Name == ""NullableContextAttribute"");
        var field = attribute.GetType().GetField(""Flag"");
        return (byte)field.GetValue(attribute);
    }
}";
            var expectedOutput =
@"1
2";
            var expectedAttributes =
@"[NullableContext(1)] [Nullable(0)] A
    A()
[NullableContext(2)] [Nullable(0)] B
    B()
";
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8, options: TestOptions.DebugExe);
            CompileAndVerify(comp, expectedOutput: expectedOutput, symbolValidator: module => AssertNullableAttributes(module, expectedAttributes));
        }
 
        [Fact]
        public void MostCommonNullableValue()
        {
            Assert.Null(getMostCommonValue());
            Assert.Null(getMostCommonValue((byte?)null));
            Assert.Null(getMostCommonValue(null, null));
            Assert.Equal((byte)0, getMostCommonValue(0));
            Assert.Equal((byte)1, getMostCommonValue(1));
            Assert.Equal((byte)2, getMostCommonValue(2));
#if !DEBUG
            Assert.Throws<InvalidOperationException>(() => getMostCommonValue(3));
#endif
            Assert.Equal((byte)0, getMostCommonValue(0, 0));
            Assert.Equal((byte)0, getMostCommonValue(0, 1));
            Assert.Equal((byte)0, getMostCommonValue(1, 0));
            Assert.Equal((byte)1, getMostCommonValue(1, 1));
            Assert.Equal((byte)1, getMostCommonValue(1, 2));
            Assert.Equal((byte)1, getMostCommonValue(2, 1));
            Assert.Equal((byte)2, getMostCommonValue(2, 2));
#if !DEBUG
            Assert.Throws<InvalidOperationException>(() => getMostCommonValue(2, 3));
#endif
            Assert.Equal((byte)0, getMostCommonValue(0, null));
            Assert.Equal((byte)0, getMostCommonValue(null, 0));
            Assert.Equal((byte)1, getMostCommonValue(1, null));
            Assert.Equal((byte)1, getMostCommonValue(null, 1));
            Assert.Equal((byte)0, getMostCommonValue(0, 1, 2, null));
            Assert.Equal((byte)0, getMostCommonValue(null, 2, 1, 0));
            Assert.Equal((byte)1, getMostCommonValue(1, 2, null));
            Assert.Equal((byte)1, getMostCommonValue(null, 2, 1));
            Assert.Equal((byte)2, getMostCommonValue(null, 2, null));
            Assert.Equal((byte)0, getMostCommonValue(0, 1, 0));
            Assert.Equal((byte)0, getMostCommonValue(1, 0, 0));
            Assert.Equal((byte)1, getMostCommonValue(1, 0, 1));
            Assert.Equal((byte)1, getMostCommonValue(1, 2, 1));
            Assert.Equal((byte)2, getMostCommonValue(2, 2, 1));
 
            static byte? getMostCommonValue(params byte?[] values)
            {
                var builder = new MostCommonNullableValueBuilder();
                foreach (var value in values)
                {
                    builder.AddValue(value);
                }
                return builder.MostCommonValue;
            }
        }
 
        [Fact]
        public void GetCommonNullableValue()
        {
            Assert.Null(getCommonValue());
            Assert.Equal((byte)0, getCommonValue(0));
            Assert.Equal((byte)1, getCommonValue(1));
            Assert.Equal((byte)2, getCommonValue(2));
            Assert.Equal((byte)3, getCommonValue(3));
            Assert.Equal((byte)0, getCommonValue(0, 0));
            Assert.Null(getCommonValue(0, 1));
            Assert.Null(getCommonValue(1, 0));
            Assert.Equal((byte)1, getCommonValue(1, 1));
            Assert.Null(getCommonValue(1, 2));
            Assert.Equal((byte)2, getCommonValue(2, 2));
            Assert.Null(getCommonValue(0, 1, 0));
            Assert.Null(getCommonValue(1, 0, 1));
            Assert.Null(getCommonValue(2, 2, 1));
            Assert.Equal((byte)3, getCommonValue(3, 3, 3));
 
            static byte? getCommonValue(params byte[] values)
            {
                var builder = ArrayBuilder<byte>.GetInstance();
                builder.AddRange(values);
                var result = MostCommonNullableValueBuilder.GetCommonValue(builder);
                builder.Free();
                return result;
            }
        }
 
        private void AssertNullableAttributes(CSharpCompilation comp, string expected)
        {
            CompileAndVerify(comp, symbolValidator: module => AssertNullableAttributes(module, expected));
        }
 
        private static void AssertNullableAttributes(ModuleSymbol module, string expected)
        {
            var actual = NullableAttributesVisitor.GetString((PEModuleSymbol)module);
            AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, actual);
        }
    }
}