Skip to content

Conversation

@smasher164
Copy link
Contributor

Previously, ApiCompat would consider IntPtr and nint (as well as UIntPtr and nuint) as different types, emitting diagnostics where there shouldn't have been. This change updates the display string used by the SymbolComparer to replace all instances of IntPtr with nint and UIntPtr with nuint.

Fixes #25566

internal static string ToComparisonDisplayString(this ISymbol symbol) => symbol.ToDisplayString(Format);
internal static string ToComparisonDisplayString(this ISymbol symbol) =>
symbol.ToDisplayString(Format)
.Replace("System.IntPtr", "nint") // Treat IntPtr and nint as the same
Copy link
Member

Choose a reason for hiding this comment

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

Will this create confusing diagnostics when someone is actually using IntPtr? Say I had an API that used IntPtr and I removed it on the right, what diagnostic do we emit?

Copy link
Member

Choose a reason for hiding this comment

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

This code path doesn't impact the Target that is being emitted by rules and APICompat. The ToComparisonDisplayString helper function is only used by the mapping infrastructure to map members together.

I.e.

doesn't use that function to for encoding the target.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This string is only used for comparing symbols, so the diagnostic that is emitted in that case is still "CP0002 : Member 'CompatTests.First.H(System.IntPtr)' exists on left but not on right".

Copy link
Member

Choose a reason for hiding this comment

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

If we do end up having to normalize this in comparisons, it might be better to do here:

string.Equals(x != null ? GetKey(x) : null, y != null ? GetKey(y) : null, StringComparison.OrdinalIgnoreCase);

And only after the equality check fails, and only if references are not provided. I'd like us to first see if we can get roslyn to behave as if the feature is supported.

Copy link
Member

Choose a reason for hiding this comment

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

IMO the ToComparisonDisplayString method is the right place as the name already suggests that it calculates a display string for comparison.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Testing this on .NET 472 and .NET 7,

if (RuntimeFeature.IsSupported("NumericIntPtr"))
{
    return symbol.ToDisplayString(Format);
}
return symbol.ToDisplayString(Format)
    .Replace("nint", "System.IntPtr") // Treat nint and IntPtr as the same
    .Replace("nuint", "System.UIntPtr"); // Treat nuint and UIntPtr as the same

tests fail on .NET 7. It seems like even when NumericIntPtr is defined, Roslyn returns different display strings.

Copy link
Member

Choose a reason for hiding this comment

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

Off the top of my head the two concerning places are nested types (which probably here use a + instead of .) and explicit interface implementations (e.g. the full method name of IEnumerable.GetEnumerator() => GetEnumerator(); I believe is System.IEnumerable.GetEnumerator. So while "nint" => "System.IntPtr" probably wouldn't create any false positives, '.' probably isn't a guaranteed "eh, it's fine".

Copy link
Member

@ViktorHofer ViktorHofer Aug 31, 2022

Choose a reason for hiding this comment

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

Agreed that nint is fairly new but it's not unlikely that nint and nuint word parts are used in a member's name. I would expect that replacing "nint" with "System.IntPtr" would result in way more false positives than the other way around. That said, these false positives shouldn't have any negative impact as far as I can tell.

Copy link
Member

@ericstj ericstj Aug 31, 2022

Choose a reason for hiding this comment

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

if (RuntimeFeature.IsSupported("NumericIntPtr"))

This is mixing tooling runtime with target runtime. The check would need to be against whatever the target references that were used when compiling the assembly. Calling the feature RuntimeFeature.IsSupported in the tool runtime has no relevance since we might be using an older runtime to test assemblies built for a newer one.

The way I was imagining this to work was we would add some parameter to DefaultSymbolsEqualityComparer like bool honorRuntimeFeatures and set that as withReferences == true. Then we could say that when it is false we'd do the hack-up in DefaultSymbolsEqualityComparer.GetKey to normalize those symbols.

it's not unlikely that nint and nuint word parts are used in a member's name

That's true. I see we're doing arbitrary replacements here wherever they occur in the string. I'll relent on my suggestion to change the direction of replacement.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, the premise of the withReferences suggestion was based on the fact that we only face this problem when we don't have the matching references. However, we probably are in this state even when references are provided, since we don't have references for the left-hand-side and that might be the runtime that doesn't support the feature :(

I was really trying to get this to a place where we didn't need this hack indefinitely but I guess that might not be possible at the moment.

Copy link
Member

@ViktorHofer ViktorHofer left a comment

Choose a reason for hiding this comment

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

Looks good. Thanks for adding a test for it.

@ericstj
Copy link
Member

ericstj commented Aug 31, 2022

Approved this as-is. I think given our current architecture (references present only for right hand side) and the only source for this feature information in the compiler coming from references - we'll always be in a state where we need to normalize these types of metadata differences ourselves. That's unfortunate but I can't think of a better solution.

@smasher164 smasher164 merged commit f19c3d7 into dotnet:main Aug 31, 2022
@smasher164 smasher164 deleted the apicompat-numericintptr branch August 31, 2022 21:43
ViktorHofer pushed a commit to ViktorHofer/sdk that referenced this pull request Sep 12, 2022
Previously, ApiCompat would consider `IntPtr` and `nint` (as well as `UIntPtr` and `nuint`) as different types, emitting diagnostics where there shouldn't have been. This change updates the display string used by the SymbolComparer to replace all instances of `IntPtr` with `nint` and `UIntPtr` with `nuint`.

Fixes dotnet#25566
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Microsoft.Dotnet.Compatibility analyzer doesn't work with the NumericIntPtr feature

4 participants