|
// 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 System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Roslyn.Test.Utilities;
using Xunit;
using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeRefactoringVerifier<
Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers.GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider>;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.GenerateEqualsAndGetHashCodeFromMembers;
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)]
public class GenerateEqualsAndGetHashCodeFromMembersTests
{
private class TestWithDialog : VerifyCS.Test
{
private static readonly TestComposition s_composition =
EditorTestCompositions.EditorFeatures.AddParts(typeof(TestPickMembersService));
public ImmutableArray<string> MemberNames;
public Action<ImmutableArray<PickMembersOption>> OptionsCallback;
protected override Task<Workspace> CreateWorkspaceImplAsync()
{
// If we're a dialog test, then mixin our mock and initialize its values to the ones the test asked for.
var workspace = new AdhocWorkspace(s_composition.GetHostServices());
var service = (TestPickMembersService)workspace.Services.GetService<IPickMembersService>();
service.MemberNames = MemberNames;
service.OptionsCallback = OptionsCallback;
return Task.FromResult<Workspace>(workspace);
}
}
private static OptionsCollection PreferImplicitTypeWithInfo()
=> new OptionsCollection(LanguageNames.CSharp)
{
{ CSharpCodeStyleOptions.VarElsewhere, true, NotificationOption2.Suggestion },
{ CSharpCodeStyleOptions.VarWhenTypeIsApparent, true, NotificationOption2.Suggestion },
{ CSharpCodeStyleOptions.VarForBuiltInTypes, true, NotificationOption2.Suggestion },
};
private static OptionsCollection PreferExplicitTypeWithInfo()
=> new OptionsCollection(LanguageNames.CSharp)
{
{ CSharpCodeStyleOptions.VarElsewhere, false, NotificationOption2.Suggestion },
{ CSharpCodeStyleOptions.VarWhenTypeIsApparent, false, NotificationOption2.Suggestion },
{ CSharpCodeStyleOptions.VarForBuiltInTypes, false, NotificationOption2.Suggestion },
};
internal static void EnableOption(ImmutableArray<PickMembersOption> options, string id)
{
var option = options.FirstOrDefault(o => o.Id == id);
if (option != null)
{
option.Value = true;
}
}
[Fact]
public async Task TestEqualsSingleField()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
a == program.a;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsSingleField_CSharp7()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
public override bool Equals(object obj)
{
return obj is Program program &&
a == program.a;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp7,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/39916")]
public async Task TestEqualsSingleField_PreferExplicitType()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
public override bool Equals(object obj)
{
Program program = obj as Program;
return !ReferenceEquals(program, null) &&
a == program.a;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferExplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestReferenceIEquatable()
{
var code =
"""
using System;
using System.Collections.Generic;
class S : {|CS0535:IEquatable<S>|} { }
class Program
{
[|S a;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class S : {|CS0535:IEquatable<S>|} { }
class Program
{
S a;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
EqualityComparer<S>.Default.Equals(a, program.a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestNullableReferenceIEquatable()
{
var code =
"""
#nullable enable
using System;
using System.Collections.Generic;
class S : {|CS0535:IEquatable<S>|} { }
class Program
{
[|S? a;|]
}
""";
var fixedCode =
"""
#nullable enable
using System;
using System.Collections.Generic;
class S : {|CS0535:IEquatable<S>|} { }
class Program
{
S? a;
public override bool Equals(object? obj)
{
return obj is Program program &&
EqualityComparer<S?>.Default.Equals(a, program.a);
}
public override int GetHashCode()
{
return -1757793268 + EqualityComparer<S?>.Default.GetHashCode(a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestValueIEquatable()
{
var code =
"""
using System;
using System.Collections.Generic;
struct S : {|CS0535:IEquatable<S>|} { }
class Program
{
[|S a;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
struct S : {|CS0535:IEquatable<S>|} { }
class Program
{
S a;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
a.Equals(program.a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsLongName()
{
var code =
"""
using System.Collections.Generic;
class ReallyLongName
{
[|int a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class ReallyLongName
{
int a;
public override bool Equals(object obj)
{
var name = obj as ReallyLongName;
return !ReferenceEquals(name, null) &&
a == name.a;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsKeywordName()
{
var code =
"""
using System.Collections.Generic;
class ReallyLongLong
{
[|long a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class ReallyLongLong
{
long a;
public override bool Equals(object obj)
{
var @long = obj as ReallyLongLong;
return !ReferenceEquals(@long, null) &&
a == @long.a;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsProperty()
{
var code =
"""
using System.Collections.Generic;
class ReallyLongName
{
[|int a;
string B { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class ReallyLongName
{
int a;
string B { get; }
public override bool Equals(object obj)
{
var name = obj as ReallyLongName;
return !ReferenceEquals(name, null) &&
a == name.a &&
B == name.B;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsBaseTypeWithNoEquals()
{
var code =
"""
class Base
{
}
class Program : Base
{
[|int i;|]
}
""";
var fixedCode =
"""
class Base
{
}
class Program : Base
{
int i;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
i == program.i;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsBaseWithOverriddenEquals()
{
var code =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Program : Base
{
[|int i;
string S { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Program : Base
{
int i;
string S { get; }
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
base.Equals(obj) &&
i == program.i &&
S == program.S;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsOverriddenDeepBase()
{
var code =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Middle : Base
{
}
class Program : Middle
{
[|int i;
string S { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Middle : Base
{
}
class Program : Middle
{
int i;
string S { get; }
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
base.Equals(obj) &&
i == program.i &&
S == program.S;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsStruct()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System.Collections.Generic;
struct ReallyLongName
{
[|int i;
string S { get; }|]
}
""",
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : IEquatable<ReallyLongName>
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is ReallyLongName name && Equals(name);
}
public bool Equals(ReallyLongName other)
{
return i == other.i &&
S == other.S;
}
public static bool operator ==(ReallyLongName left, ReallyLongName right)
{
return left.Equals(right);
}
public static bool operator !=(ReallyLongName left, ReallyLongName right)
{
return !(left == right);
}
}
""");
}
[Fact]
public async Task TestEqualsStructCSharpLatest()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System.Collections.Generic;
struct ReallyLongName
{
[|int i;
string S { get; }|]
}
""",
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : IEquatable<ReallyLongName>
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is ReallyLongName name && Equals(name);
}
public bool Equals(ReallyLongName other)
{
return i == other.i &&
S == other.S;
}
public static bool operator ==(ReallyLongName left, ReallyLongName right)
{
return left.Equals(right);
}
public static bool operator !=(ReallyLongName left, ReallyLongName right)
{
return !(left == right);
}
}
""");
}
[Fact]
public async Task TestEqualsStructAlreadyImplementsIEquatable()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : {|CS0535:IEquatable<ReallyLongName>|}
{
[|int i;
string S { get; }|]
}
""",
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : {|CS0535:IEquatable<ReallyLongName>|}
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is ReallyLongName name &&
i == name.i &&
S == name.S;
}
public static bool operator ==(ReallyLongName left, ReallyLongName right)
{
return left.Equals(right);
}
public static bool operator !=(ReallyLongName left, ReallyLongName right)
{
return !(left == right);
}
}
""");
}
[Fact]
public async Task TestEqualsStructAlreadyHasOperators()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System;
using System.Collections.Generic;
struct ReallyLongName
{
[|int i;
string S { get; }|]
public static bool operator ==(ReallyLongName left, ReallyLongName right) => false;
public static bool operator !=(ReallyLongName left, ReallyLongName right) => false;
}
""",
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : IEquatable<ReallyLongName>
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is ReallyLongName name && Equals(name);
}
public bool Equals(ReallyLongName other)
{
return i == other.i &&
S == other.S;
}
public static bool operator ==(ReallyLongName left, ReallyLongName right) => false;
public static bool operator !=(ReallyLongName left, ReallyLongName right) => false;
}
""");
}
[Fact]
public async Task TestEqualsStructAlreadyImplementsIEquatableAndHasOperators()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : {|CS0535:IEquatable<ReallyLongName>|}
{
[|int i;
string S { get; }|]
public static bool operator ==(ReallyLongName left, ReallyLongName right) => false;
public static bool operator !=(ReallyLongName left, ReallyLongName right) => false;
}
""",
"""
using System;
using System.Collections.Generic;
struct ReallyLongName : {|CS0535:IEquatable<ReallyLongName>|}
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is ReallyLongName name &&
i == name.i &&
S == name.S;
}
public static bool operator ==(ReallyLongName left, ReallyLongName right) => false;
public static bool operator !=(ReallyLongName left, ReallyLongName right) => false;
}
""");
}
[Fact]
public async Task TestEqualsGenericType()
{
var code = """
using System.Collections.Generic;
class Program<T>
{
[|int i;|]
}
""";
var expected = """
using System.Collections.Generic;
class Program<T>
{
int i;
public override bool Equals(object obj)
{
var program = obj as Program<T>;
return !ReferenceEquals(program, null) &&
i == program.i;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = expected,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsNullableContext()
{
await VerifyCS.VerifyRefactoringAsync(
"""
#nullable enable
class Program
{
[|int a;|]
}
""",
"""
#nullable enable
class Program
{
int a;
public override bool Equals(object? obj)
{
return obj is Program program &&
a == program.a;
}
}
""");
}
[Fact]
public async Task TestGetHashCodeSingleField1()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int i;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
i == program.i;
}
public override int GetHashCode()
{
return 165851236 + i.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeSingleField2()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int j;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int j;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
j == program.j;
}
public override int GetHashCode()
{
return 1424088837 + j.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeWithBaseHashCode1()
{
var code =
"""
using System.Collections.Generic;
class Base {
public override int GetHashCode() => 0;
}
class Program : Base
{
[|int j;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Base {
public override int GetHashCode() => 0;
}
class Program : Base
{
int j;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
j == program.j;
}
public override int GetHashCode()
{
var hashCode = 339610899;
hashCode = hashCode * -1521134295 + base.GetHashCode();
hashCode = hashCode * -1521134295 + j.GetHashCode();
return hashCode;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeWithBaseHashCode2()
{
var code =
"""
using System.Collections.Generic;
class Base {
public override int GetHashCode() => 0;
}
class Program : Base
{
int j;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Base {
public override int GetHashCode() => 0;
}
class Program : Base
{
int j;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null);
}
public override int GetHashCode()
{
return 624022166 + base.GetHashCode();
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
MemberNames = ImmutableArray<string>.Empty,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeSingleField_CodeStyle1()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int i;
public override bool Equals(object obj)
{
Program program = obj as Program;
return !ReferenceEquals(program, null) &&
i == program.i;
}
public override int GetHashCode() => 165851236 + i.GetHashCode();
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
Options =
{
{ CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement },
},
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeTypeParameter()
{
var code =
"""
using System.Collections.Generic;
class Program<T>
{
[|T i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program<T>
{
T i;
public override bool Equals(object obj)
{
var program = obj as Program<T>;
return !ReferenceEquals(program, null) &&
EqualityComparer<T>.Default.Equals(i, program.i);
}
public override int GetHashCode()
{
return 165851236 + EqualityComparer<T>.Default.GetHashCode(i);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeGenericType()
{
var code =
"""
using System.Collections.Generic;
class Program<T>
{
[|Program<T> i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program<T>
{
Program<T> i;
public override bool Equals(object obj)
{
var program = obj as Program<T>;
return !ReferenceEquals(program, null) &&
EqualityComparer<Program<T>>.Default.Equals(i, program.i);
}
public override int GetHashCode()
{
return 165851236 + EqualityComparer<Program<T>>.Default.GetHashCode(i);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeMultipleMembers()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int i;
string S { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int i;
string S { get; }
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
i == program.i &&
S == program.S;
}
public override int GetHashCode()
{
var hashCode = -538000506;
hashCode = hashCode * -1521134295 + i.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(S);
return hashCode;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestSmartTagText1()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|bool b;
HashSet<string> s;|]
public Program(bool b)
{
this.b = b;
}
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
bool b;
HashSet<string> s;
public Program(bool b)
{
this.b = b;
}
public override bool Equals(object obj)
{
return obj is Program program &&
b == program.b &&
EqualityComparer<HashSet<string>>.Default.Equals(s, program.s);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
CodeActionEquivalenceKey = FeaturesResources.Generate_Equals_object,
CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Generate_Equals_object, codeAction.Title),
}.RunAsync();
}
[Fact]
public async Task TestSmartTagText2()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|bool b;
HashSet<string> s;|]
public Program(bool b)
{
this.b = b;
}
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
bool b;
HashSet<string> s;
public Program(bool b)
{
this.b = b;
}
public override bool Equals(object obj)
{
return obj is Program program &&
b == program.b &&
EqualityComparer<HashSet<string>>.Default.Equals(s, program.s);
}
public override int GetHashCode()
{
int hashCode = -666523601;
hashCode = hashCode * -1521134295 + b.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<HashSet<string>>.Default.GetHashCode(s);
return hashCode;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
CodeActionEquivalenceKey = FeaturesResources.Generate_Equals_and_GetHashCode,
CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Generate_Equals_and_GetHashCode, codeAction.Title),
}.RunAsync();
}
[Fact]
public async Task TestSmartTagText3()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|bool b;
HashSet<string> s;|]
public Program(bool b)
{
this.b = b;
}
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
bool b;
HashSet<string> s;
public Program(bool b)
{
this.b = b;
}
public override bool Equals(object obj)
{
return obj is Program program &&
b == program.b &&
EqualityComparer<HashSet<string>>.Default.Equals(s, program.s);
}
public override int GetHashCode()
{
int hashCode = -666523601;
hashCode = hashCode * -1521134295 + b.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<HashSet<string>>.Default.GetHashCode(s);
return hashCode;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
CodeActionEquivalenceKey = FeaturesResources.Generate_Equals_and_GetHashCode,
CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Generate_Equals_and_GetHashCode, codeAction.Title),
}.RunAsync();
}
[Fact]
public async Task Tuple_Disabled()
{
var code =
"""
using System.Collections.Generic;
class C
{
[|{|CS8059:(int, string)|} a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class C
{
{|CS8059:(int, string)|} a;
public override bool Equals(object obj)
{
var c = obj as C;
return !ReferenceEquals(c, null) &&
a.Equals(c.a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task Tuples_Equals()
{
var code =
"""
using System.Collections.Generic;
class C
{
[|{|CS8059:(int, string)|} a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class C
{
{|CS8059:(int, string)|} a;
public override bool Equals(object obj)
{
var c = obj as C;
return !ReferenceEquals(c, null) &&
a.Equals(c.a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TupleWithNames_Equals()
{
var code =
"""
using System.Collections.Generic;
class C
{
[|{|CS8059:(int x, string y)|} a;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class C
{
{|CS8059:(int x, string y)|} a;
public override bool Equals(object obj)
{
var c = obj as C;
return !ReferenceEquals(c, null) &&
a.Equals(c.a);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task Tuple_HashCode()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|{|CS8059:(int, string)|} i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
{|CS8059:(int, string)|} i;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
i.Equals(program.i);
}
public override int GetHashCode()
{
return 165851236 + i.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TupleWithNames_HashCode()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|{|CS8059:(int x, string y)|} i;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
{|CS8059:(int x, string y)|} i;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
i.Equals(program.i);
}
public override int GetHashCode()
{
return 165851236 + i.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task StructWithoutGetHashCodeOverride_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo
{
[|Bar bar;|]
}
struct Bar
{
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo
{
Bar bar;
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
EqualityComparer<Bar>.Default.Equals(bar, foo.bar);
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
struct Bar
{
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task StructWithGetHashCodeOverride_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo
{
[|Bar bar;|]
}
struct Bar
{
public override int GetHashCode() => 0;
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo
{
Bar bar;
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
EqualityComparer<Bar>.Default.Equals(bar, foo.bar);
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
struct Bar
{
public override int GetHashCode() => 0;
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task NullableStructWithoutGetHashCodeOverride_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo
{
[|Bar? bar;|]
}
struct Bar
{
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo
{
Bar? bar;
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
EqualityComparer<Bar?>.Default.Equals(bar, foo.bar);
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
struct Bar
{
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task StructTypeParameter_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo<TBar> where TBar : struct
{
[|TBar bar;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo<TBar> where TBar : struct
{
TBar bar;
public override bool Equals(object obj)
{
var foo = obj as Foo<TBar>;
return !ReferenceEquals(foo, null) &&
EqualityComparer<TBar>.Default.Equals(bar, foo.bar);
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task NullableStructTypeParameter_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo<TBar> where TBar : struct
{
[|TBar? bar;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo<TBar> where TBar : struct
{
TBar? bar;
public override bool Equals(object obj)
{
var foo = obj as Foo<TBar>;
return !ReferenceEquals(foo, null) &&
EqualityComparer<TBar?>.Default.Equals(bar, foo.bar);
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task Enum_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo
{
[|Bar bar;|]
}
enum Bar
{
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo
{
Bar bar;
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
bar == foo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
enum Bar
{
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task PrimitiveValueType_ShouldCallGetHashCodeDirectly()
{
var code =
"""
using System.Collections.Generic;
class Foo
{
[|ulong bar;|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Foo
{
ulong bar;
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
bar == foo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestWithDialog1()
{
var code =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
a == program.a &&
b == program.b;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = ImmutableArray.Create("a", "b"),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestWithDialog2()
{
var code =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
bool c;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
bool c;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
c == program.c &&
b == program.b;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = ImmutableArray.Create("c", "b"),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestWithDialog3()
{
var code =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
bool c;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int a;
string b;
bool c;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null);
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = ImmutableArray<string>.Empty,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17643")]
public async Task TestWithDialogNoBackingField()
{
var code =
"""
class Program
{
public int F { get; set; }
[||]
}
""";
var fixedCode =
"""
class Program
{
public int F { get; set; }
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
F == program.F;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/25690")]
public async Task TestWithDialogNoIndexer()
{
var code =
"""
class Program
{
public int P => 0;
public int this[int index] => 0;
[||]
}
""";
var fixedCode =
"""
class Program
{
public int P => 0;
public int this[int index] => 0;
public override bool Equals(object obj)
{
return obj is Program program &&
P == program.P;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/25707")]
public async Task TestWithDialogNoSetterOnlyProperty()
{
var code =
"""
class Program
{
public int P => 0;
public int S { set { } }
[||]
}
""";
var fixedCode =
"""
class Program
{
public int P => 0;
public int S { set { } }
public override bool Equals(object obj)
{
return obj is Program program &&
P == program.P;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/41958")]
public async Task TestWithDialogInheritedMembers()
{
var code =
"""
class Base
{
public int C { get; set; }
}
class Middle : Base
{
public int B { get; set; }
}
class Derived : Middle
{
public int A { get; set; }
[||]
}
""";
var fixedCode =
"""
class Base
{
public int C { get; set; }
}
class Middle : Base
{
public int B { get; set; }
}
class Derived : Middle
{
public int A { get; set; }
public override bool Equals(object obj)
{
return obj is Derived derived &&
C == derived.C &&
B == derived.B &&
A == derived.A;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
}.RunAsync();
}
[Fact]
public async Task TestGenerateOperators1()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
public string s;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
s == program.s;
}
public static bool operator ==(Program left, Program right)
{
return EqualityComparer<Program>.Default.Equals(left, right);
}
public static bool operator !=(Program left, Program right)
{
return !(left == right);
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGenerateOperators2()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
public string s;
public override bool Equals(object obj)
{
Program program = obj as Program;
return !ReferenceEquals(program, null) &&
s == program.s;
}
public static bool operator ==(Program left, Program right) => EqualityComparer<Program>.Default.Equals(left, right);
public static bool operator !=(Program left, Program right) => !(left == right);
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId),
LanguageVersion = LanguageVersion.CSharp6,
Options =
{
{ CSharpCodeStyleOptions.PreferExpressionBodiedOperators, CSharpCodeStyleOptions.WhenPossibleWithSilentEnforcement },
},
}.RunAsync();
}
[Fact]
public async Task TestGenerateOperators3()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
public static bool operator {|CS0216:==|}(Program left, Program right) => true;
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
public string s;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
s == program.s;
}
public static bool operator {|CS0216:==|}(Program left, Program right) => true;
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => Assert.Null(options.FirstOrDefault(i => i.Id == GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId)),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGenerateOperators4()
{
var code =
"""
using System.Collections.Generic;
struct Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
struct Program
{
public string s;
public override bool Equals(object obj)
{
if (!(obj is Program))
{
return false;
}
var program = (Program)obj;
return s == program.s;
}
public static bool operator ==(Program left, Program right)
{
return left.Equals(right);
}
public static bool operator !=(Program left, Program right)
{
return !(left == right);
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGenerateLiftedOperators()
{
var code =
"""
using System;
using System.Collections.Generic;
class Foo
{
[|public bool? BooleanValue { get; }
public decimal? DecimalValue { get; }
public Bar? EnumValue { get; }
public DateTime? DateTimeValue { get; }|]
}
enum Bar
{
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Foo
{
public bool? BooleanValue { get; }
public decimal? DecimalValue { get; }
public Bar? EnumValue { get; }
public DateTime? DateTimeValue { get; }
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
BooleanValue == foo.BooleanValue &&
DecimalValue == foo.DecimalValue &&
EnumValue == foo.EnumValue &&
DateTimeValue == foo.DateTimeValue;
}
}
enum Bar
{
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task LiftedOperatorIsNotUsedWhenDirectOperatorWouldNotBeUsed()
{
var code =
"""
using System;
using System.Collections.Generic;
class Foo
{
[|public Bar Value { get; }
public Bar? NullableValue { get; }|]
}
struct Bar : IEquatable<Bar>
{
private readonly int value;
public override bool Equals(object obj) => false;
public bool Equals(Bar other) => value == other.value;
public override int GetHashCode() => -1584136870 + value.GetHashCode();
public static bool operator ==(Bar left, Bar right) => left.Equals(right);
public static bool operator !=(Bar left, Bar right) => !(left == right);
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Foo
{
public Bar Value { get; }
public Bar? NullableValue { get; }
public override bool Equals(object obj)
{
var foo = obj as Foo;
return !ReferenceEquals(foo, null) &&
Value.Equals(foo.Value) &&
EqualityComparer<Bar?>.Default.Equals(NullableValue, foo.NullableValue);
}
}
struct Bar : IEquatable<Bar>
{
private readonly int value;
public override bool Equals(object obj) => false;
public bool Equals(Bar other) => value == other.value;
public override int GetHashCode() => -1584136870 + value.GetHashCode();
public static bool operator ==(Bar left, Bar right) => left.Equals(right);
public static bool operator !=(Bar left, Bar right) => !(left == right);
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnStruct()
{
var code =
"""
using System.Collections.Generic;
struct Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
struct Program : IEquatable<Program>
{
public string s;
public override bool Equals(object obj)
{
return obj is Program && Equals((Program)obj);
}
public bool Equals(Program other)
{
return s == other.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/25708")]
public async Task TestOverrideEqualsOnRefStructReturnsFalse()
{
var code =
"""
ref struct Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
ref struct Program
{
public string s;
public override bool Equals(object obj)
{
return false;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/25708")]
public async Task TestImplementIEquatableOnRefStructSkipsIEquatable()
{
var code =
"""
ref struct Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
ref struct Program
{
public string s;
public override bool Equals(object obj)
{
return false;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
// We are forcefully enabling the ImplementIEquatable option, as that is our way
// to test that the option does nothing. The VS mode will ensure if the option
// is not available it will not be shown.
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnStructInNullableContextWithUnannotatedMetadata()
{
var code =
"""
#nullable enable
struct Foo
{
public int Bar { get; }
[||]
}
""";
var fixedCode =
"""
#nullable enable
using System;
struct Foo : IEquatable<Foo>
{
public int Bar { get; }
public override bool Equals(object? obj)
{
return obj is Foo foo && Equals(foo);
}
public bool Equals(Foo other)
{
return Bar == other.Bar;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp8,
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnStructInNullableContextWithAnnotatedMetadata()
{
var code =
"""
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
struct Foo
{
public bool Bar { get; }
[||]
}
""";
var fixedCode =
"""
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
struct Foo : IEquatable<Foo>
{
public bool Bar { get; }
public override bool Equals(object? obj)
{
return obj is Foo foo && Equals(foo);
}
public bool Equals(Foo other)
{
return Bar == other.Bar;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp8,
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClass_CSharp6()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Program : IEquatable<Program>
{
public string s;
public override bool Equals(object obj)
{
return Equals(obj as Program);
}
public bool Equals(Program other)
{
return !ReferenceEquals(other, null) &&
s == other.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClass_CSharp7()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Program : IEquatable<Program>
{
public string s;
public override bool Equals(object obj)
{
return Equals(obj as Program);
}
public bool Equals(Program other)
{
return !(other is null) &&
s == other.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp7,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClass_CSharp8()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Program : IEquatable<Program>
{
public string s;
public override bool Equals(object obj)
{
return Equals(obj as Program);
}
public bool Equals(Program other)
{
return !(other is null) &&
s == other.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp8,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClass_CSharp9()
{
var code =
"""
using System.Collections.Generic;
class Program
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
class Program : IEquatable<Program>
{
public string s;
public override bool Equals(object obj)
{
return Equals(obj as Program);
}
public bool Equals(Program other)
{
return other is not null &&
s == other.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp9,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClassInNullableContextWithUnannotatedMetadata()
{
var code =
"""
#nullable enable
class Foo
{
public int Bar { get; }
[||]
}
""";
var fixedCode =
"""
#nullable enable
using System;
class Foo : IEquatable<Foo?>
{
public int Bar { get; }
public override bool Equals(object? obj)
{
return Equals(obj as Foo);
}
public bool Equals(Foo? other)
{
return !(other is null) &&
Bar == other.Bar;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp8,
}.RunAsync();
}
[Fact]
public async Task TestImplementIEquatableOnClassInNullableContextWithAnnotatedMetadata()
{
var code =
"""
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
class Foo
{
public bool Bar { get; }
[||]
}
""";
var fixedCode =
"""
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
class Foo : IEquatable<Foo?>
{
public bool Bar { get; }
public override bool Equals(object? obj)
{
return Equals(obj as Foo);
}
public bool Equals(Foo? other)
{
return !(other is null) &&
Bar == other.Bar;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId),
LanguageVersion = LanguageVersion.CSharp8,
}.RunAsync();
}
[Fact]
public async Task TestDoNotOfferIEquatableIfTypeAlreadyImplementsIt()
{
var code =
"""
using System.Collections.Generic;
class Program : {|CS0535:System.IEquatable<Program>|}
{
public string s;
[||]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program : {|CS0535:System.IEquatable<Program>|}
{
public string s;
public override bool Equals(object obj)
{
var program = obj as Program;
return !ReferenceEquals(program, null) &&
s == program.s;
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => Assert.Null(options.FirstOrDefault(i => i.Id == GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId)),
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestMissingReferences1()
{
await new VerifyCS.Test
{
LanguageVersion = LanguageVersion.CSharp6,
CodeActionIndex = 1,
TestState =
{
Sources =
{
"""
public class Class1
{
[|int i;|]
public void F()
{
}
}
""",
},
ExpectedDiagnostics =
{
// /0/Test0.cs(1,14): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(1, 14, 1, 20).WithArguments("System.Object"),
// /0/Test0.cs(1,14): error CS1729: 'object' does not contain a constructor that takes 0 arguments
DiagnosticResult.CompilerError("CS1729").WithSpan(1, 14, 1, 20).WithArguments("object", "0"),
// /0/Test0.cs(3,5): error CS0518: Predefined type 'System.Int32' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(3, 5, 3, 8).WithArguments("System.Int32"),
// /0/Test0.cs(5,12): error CS0518: Predefined type 'System.Void' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(5, 12, 5, 16).WithArguments("System.Void"),
},
},
FixedState =
{
Sources = {
"""
public class Class1
{
int i;
public override System.Boolean Equals(System.Object obj)
{
Class1 @class = obj as Class1;
return !ReferenceEquals(@class, null) &&
i == @class.i;
}
public void F()
{
}
public override System.Int32 GetHashCode()
{
return 165851236 + EqualityComparer<System.Int32>.Default.GetHashCode(i);
}
}
""",
},
ExpectedDiagnostics =
{
// /0/Test0.cs(1,14): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(1, 14, 1, 20).WithArguments("System.Object"),
// /0/Test0.cs(1,14): error CS1729: 'object' does not contain a constructor that takes 0 arguments
DiagnosticResult.CompilerError("CS1729").WithSpan(1, 14, 1, 20).WithArguments("object", "0"),
// /0/Test0.cs(3,5): error CS0518: Predefined type 'System.Int32' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(3, 5, 3, 8).WithArguments("System.Int32"),
// /0/Test0.cs(5,21): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(5, 21, 5, 27).WithArguments("System.Object"),
// /0/Test0.cs(5,28): error CS1069: The type name 'Boolean' could not be found in the namespace 'System'. This type has been forwarded to assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' Consider adding a reference to that assembly.
DiagnosticResult.CompilerError("CS1069").WithSpan(5, 28, 5, 35).WithArguments("Boolean", "System", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
// /0/Test0.cs(5,43): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(5, 43, 5, 49).WithArguments("System.Object"),
// /0/Test0.cs(5,50): error CS1069: The type name 'Object' could not be found in the namespace 'System'. This type has been forwarded to assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' Consider adding a reference to that assembly.
DiagnosticResult.CompilerError("CS1069").WithSpan(5, 50, 5, 56).WithArguments("Object", "System", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
// /0/Test0.cs(7,9): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(7, 9, 7, 15).WithArguments("System.Object"),
// /0/Test0.cs(7,32): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(7, 32, 7, 38).WithArguments("System.Object"),
// /0/Test0.cs(8,17): error CS0103: The name 'ReferenceEquals' does not exist in the current context
DiagnosticResult.CompilerError("CS0103").WithSpan(8, 17, 8, 32).WithArguments("ReferenceEquals"),
// /0/Test0.cs(8,17): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(8, 17, 8, 32).WithArguments("System.Object"),
// /0/Test0.cs(9,16): error CS0518: Predefined type 'System.Boolean' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(9, 16, 9, 29).WithArguments("System.Boolean"),
// /0/Test0.cs(12,12): error CS0518: Predefined type 'System.Void' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(12, 12, 12, 16).WithArguments("System.Void"),
// /0/Test0.cs(16,21): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(16, 21, 16, 27).WithArguments("System.Object"),
// /0/Test0.cs(16,28): error CS1069: The type name 'Int32' could not be found in the namespace 'System'. This type has been forwarded to assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' Consider adding a reference to that assembly.
DiagnosticResult.CompilerError("CS1069").WithSpan(16, 28, 16, 33).WithArguments("Int32", "System", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
// /0/Test0.cs(18,16): error CS0518: Predefined type 'System.Int32' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(18, 16, 18, 25).WithArguments("System.Int32"),
// /0/Test0.cs(18,28): error CS0103: The name 'EqualityComparer' does not exist in the current context
DiagnosticResult.CompilerError("CS0103").WithSpan(18, 28, 18, 58).WithArguments("EqualityComparer"),
// /0/Test0.cs(18,28): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(18, 28, 18, 58).WithArguments("System.Object"),
// /0/Test0.cs(18,45): error CS0518: Predefined type 'System.Object' is not defined or imported
DiagnosticResult.CompilerError("CS0518").WithSpan(18, 45, 18, 51).WithArguments("System.Object"),
// /0/Test0.cs(18,52): error CS1069: The type name 'Int32' could not be found in the namespace 'System'. This type has been forwarded to assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' Consider adding a reference to that assembly.
DiagnosticResult.CompilerError("CS1069").WithSpan(18, 52, 18, 57).WithArguments("Int32", "System", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
},
},
ReferenceAssemblies = ReferenceAssemblies.Default.WithAssemblies(ImmutableArray<string>.Empty),
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeInCheckedContext()
{
var code =
"""
using System.Collections.Generic;
class Program
{
[|int i;
string S { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Program
{
int i;
string S { get; }
public override bool Equals(object obj)
{
Program program = obj as Program;
return !ReferenceEquals(program, null) &&
i == program.i &&
S == program.S;
}
public override int GetHashCode()
{
unchecked
{
int hashCode = -538000506;
hashCode = hashCode * -1521134295 + i.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(S);
return hashCode;
}
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
SolutionTransforms =
{
(solution, projectId) =>
{
var compilationOptions = solution.GetRequiredProject(projectId).CompilationOptions;
return solution.WithProjectCompilationOptions(projectId, compilationOptions.WithOverflowChecks(true));
},
},
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeStruct()
{
var code =
"""
using System.Collections.Generic;
struct S
{
[|int j;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
struct S : IEquatable<S>
{
int j;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j;
}
public override int GetHashCode()
{
return 1424088837 + j.GetHashCode();
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeSystemHashCodeOneMember()
{
var code =
"""
using System.Collections.Generic;
namespace System { public struct HashCode { } }
struct S
{
[|int j;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
namespace System { public struct HashCode { } }
struct S : IEquatable<S>
{
int j;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j;
}
public override int GetHashCode()
{
return HashCode.Combine(j);
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
// /0/Test0.cs(21,25): error CS0117: 'HashCode' does not contain a definition for 'Combine'
DiagnosticResult.CompilerError("CS0117").WithSpan(21, 25, 21, 32).WithArguments("System.HashCode", "Combine"),
},
},
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37297")]
public async Task TestPublicSystemHashCodeOtherProject()
{
var publicHashCode =
"""
using System.Collections.Generic;
namespace System { public struct HashCode { } }
""";
var code =
"""
struct S
{
[|int j;|]
}
""";
var fixedCode =
"""
using System;
struct S : IEquatable<S>
{
int j;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j;
}
public override int GetHashCode()
{
return HashCode.Combine(j);
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestState =
{
AdditionalProjects =
{
["P1"] =
{
Sources = { ("HashCode.cs", publicHashCode) },
},
},
Sources = { code },
AdditionalProjectReferences = { "P1" },
},
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
// /0/Test0.cs(19,25): error CS0117: 'HashCode' does not contain a definition for 'Combine'
DiagnosticResult.CompilerError("CS0117").WithSpan(19, 25, 19, 32).WithArguments("System.HashCode", "Combine"),
},
},
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37297")]
public async Task TestInternalSystemHashCode()
{
var internalHashCode =
"""
using System.Collections.Generic;
namespace System { internal struct HashCode { } }
""";
var code =
"""
struct S
{
[|int j;|]
}
""";
var fixedCode =
"""
using System;
struct S : IEquatable<S>
{
int j;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j;
}
public override int GetHashCode()
{
return 1424088837 + j.GetHashCode();
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestState =
{
AdditionalProjects =
{
["P1"] =
{
Sources = { ("HashCode.cs", internalHashCode) },
},
},
Sources = { code },
AdditionalProjectReferences = { "P1" },
},
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeSystemHashCodeEightMembers()
{
var code =
"""
using System.Collections.Generic;
namespace System { public struct HashCode { } }
struct S
{
[|int j, k, l, m, n, o, p, q;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
namespace System { public struct HashCode { } }
struct S : IEquatable<S>
{
int j, k, l, m, n, o, p, q;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j &&
k == other.k &&
l == other.l &&
m == other.m &&
n == other.n &&
o == other.o &&
p == other.p &&
q == other.q;
}
public override int GetHashCode()
{
return HashCode.Combine(j, k, l, m, n, o, p, q);
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
// /0/Test0.cs(28,25): error CS0117: 'HashCode' does not contain a definition for 'Combine'
DiagnosticResult.CompilerError("CS0117").WithSpan(28, 25, 28, 32).WithArguments("System.HashCode", "Combine"),
},
},
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestGetHashCodeSystemHashCodeNineMembers()
{
var code =
"""
using System.Collections.Generic;
namespace System { public struct HashCode { public void Add<T>(T value) { } public int ToHashCode() => 0; } }
struct S
{
[|int j, k, l, m, n, o, p, q, r;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
namespace System { public struct HashCode { public void Add<T>(T value) { } public int ToHashCode() => 0; } }
struct S : IEquatable<S>
{
int j, k, l, m, n, o, p, q, r;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j &&
k == other.k &&
l == other.l &&
m == other.m &&
n == other.n &&
o == other.o &&
p == other.p &&
q == other.q &&
r == other.r;
}
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(j);
hash.Add(k);
hash.Add(l);
hash.Add(m);
hash.Add(n);
hash.Add(o);
hash.Add(p);
hash.Add(q);
hash.Add(r);
return hash.ToHashCode();
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/39916")]
public async Task TestGetHashCodeSystemHashCodeNineMembers_Explicit()
{
var code =
"""
using System.Collections.Generic;
namespace System { public struct HashCode { public void Add<T>(T value) { } public int ToHashCode() => 0; } }
struct S
{
[|int j, k, l, m, n, o, p, q, r;|]
}
""";
var fixedCode =
"""
using System;
using System.Collections.Generic;
namespace System { public struct HashCode { public void Add<T>(T value) { } public int ToHashCode() => 0; } }
struct S : IEquatable<S>
{
int j, k, l, m, n, o, p, q, r;
public override bool Equals(object obj)
{
return obj is S && Equals((S)obj);
}
public bool Equals(S other)
{
return j == other.j &&
k == other.k &&
l == other.l &&
m == other.m &&
n == other.n &&
o == other.o &&
p == other.p &&
q == other.q &&
r == other.r;
}
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(j);
hash.Add(k);
hash.Add(l);
hash.Add(m);
hash.Add(n);
hash.Add(o);
hash.Add(p);
hash.Add(q);
hash.Add(r);
return hash.ToHashCode();
}
public static bool operator ==(S left, S right)
{
return left.Equals(right);
}
public static bool operator !=(S left, S right)
{
return !(left == right);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.CSharp6,
Options = { PreferExplicitTypeWithInfo() },
}.RunAsync();
}
[Fact]
public async Task TestEqualsSingleField_Patterns()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System.Collections.Generic;
class Program
{
[|int a;|]
}
""",
"""
using System.Collections.Generic;
class Program
{
int a;
public override bool Equals(object obj)
{
return obj is Program program &&
a == program.a;
}
}
""");
}
[Fact]
public async Task TestEqualsSingleFieldInStruct_Patterns()
{
await VerifyCS.VerifyRefactoringAsync(
"""
using System.Collections.Generic;
struct Program
{
[|int a;|]
}
""",
"""
using System;
using System.Collections.Generic;
struct Program : IEquatable<Program>
{
int a;
public override bool Equals(object obj)
{
return obj is Program program && Equals(program);
}
public bool Equals(Program other)
{
return a == other.a;
}
public static bool operator ==(Program left, Program right)
{
return left.Equals(right);
}
public static bool operator !=(Program left, Program right)
{
return !(left == right);
}
}
""");
}
[Fact]
public async Task TestEqualsBaseWithOverriddenEquals_Patterns()
{
var code =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Program : Base
{
[|int i;
string S { get; }|]
}
""";
var fixedCode =
"""
using System.Collections.Generic;
class Base
{
public override bool Equals(object o)
{
return false;
}
}
class Program : Base
{
int i;
string S { get; }
public override bool Equals(object obj)
{
return obj is Program program &&
base.Equals(obj) &&
i == program.i &&
S == program.S;
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedCode = fixedCode,
CodeActionIndex = 0,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/33601")]
public async Task TestPartialSelection()
{
var code =
"""
using System.Collections.Generic;
class Program
{
int [|a|];
}
""";
await new VerifyCS.Test
{
TestCode = code,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40053")]
public async Task TestEqualityOperatorsNullableAnnotationWithReferenceType()
{
var code =
"""
#nullable enable
using System;
namespace N
{
public class C[||]
{
public int X;
}
}
""";
var fixedCode =
"""
#nullable enable
using System;
using System.Collections.Generic;
namespace N
{
public class C
{
public int X;
public override bool Equals(object? obj)
{
return obj is C c &&
X == c.X;
}
public static bool operator ==(C? left, C? right)
{
return EqualityComparer<C>.Default.Equals(left, right);
}
public static bool operator !=(C? left, C? right)
{
return !(left == right);
}
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
// /0/Test0.cs(20,55): error CS8604: Possible null reference argument for parameter 'x' in 'bool EqualityComparer<C>.Equals(C x, C y)'.
DiagnosticResult.CompilerError("CS8604").WithSpan(19, 55, 19, 59).WithArguments("x", "bool EqualityComparer<C>.Equals(C x, C y)"),
// /0/Test0.cs(20,61): error CS8604: Possible null reference argument for parameter 'y' in 'bool EqualityComparer<C>.Equals(C x, C y)'.
DiagnosticResult.CompilerError("CS8604").WithSpan(19, 61, 19, 66).WithArguments("y", "bool EqualityComparer<C>.Equals(C x, C y)"),
},
},
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId),
LanguageVersion = LanguageVersion.Default,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40053")]
public async Task TestEqualityOperatorsNullableAnnotationWithValueType()
{
var code =
"""
#nullable enable
using System;
namespace N
{
public struct C[||]
{
public int X;
}
}
""";
var fixedCode =
"""
#nullable enable
using System;
namespace N
{
public struct C
{
public int X;
public override bool Equals(object? obj)
{
return obj is C c &&
X == c.X;
}
public static bool operator ==(C left, C right)
{
return left.Equals(right);
}
public static bool operator !=(C left, C right)
{
return !(left == right);
}
}
}
""";
await new TestWithDialog
{
TestCode = code,
FixedCode = fixedCode,
MemberNames = default,
OptionsCallback = options => EnableOption(options, GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId),
LanguageVersion = LanguageVersion.Default,
Options = { PreferImplicitTypeWithInfo() },
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42574")]
public async Task TestPartialTypes1()
{
await new TestWithDialog
{
TestState =
{
Sources =
{
"""
partial class Goo
{
int bar;
[||]
}
""",
"""
partial class Goo
{
}
""",
},
},
FixedState =
{
Sources =
{
"""
partial class Goo
{
int bar;
public override bool Equals(object obj)
{
return obj is Goo goo &&
bar == goo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""",
"""
partial class Goo
{
}
""",
},
},
MemberNames = ImmutableArray.Create("bar"),
CodeActionIndex = 1,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42574")]
public async Task TestPartialTypes2()
{
await new TestWithDialog
{
TestState =
{
Sources =
{
"""
partial class Goo
{
int bar;
}
""",
"""
partial class Goo
{
[||]
}
""",
},
},
FixedState =
{
Sources =
{
"""
partial class Goo
{
int bar;
}
""",
"""
partial class Goo
{
public override bool Equals(object obj)
{
return obj is Goo goo &&
bar == goo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""",
},
},
MemberNames = ImmutableArray.Create("bar"),
CodeActionIndex = 1,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42574")]
public async Task TestPartialTypes3()
{
await new TestWithDialog
{
TestState =
{
Sources =
{
"""
partial class Goo
{
[||]
}
""",
"""
partial class Goo
{
int bar;
}
""",
},
},
FixedState =
{
Sources =
{
"""
partial class Goo
{
public override bool Equals(object obj)
{
return obj is Goo goo &&
bar == goo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""",
"""
partial class Goo
{
int bar;
}
""",
},
},
MemberNames = ImmutableArray.Create("bar"),
CodeActionIndex = 1,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42574")]
public async Task TestPartialTypes4()
{
await new TestWithDialog
{
TestState =
{
Sources =
{
"""
partial class Goo
{
}
""",
"""
partial class Goo
{
int bar;
[||]
}
""",
},
},
FixedState =
{
Sources =
{
"""
partial class Goo
{
}
""",
"""
partial class Goo
{
int bar;
public override bool Equals(object obj)
{
return obj is Goo goo &&
bar == goo.bar;
}
public override int GetHashCode()
{
return 999205674 + bar.GetHashCode();
}
}
""",
},
},
MemberNames = ImmutableArray.Create("bar"),
CodeActionIndex = 1,
}.RunAsync();
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43290")]
public async Task TestAbstractBase()
{
var code =
"""
#nullable enable
namespace System { public struct HashCode { } }
abstract class Base
{
public abstract override bool Equals(object? obj);
public abstract override int GetHashCode();
}
class {|CS0534:{|CS0534:Derived|}|} : Base
{
[|public int P { get; }|]
}
""";
var fixedCode =
"""
#nullable enable
using System;
namespace System { public struct HashCode { } }
abstract class Base
{
public abstract override bool Equals(object? obj);
public abstract override int GetHashCode();
}
class Derived : Base
{
public int P { get; }
public override bool Equals(object? obj)
{
return obj is Derived derived &&
P == derived.P;
}
public override int GetHashCode()
{
return HashCode.Combine(P);
}
}
""";
await new VerifyCS.Test
{
TestCode = code,
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
// /0/Test0.cs(23,25): error CS0117: 'HashCode' does not contain a definition for 'Combine'
DiagnosticResult.CompilerError("CS0117").WithSpan(25, 25, 25, 32).WithArguments("System.HashCode", "Combine"),
},
},
CodeActionIndex = 1,
LanguageVersion = LanguageVersion.Default,
}.RunAsync();
}
}
|