diff --git a/src/Nullean.PublicApi.Diff/ApiComparison.cs b/src/Nullean.PublicApi.Diff/ApiComparison.cs
index db0fb17..d964851 100644
--- a/src/Nullean.PublicApi.Diff/ApiComparison.cs
+++ b/src/Nullean.PublicApi.Diff/ApiComparison.cs
@@ -822,6 +822,12 @@ private static bool Same(ApiTypeReference a, ApiTypeReference b, Sameness samene
a = Runtime(a);
b = Runtime(b);
}
+ else
+ {
+ // nint and IntPtr are one type to C# as well, since C# 11: only how a file spelled it differs
+ a = a is ApiNativeIntegerType ? Runtime(a) with { IsNullable = a.IsNullable } : a;
+ b = b is ApiNativeIntegerType ? Runtime(b) with { IsNullable = b.IsNullable } : b;
+ }
switch (a, b)
{
case (ApiNamedType x, ApiNamedType y):
diff --git a/src/Nullean.PublicApi/ApiReader.cs b/src/Nullean.PublicApi/ApiReader.cs
index ba40a5f..0821577 100644
--- a/src/Nullean.PublicApi/ApiReader.cs
+++ b/src/Nullean.PublicApi/ApiReader.cs
@@ -65,7 +65,47 @@ public sealed class ApiReader
private ApiReader(MetadataReader reader)
{
_reader = reader;
- _types = new TypeReferenceProvider(reader);
+ _types = new TypeReferenceProvider(reader, NumericIntPtr(reader));
+ }
+
+ ///
+ /// Whether the assembly was built for a runtime where IntPtr is nint: one targeting .NET 7 or later, or a core
+ /// library that declares RuntimeFeature.NumericIntPtr itself.
+ ///
+ private static bool NumericIntPtr(MetadataReader reader)
+ {
+ foreach (var handle in reader.TypeDefinitions)
+ {
+ var type = reader.GetTypeDefinition(handle);
+ if (reader.StringComparer.Equals(type.Name, "RuntimeFeature") && reader.StringComparer.Equals(type.Namespace, "System.Runtime.CompilerServices"))
+ return type.GetFields().Any(f => reader.StringComparer.Equals(reader.GetFieldDefinition(f).Name, "NumericIntPtr"));
+ }
+ var probe = new TypeReferenceProvider(reader);
+ foreach (var handle in reader.GetAssemblyDefinition().GetCustomAttributes())
+ {
+ var attribute = reader.GetCustomAttribute(handle);
+ if (attribute.Constructor.Kind != HandleKind.MemberReference)
+ continue;
+ var parent = reader.GetMemberReference((MemberReferenceHandle)attribute.Constructor).Parent;
+ if (parent.Kind != HandleKind.TypeReference || probe.FullName(parent) != "System.Runtime.Versioning.TargetFrameworkAttribute")
+ continue;
+ var value = attribute.DecodeValue(probe);
+ if (value.FixedArguments is [{ Value: string framework }, ..])
+ return IsNet7OrLater(framework);
+ }
+ return false;
+ }
+
+ /// .NETCoreApp,Version=v8.0 and the like.
+ private static bool IsNet7OrLater(string framework)
+ {
+ const string prefix = ".NETCoreApp,Version=v";
+#pragma warning disable CA1846 // netstandard2.0 cannot parse a span
+ return framework.StartsWith(prefix, StringComparison.Ordinal) && Version.TryParse(
+ framework.Substring(prefix.Length),
+ out var version
+ ) && version.Major >= 7;
+#pragma warning restore CA1846
}
/// Reads the assembly at .
diff --git a/src/Nullean.PublicApi/Signatures.cs b/src/Nullean.PublicApi/Signatures.cs
index 9648c3c..757eceb 100644
--- a/src/Nullean.PublicApi/Signatures.cs
+++ b/src/Nullean.PublicApi/Signatures.cs
@@ -18,13 +18,23 @@ internal readonly record struct TypeContext(ImmutableArray TypeParameter
}
/// Decodes signatures and attribute blobs into s.
+/// The assembly's metadata.
+///
+/// Whether IntPtr is nint: since C# 11, on a runtime that says so (.NET 7 and later), they are the same type and the
+/// compiler no longer marks which one a signature meant.
+///
internal sealed class TypeReferenceProvider(
- MetadataReader reader
+ MetadataReader reader,
+ bool numericIntPtr = false
) : ISignatureTypeProvider, ICustomAttributeTypeProvider
{
private readonly Dictionary _named = [];
- public ApiTypeReference GetPrimitiveType(PrimitiveTypeCode typeCode) => Primitive(typeCode);
+ private static readonly ApiNativeIntegerType Nint = new(false);
+ private static readonly ApiNativeIntegerType Nuint = new(true);
+
+ public ApiTypeReference GetPrimitiveType(PrimitiveTypeCode typeCode) =>
+ numericIntPtr && typeCode is PrimitiveTypeCode.IntPtr ? Nint : numericIntPtr && typeCode is PrimitiveTypeCode.UIntPtr ? Nuint : Primitive(typeCode);
// the records are immutable, so every reference to int or string shares one, made once
private static readonly ApiNamedType?[] Primitives = BuildPrimitives();
diff --git a/tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.eee471f4.verified.txt b/tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.34d2702d.verified.txt
similarity index 93%
rename from tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.eee471f4.verified.txt
rename to tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.34d2702d.verified.txt
index 12cbf7e..185c632 100644
--- a/tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.eee471f4.verified.txt
+++ b/tests/Nullean.PublicApi.Tests/.snapshots/WriterTests/Writes.signatures.34d2702d.verified.txt
@@ -1,6 +1,6 @@
assembly Lib
using System: IComparable<>
-using struct System: DateTime, DayOfWeek, IntPtr, UIntPtr
+using struct System: DateTime, DayOfWeek
using System.Collections.Generic: Dictionary<,>, List<>
namespace Lib
@@ -14,7 +14,7 @@ namespace Lib
override bool Equals(object? obj)
T Generic(U value) where T : struct, IComparable where U : class?, new()
override int GetHashCode()
- IntPtr Native(UIntPtr value)
+ nint Native(nuint value)
List<(int a, (string b, int c) inner)> NestedTuples()
void NotNull(T value) where T : notnull
void Nullability(string? a, List b, Dictionary?> c, string?[]? d, int[][,] jagged)
diff --git a/tests/Nullean.PublicApi.Tests/ComparisonTests.cs b/tests/Nullean.PublicApi.Tests/ComparisonTests.cs
index 156b192..71031ca 100644
--- a/tests/Nullean.PublicApi.Tests/ComparisonTests.cs
+++ b/tests/Nullean.PublicApi.Tests/ComparisonTests.cs
@@ -63,6 +63,14 @@ public void Configured_not_an_extension_point_does_not_count()
Report(result).Should().MatchSnapshot();
}
+ [Test]
+ public void IntPtr_read_as_nint_is_no_change()
+ {
+ const string source = "public class A { public System.IntPtr M(System.UIntPtr value) => default; }";
+ var result = Compare(source, "[assembly: System.Runtime.Versioning.TargetFramework(\".NETCoreApp,Version=v11.0\")]\n" + source);
+ result.Changes.Should().BeEmpty();
+ }
+
[Test]
public void A_break_below_one_asks_for_a_minor_version()
{
diff --git a/tests/Nullean.PublicApi.Tests/WriterTests.cs b/tests/Nullean.PublicApi.Tests/WriterTests.cs
index ec64893..b037abf 100644
--- a/tests/Nullean.PublicApi.Tests/WriterTests.cs
+++ b/tests/Nullean.PublicApi.Tests/WriterTests.cs
@@ -74,6 +74,8 @@ protected class Family { }
yield return ("signatures", """
using System;
using System.Collections.Generic;
+ // built for .NET 11, where IntPtr is nint, as the SDK says in every build
+ [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v11.0")]
namespace Lib;
public class Signatures
{