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

Ignore generic type parameter names when matching #40359

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis;

Expand All @@ -11,8 +12,26 @@ namespace Microsoft.DotNet.ApiCompatibility.Rules
/// </summary>
public class CannotChangeParameterName : IRule
{
public CannotChangeParameterName(IRuleSettings settings, IRuleRegistrationContext context) =>
public CannotChangeParameterName(IRuleSettings settings, IRuleRegistrationContext context)
{
context.RegisterOnTypeSymbolAction(RunOnTypeSymbol);
context.RegisterOnMemberSymbolAction(RunOnMemberSymbol);
}


private void RunOnTypeSymbol(ITypeSymbol? left,
ITypeSymbol? right,
MetadataInformation leftMetadata,
MetadataInformation rightMetadata,
IList<CompatDifference> differences)
{
if (left is not INamedTypeSymbol leftType || right is not INamedTypeSymbol rightType)
{
return;
}

CompareParameters(leftType.TypeParameters, rightType.TypeParameters, left, leftMetadata, rightMetadata, differences, parameterIndicator: "``");
}

private void RunOnMemberSymbol(
ISymbol? left,
Expand All @@ -28,12 +47,26 @@ private void RunOnMemberSymbol(
return;
}

Debug.Assert(leftMethod.Parameters.Length == rightMethod.Parameters.Length);
CompareParameters(leftMethod.Parameters, rightMethod.Parameters, left, leftMetadata, rightMetadata, differences);

CompareParameters(leftMethod.TypeParameters, rightMethod.TypeParameters, left, leftMetadata, rightMetadata, differences, parameterIndicator: "``");

}

private void CompareParameters(IReadOnlyList<ISymbol> leftParameters,
IReadOnlyList<ISymbol> rightParameters,
ISymbol left,
MetadataInformation leftMetadata,
MetadataInformation rightMetadata,
IList<CompatDifference> differences,
string parameterIndicator = "$")
{
Debug.Assert(leftParameters.Count == rightParameters.Count);

for (int i = 0; i < leftMethod.Parameters.Length; i++)
for (int i = 0; i < leftParameters.Count; i++)
{
IParameterSymbol leftParam = leftMethod.Parameters[i];
IParameterSymbol rightParam = rightMethod.Parameters[i];
ISymbol leftParam = leftParameters[i];
ISymbol rightParam = rightParameters[i];

if (!leftParam.Name.Equals(rightParam.Name))
{
Expand All @@ -43,7 +76,7 @@ private void RunOnMemberSymbol(
DiagnosticIds.CannotChangeParameterName,
string.Format(Resources.CannotChangeParameterName, left, leftParam.Name, rightParam.Name),
DifferenceType.Changed,
$"{leftMethod.GetDocumentationCommentId()}${i}"));
$"{left.GetDocumentationCommentId()}{parameterIndicator}{i}"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private bool ParametersMatch(IMethodSymbol method, IMethodSymbol candidate)

for (int i = 0; i < method.Parameters.Length; i++)
{
if (!_settings.SymbolEqualityComparer.Equals(method.Parameters[i].Type, method.Parameters[i].Type))
if (!_settings.SymbolEqualityComparer.Equals(method.Parameters[i].Type, candidate.Parameters[i].Type))
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,37 @@ static SymbolExtensions()
{
// This is the default format for symbol.ToDisplayString;
SymbolDisplayFormat format = SymbolDisplayFormat.CSharpErrorMessageFormat;
format = format.WithMemberOptions(format.MemberOptions | SymbolDisplayMemberOptions.IncludeType);
format = format.AddMemberOptions(SymbolDisplayMemberOptions.IncludeType);
format = format.AddParameterOptions(SymbolDisplayParameterOptions.IncludeExtensionThis);

DisplayFormat = format.WithParameterOptions(format.ParameterOptions | SymbolDisplayParameterOptions.IncludeExtensionThis);
DisplayFormat = format;

// Remove ? annotations from reference types as we want to map the APIs without nullable annotations
// and have a special rule to catch those differences.
// Also don't use keyword names for special types. This makes the comparison more accurate when no
// references are running or if one side has references and the other doesn't.
format = format.WithMiscellaneousOptions(format.MiscellaneousOptions &
~SymbolDisplayMiscellaneousOptions.UseSpecialTypes &
~SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName &
~SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
format = format.RemoveMiscellaneousOptions(
SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName |
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

// Remove ref/out from parameters to compare APIs when building the mappers.
s_comparisonFormat = format.WithParameterOptions((format.ParameterOptions | SymbolDisplayParameterOptions.IncludeExtensionThis) & ~SymbolDisplayParameterOptions.IncludeParamsRefOut);
s_comparisonFormat = format.RemoveParameterOptions(SymbolDisplayParameterOptions.IncludeParamsRefOut);
}

public static string ToComparisonDisplayString(this ISymbol symbol) =>
symbol.ToDisplayString(s_comparisonFormat)
public static string ToComparisonDisplayString(this ISymbol symbol)
{
var parts = symbol.ToDisplayParts(s_comparisonFormat);

// Type parameter names are not significant. Remove these leaving behind
// Angle brackets and commas for each parameter > 1.
parts = parts.RemoveAll(p => p.Kind == SymbolDisplayPartKind.TypeParameterName);

return parts
.ToDisplayString()
.Replace("System.IntPtr", "nint") // Treat IntPtr and nint as the same
.Replace("System.UIntPtr", "nuint"); // Treat UIntPtr and nuint as the same
}

public static IEnumerable<ITypeSymbol> GetAllBaseTypes(this ITypeSymbol type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,70 +15,116 @@ public class ParameterNamesCannotChangeTests
{
// Method
{
@"
namespace CompatTests
{
public class First {
public void F(int a, string s) {}
}
}
",
@"
namespace CompatTests
{
public class First {
public void F(int b, string t) {}
}
}
",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.F(System.Int32,System.String)$0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.F(System.Int32,System.String)$1")
}
"""
namespace CompatTests
{
public class First {
public void F(int a, string s) {}
}
}
""",
"""
namespace CompatTests
{
public class First {
public void F(int b, string t) {}
}
}
""",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.F(System.Int32,System.String)$0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.F(System.Int32,System.String)$1")
}
},
// Constructor
{
@"
namespace CompatTests
{
public class First {
public First(int a, string s) {}
}
}
",
@"
namespace CompatTests
{
public class First {
public First(int b, string t) {}
}
}
",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.#ctor(System.Int32,System.String)$0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.#ctor(System.Int32,System.String)$1")
}
"""
namespace CompatTests
{
public class First {
public First(int a, string s) {}
}
}
""",
"""
namespace CompatTests
{
public class First {
public First(int b, string t) {}
}
}
""",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.#ctor(System.Int32,System.String)$0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.First.#ctor(System.Int32,System.String)$1")
}
},
// Property
{
@"
namespace CompatTests
{
public class First {
public int F { get; }
}
}
",
@"
namespace CompatTests
{
public class First {
public int F { get; }
}
}
",
new CompatDifference[] {}
}
"""
namespace CompatTests
{
public class First {
public int F { get; }
}
}
""",
"""
namespace CompatTests
{
public class First {
public int F { get; }
}
}
""",
new CompatDifference[] {}
},
// Generic class
{
"""
namespace CompatTests
{
public class First<T1, T2> { }
}
""",
"""
namespace CompatTests
{
public class First<V1, V2> { }
}
""",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "T:CompatTests.First`2``0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "T:CompatTests.First`2``1")
}
},

// Generic method
{
"""
namespace CompatTests
{
public class C
{
public void First<T1, T2>() { }
}
}
""",
"""
namespace CompatTests
{
public class C
{
public void First<V1, V2>() { }
}
}
""",
new CompatDifference[] {
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.C.First``2``0"),
CompatDifference.CreateWithDefaultMetadata(DiagnosticIds.CannotChangeParameterName, "", DifferenceType.Changed, "M:CompatTests.C.First``2``1")
}
},

};

[Theory]
Expand Down