Skip to content

Commit

Permalink
Merge 2045c6a into cba483e
Browse files Browse the repository at this point in the history
  • Loading branch information
salvois committed Oct 12, 2022
2 parents cba483e + 2045c6a commit 030c876
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 6 deletions.
18 changes: 12 additions & 6 deletions Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,12 +584,18 @@ private static bool IsAnonymousType(this Type type)
public static bool IsRecord(this Type type)
{
return TypeIsRecordCache.GetOrAdd(type, static t =>
t.GetMethod("<Clone>$") is not null &&
t.GetTypeInfo()
.DeclaredProperties
.FirstOrDefault(p => p.Name == "EqualityContract")?
.GetMethod?
.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is not null);
{
bool isRecord = t.GetMethod("<Clone>$") is not null &&
t.GetTypeInfo()
.DeclaredProperties
.FirstOrDefault(p => p.Name == "EqualityContract")?
.GetMethod?
.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is not null;
bool isRecordStruct = t.BaseType == typeof(ValueType) &&
t.GetMethods().Where(m => m.Name == "op_Inequality").SelectMany(m => m.GetCustomAttributes(typeof(CompilerGeneratedAttribute))).Any() &&
t.GetMethods().Where(m => m.Name == "op_Equality").SelectMany(m => m.GetCustomAttributes(typeof(CompilerGeneratedAttribute))).Any();
return isRecord || isRecordStruct;
});
}

private static bool IsKeyValuePair(Type type)
Expand Down
15 changes: 15 additions & 0 deletions Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ public void When_the_subject_is_a_record_it_should_compare_it_by_its_members()
actual.Should().BeEquivalentTo(expected);
}

[Fact]
public void When_the_subject_is_a_readonly_record_struct_it_should_compare_it_by_its_members()
{
var actual = new MyReadonlyRecordStruct("foo", new[] { "bar", "zip", "foo" });

var expected = new MyReadonlyRecordStruct("foo", new[] { "bar", "zip", "foo" });

actual.Should().BeEquivalentTo(expected);
}

[Fact]
public void When_the_subject_is_a_record_it_should_mention_that_in_the_configuration_output()
{
Expand Down Expand Up @@ -90,4 +100,9 @@ private record MyRecord

public string[] CollectionProperty { get; init; }
}

private readonly record struct MyReadonlyRecordStruct(string StringField, string[] CollectionProperty)
{
public readonly string StringField = StringField;
}
}
66 changes: 66 additions & 0 deletions Tests/FluentAssertions.Specs/Types/TypeExtensionsSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using FluentAssertions.Common;
Expand Down Expand Up @@ -124,6 +125,33 @@ public void When_getting_fake_implicit_conversion_operator_from_a_type_with_fake
result.Should().NotBeNull();
}

[Theory]
[InlineData(typeof(MyRecord), true)]
[InlineData(typeof(MyRecordStruct), true)]
[InlineData(typeof(MyRecordStructWithOverriddenEquality), true)]
[InlineData(typeof(MyReadonlyRecordStruct), true)]
[InlineData(typeof(MyStruct), false)]
[InlineData(typeof(MyStructWithOverriddenEquality), false)]
[InlineData(typeof(MyClass), false)]
[InlineData(typeof(int), false)]
[InlineData(typeof(string), false)]
public void IsRecord_should_detect_records_correctly(Type type, bool expected)
{
type.IsRecord().Should().Be(expected);
}

[Fact]
public void When_checking_if_anonymous_type_is_record_it_should_return_false()
{
new { Value = 42 }.GetType().IsRecord().Should().BeFalse();
}

[Fact]
public void When_checking_if_class_with_multiple_equality_methods_is_record_it_should_return_false()
{
typeof(ImmutableArray<int>).IsRecord().Should().BeFalse();
}

private static MethodInfo GetFakeConversionOperator(Type type, string name, BindingFlags bindingAttr, Type returnType)
{
MethodInfo[] methods = type.GetMethods(bindingAttr);
Expand Down Expand Up @@ -153,4 +181,42 @@ private TypeWithFakeConversionOperators(int value)
public static byte op_Explicit(TypeWithFakeConversionOperators typeWithFakeConversionOperators) => (byte)typeWithFakeConversionOperators.value;
#pragma warning restore SA1300, IDE1006
}

private record MyRecord(int Value);

private record struct MyRecordStruct(int Value);

private record struct MyRecordStructWithOverriddenEquality(int Value)
{
public bool Equals(MyRecordStructWithOverriddenEquality other) => Value == other.Value;

public override int GetHashCode() => Value;
}

private readonly record struct MyReadonlyRecordStruct(int Value);

private struct MyStruct
{
public int Value { get; set; }
}

private struct MyStructWithOverriddenEquality : IEquatable<MyStructWithOverriddenEquality>
{
public int Value { get; set; }

public bool Equals(MyStructWithOverriddenEquality other) => Value == other.Value;

public override bool Equals(object obj) => obj is MyStructWithOverriddenEquality other && Equals(other);

public override int GetHashCode() => Value;

public static bool operator ==(MyStructWithOverriddenEquality left, MyStructWithOverriddenEquality right) => left.Equals(right);

public static bool operator !=(MyStructWithOverriddenEquality left, MyStructWithOverriddenEquality right) => !left.Equals(right);
}

private class MyClass
{
public int Value { get; set; }
}
}

0 comments on commit 030c876

Please sign in to comment.