Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add [MemberNotNull] and [MemberNotNullWhen] support #85

Closed
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public CustomAttribute AttributeUsage(AttributeTargets validOn, bool? allowMulti
return customAttribute;
}

public CustomAttribute ParamArray()
{
MethodDefinition constructor = _wellKnownTypes.SystemParamArrayAttribute.Resolve().Methods.Single(method => method.IsConstructor && !method.IsStatic && method.Parameters.Count == 0);
var customAttribute = new CustomAttribute(_wellKnownTypes.Module.ImportReference(constructor));
return customAttribute;
}

public CustomAttribute ReferenceAssembly()
{
MethodDefinition constructor = _wellKnownTypes.SystemRuntimeCompilerServicesReferenceAssemblyAttribute.Value.Resolve().Methods.Single(method => method.IsConstructor && !method.IsStatic && method.Parameters.Count == 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// This was copied from https://github.com/dotnet/coreclr/blob/60f1e6265bd1039f023a82e0643b524d6aaf7845/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// This was copied from https://github.com/dotnet/runtime/blob/v5.0.0-rc.1.20451.14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// and updated to have the scope of the attributes be internal.

namespace System.Diagnostics.CodeAnalysis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

' This was copied from https://github.com/dotnet/coreclr/blob/60f1e6265bd1039f023a82e0643b524d6aaf7845/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
' This was copied from https://github.com/dotnet/runtime/blob/v5.0.0-rc.1.20451.14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
' and updated to have the scope of the attributes be internal.

Imports System
Expand Down Expand Up @@ -94,7 +94,7 @@ Namespace Global.System.Diagnostics.CodeAnalysis

''' <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
<AttributeUsage(AttributeTargets.Parameter, Inherited:=False)>
Friend NotInheritable Class DoesNotReturnAttribute
Friend NotInheritable Class DoesNotReturnIfAttribute
Inherits Attribute

''' <summary>Initializes the attribute with the specified parameter value.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// <auto-generated />
// 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.

// This was copied from https://github.com/dotnet/runtime/blob/v5.0.0-rc.1.20451.14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// and updated to have the scope of the attributes be internal.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
internal sealed class MemberNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with a field or property member.</summary>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullAttribute(string member) => Members = new[] { member };

/// <summary>Initializes the attribute with the list of field and property members.</summary>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullAttribute(params string[] members) => Members = members;

/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
}

/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
internal sealed class MemberNotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute(bool returnValue, string member)
{
ReturnValue = returnValue;
Members = new[] { member };
}

/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
{
ReturnValue = returnValue;
Members = members;
}

/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }

/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
' <auto-generated />
' 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.

' This was copied from https://github.com/dotnet/runtime/blob/v5.0.0-rc.1.20451.14/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
' and updated to have the scope of the attributes be internal.

Imports System
Imports System.Diagnostics

Namespace Global.System.Diagnostics.CodeAnalysis

''' <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
<AttributeUsage(AttributeTargets.Method Or AttributeTargets.Property, Inherited:=False, AllowMultiple:=True)>
Friend NotInheritable Class MemberNotNullAttribute
Inherits Attribute

''' <summary>Initializes the attribute with a field or property member.</summary>
''' <param name="member">
''' The field or property member that is promised to be not-null.
''' </param>
Public Sub New(member As String)
Me.Members = {member}
End Sub

''' <summary>Initializes the attribute with the list of field and property members.</summary>
''' <param name="members">
''' The list of field and property members that are promised to be not-null.
''' </param>
Public Sub New(ParamArray members As String())
Me.Members = members
End Sub

''' <summary>Gets field or property member names.</summary>
Public ReadOnly Property Members As String()
End Class

''' <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
<AttributeUsage(AttributeTargets.Method Or AttributeTargets.Property, Inherited:=False, AllowMultiple:=True)>
Friend NotInheritable Class MemberNotNullWhenAttribute
Inherits Attribute

''' <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
''' <param name="returnValue">
''' The return value condition. If the method returns this value, the associated parameter will not be null.
''' </param>
''' <param name="member">
''' The field or property member that is promised to be not-null.
''' </param>
Public Sub New(returnValue As Boolean, member As String)
Me.ReturnValue = returnValue
Me.Members = {member}
End Sub

''' <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
''' <param name="returnValue">
''' The return value condition. If the method returns this value, the associated parameter will not be null.
''' </param>
''' <param name="members">
''' The list of field and property members that are promised to be not-null.
''' </param>
Public Sub New(ReturnValue As Boolean, ParamArray members As String())
Me.ReturnValue = ReturnValue
Me.Members = members
End Sub

''' <summary>Gets the return value condition.</summary>
Public ReadOnly Property ReturnValue As Boolean

''' <summary>Gets field or property member names.</summary>
Public ReadOnly Property Members As String()
End Class

End Namespace
2 changes: 2 additions & 0 deletions TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ internal static void Main(SuppressibleLoggingHelper? log, string referenceAssemb
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisDoesNotReturnIfAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisMaybeNullAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisMaybeNullWhenAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisMemberNotNullAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisMemberNotNullWhenAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisNotNullAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisNotNullIfNotNullAttribute);
AddAttributeOfInterest(attributesOfInterest, wellKnownTypes.SystemDiagnosticsCodeAnalysisNotNullWhenAttribute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,33 @@
<CodeAnalysisRuleSet>..\TunnelVisionLabs.ReferenceAssemblyAnnotator.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>

<ItemGroup>
<Compile Remove="NullableAttributes.gen2.cs" />
</ItemGroup>

<ItemGroup>
<None Remove="NullableAttributes.gen2.vb" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.0" />
<PackageReference Include="Microsoft.Build.Framework" Version="16.0.461" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="16.0.461" />
</ItemGroup>

<ItemGroup>
<Content Include="NullableAttributes.gen2.vb">
<PackagePath>build</PackagePath>
<Pack>true</Pack>
</Content>
<Content Include="NullableAttributes.gen2.cs">
<PackagePath>build</PackagePath>
<Pack>true</Pack>
</Content>
<Content Include="TunnelVisionLabs.ReferenceAssemblyAnnotator.props" Pack="true" PackagePath="build" />
<Content Include="TunnelVisionLabs.ReferenceAssemblyAnnotator.targets" Pack="true" PackagePath="build" />
<Content Include="NullableAttributes.cs" Pack="true" PackagePath="build" />
<Content Include="NullableAttributes.vb" Pack="true" PackagePath="build" />
<Content Include="NullableAttributes.gen1.cs" Pack="true" PackagePath="build" />
<Content Include="NullableAttributes.gen1.vb" Pack="true" PackagePath="build" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,39 @@
<UsingTask TaskName="TunnelVisionLabs.ReferenceAssemblyAnnotator.AnnotatorBuildTask" AssemblyFile="$(ReferenceAssemblyAnnotatorBuildTaskPath)TunnelVisionLabs.ReferenceAssemblyAnnotator.dll" />

<PropertyGroup>
<_FrameworkIncludesNullableAttributes Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND '$(TargetFrameworkVersion.Substring(1))' &gt;= '3.0'">true</_FrameworkIncludesNullableAttributes>
<_FrameworkIncludesNullableAttributes Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard' AND '$(TargetFrameworkVersion.Substring(1))' &gt;= '2.1'">true</_FrameworkIncludesNullableAttributes>
<_FrameworkIncludesNullableAttributes Condition="'$(_FrameworkIncludesNullableAttributes)' == ''">false</_FrameworkIncludesNullableAttributes>
<_FrameworkIncludesGen1NullableAttributes Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '3.0'))">true</_FrameworkIncludesGen1NullableAttributes>
<_FrameworkIncludesGen1NullableAttributes Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))">true</_FrameworkIncludesGen1NullableAttributes>
<_FrameworkIncludesGen1NullableAttributes Condition="'$(_FrameworkIncludesGen1NullableAttributes)' == ''">false</_FrameworkIncludesGen1NullableAttributes>

<!-- Note: only .NET 5.0 and higher include the full set of nullable attributes. -->
<_FrameworkIncludesGen2NullableAttributes Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0'))">true</_FrameworkIncludesGen2NullableAttributes>
<_FrameworkIncludesGen2NullableAttributes Condition="'$(_FrameworkIncludesGen2NullableAttributes)' == ''">false</_FrameworkIncludesGen2NullableAttributes>

<GenerateGen1NullableAttributes Condition="'$(GenerateNullableAttributes)' == '' AND '$(_FrameworkIncludesGen1NullableAttributes)' != 'true'">true</GenerateGen1NullableAttributes>
<GenerateGen2NullableAttributes Condition="'$(GenerateNullableAttributes)' == '' AND '$(_FrameworkIncludesGen2NullableAttributes)' != 'true'">true</GenerateGen2NullableAttributes>

<Gen1NullableAttributesPath Condition="'$(Gen1NullableAttributesPath)' == ''">$(MSBuildThisFileDirectory)NullableAttributes.gen1$(DefaultLanguageSourceExtension)</Gen1NullableAttributesPath>
<Gen2NullableAttributesPath Condition="'$(Gen2NullableAttributesPath)' == ''">$(MSBuildThisFileDirectory)NullableAttributes.gen2$(DefaultLanguageSourceExtension)</Gen2NullableAttributesPath>
</PropertyGroup>

<GenerateNullableAttributes Condition="'$(GenerateNullableAttributes)' == '' AND '$(_FrameworkIncludesNullableAttributes)' != 'true'">true</GenerateNullableAttributes>
<ItemGroup Condition="'$(GenerateGen1NullableAttributes)' == 'true' AND Exists($(Gen1NullableAttributesPath))">
<Compile Include="$(Gen1NullableAttributesPath)" Visible="false" />

<NullableAttributesPath Condition="'$(NullableAttributesPath)' == ''">$(MSBuildThisFileDirectory)NullableAttributes$(DefaultLanguageSourceExtension)</NullableAttributesPath>
</PropertyGroup>
<!-- Workaround for https://github.com/dotnet/wpf/issues/810 -->
<_GeneratedCodeFiles Include="$(Gen1NullableAttributesPath)" Visible="false" Condition="'$(UseWPF)' == 'true'" />

<!-- Make sure the source file is embedded in PDB to support Source Link -->
<EmbeddedFiles Condition="'$(DebugType)' != 'none'" Include="$(Gen1NullableAttributesPath)" />
</ItemGroup>

<ItemGroup Condition="'$(GenerateNullableAttributes)' == 'true' AND Exists($(NullableAttributesPath))">
<Compile Include="$(NullableAttributesPath)" Visible="false" />
<ItemGroup Condition="'$(GenerateGen2NullableAttributes)' == 'true' AND Exists($(Gen2NullableAttributesPath))">
<Compile Include="$(Gen2NullableAttributesPath)" Visible="false" />

<!-- Workaround for https://github.com/dotnet/wpf/issues/810 -->
<_GeneratedCodeFiles Include="$(NullableAttributesPath)" Visible="false" Condition="'$(UseWPF)' == 'true'" />
<_GeneratedCodeFiles Include="$(Gen2NullableAttributesPath)" Visible="false" Condition="'$(UseWPF)' == 'true'" />

<!-- Make sure the source file is embedded in PDB to support Source Link -->
<EmbeddedFiles Condition="'$(DebugType)' != 'none'" Include="$(NullableAttributesPath)" />
<EmbeddedFiles Condition="'$(DebugType)' != 'none'" Include="$(Gen2NullableAttributesPath)" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
{
using System;
using Mono.Cecil;
using Mono.Cecil.Rocks;

internal partial class WellKnownTypes
{
private sealed class MemberNotNullAttributeProvidedType : ProvidedAttributeType
{
public MemberNotNullAttributeProvidedType()
: base("System.Diagnostics.CodeAnalysis", "MemberNotNullAttribute")
{
}

protected override void ImplementAttribute(ModuleDefinition module, TypeDefinition attribute, WellKnownTypes wellKnownTypes, CustomAttributeFactory attributeFactory)
{
var constructor1 = MethodFactory.Constructor(wellKnownTypes.TypeSystem);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the first time I play with Cecil, so you'll likely want to triple-check I'm not doing anything stupid 😄

constructor1.Parameters.Add(new ParameterDefinition("member", ParameterAttributes.None, wellKnownTypes.TypeSystem.String));
attribute.Methods.Add(constructor1);

var members = new ParameterDefinition("members", ParameterAttributes.None, wellKnownTypes.TypeSystem.String.MakeArrayType());
members.CustomAttributes.Add(attributeFactory.ParamArray());

var constructor2 = MethodFactory.Constructor(wellKnownTypes.TypeSystem);
constructor2.Parameters.Add(members);
attribute.Methods.Add(constructor2);

attribute.CustomAttributes.Add(attributeFactory.NullableContext(1));
attribute.CustomAttributes.Add(attributeFactory.Nullable(0));
attribute.CustomAttributes.Add(attributeFactory.AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, inherited: false, allowMultiple: true));
}
}
}
}
Loading