Skip to content

Commit

Permalink
#336 did a minor update for method discovery as well
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonBock committed Oct 22, 2024
1 parent 7b21a1e commit 9a9156b
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 14 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Static virtual members on interfaces will not be processed (issue [#337](https://github.com/JasonBock/Rocks/issues/3327))
- Special types (`ArgIterator`, `RuntimeArgumentHandle`, `TypedReference`) used as parameter or return types are now handled (issue [#329](https://github.com/JasonBock/Rocks/issues/329))
- Nested generic types are now handled correctly (issue [#333](https://github.com/JasonBock/Rocks/issues/333))
- Members that hide other members are now handled correctly (issue [#336](https://github.com/JasonBock/Rocks/issues/336))

## [8.3.1] - 2024-09-30

Expand Down
297 changes: 297 additions & 0 deletions src/Rocks.Tests/Generators/InheritanceGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,303 @@ namespace Rocks.Tests.Generators;

public static class InheritanceGeneratorTests
{
[Test]
public static async Task GenerateWhenNewMethodIsIntroducedAsync()
{
var code =
"""
#nullable enable

using Rocks;
using System;

[assembly: Rock(typeof(BindableReactiveProperty<>), BuildType.Create | BuildType.Make)]

public class ReactiveProperty<T>
{
public virtual T RetrieveValue() => default!;
}

public class BindableReactiveProperty<T>
: ReactiveProperty<T>
{
public new T RetrieveValue() => default!;
}
""";

var createGeneratedCode =
""""
// <auto-generated/>

#pragma warning disable CS8618
#pragma warning disable CS8633
#pragma warning disable CS8714
#pragma warning disable CS8775

#nullable enable

using Rocks.Extensions;

[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class BindableReactivePropertyCreateExpectations<T>
: global::Rocks.Expectations
{
internal sealed class Handler0
: global::Rocks.Handler<global::System.Func<object?, bool>, bool>
{
public global::Rocks.Argument<object?> @obj { get; set; }
}
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler0>? @handlers0;
internal sealed class Handler1
: global::Rocks.Handler<global::System.Func<int>, int>
{ }
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler1>? @handlers1;
internal sealed class Handler2
: global::Rocks.Handler<global::System.Func<string?>, string?>
{ }
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler2>? @handlers2;

public override void Verify()
{
if (this.WasInstanceInvoked)
{
var failures = new global::System.Collections.Generic.List<string>();

if (this.handlers0 is not null) { failures.AddRange(this.Verify(this.handlers0, 0)); }
if (this.handlers1 is not null) { failures.AddRange(this.Verify(this.handlers1, 1)); }
if (this.handlers2 is not null) { failures.AddRange(this.Verify(this.handlers2, 2)); }

if (failures.Count > 0)
{
throw new global::Rocks.Exceptions.VerificationException(failures);
}
}
}

private sealed class Mock
: global::BindableReactiveProperty<T>
{
public Mock(global::BindableReactivePropertyCreateExpectations<T> @expectations)
{
this.Expectations = @expectations;
}

[global::Rocks.MemberIdentifier(0)]
public override bool Equals(object? @obj)
{
if (this.Expectations.handlers0 is not null)
{
foreach (var @handler in this.Expectations.handlers0)
{
if (@[email protected](@obj!))
{
@handler.CallCount++;
var @result = @handler.Callback is not null ?
@handler.Callback(@obj!) : @handler.ReturnValue;
return @result!;
}
}

throw new global::Rocks.Exceptions.ExpectationException(
$"""
No handlers match for {this.GetType().GetMemberDescription(0)}
obj: {@obj.FormatValue()}
""");
}
else
{
return base.Equals(@obj: @obj!);
}
}

[global::Rocks.MemberIdentifier(1)]
public override int GetHashCode()
{
if (this.Expectations.handlers1 is not null)
{
var @handler = this.Expectations.handlers1.First;
@handler.CallCount++;
var @result = @handler.Callback is not null ?
@handler.Callback() : @handler.ReturnValue;
return @result!;
}
else
{
return base.GetHashCode();
}
}

[global::Rocks.MemberIdentifier(2)]
public override string? ToString()
{
if (this.Expectations.handlers2 is not null)
{
var @handler = this.Expectations.handlers2.First;
@handler.CallCount++;
var @result = @handler.Callback is not null ?
@handler.Callback() : @handler.ReturnValue;
return @result!;
}
else
{
return base.ToString();
}
}

private global::BindableReactivePropertyCreateExpectations<T> Expectations { get; }
}

internal sealed class MethodExpectations
{
internal MethodExpectations(global::BindableReactivePropertyCreateExpectations<T> expectations) =>
this.Expectations = expectations;

internal global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler0 Equals(global::Rocks.Argument<object?> @obj)
{
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
global::System.ArgumentNullException.ThrowIfNull(@obj);

var @handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler0
{
@obj = @obj,
};

if (this.Expectations.handlers0 is null) { this.Expectations.handlers0 = new(@handler); }
else { this.Expectations.handlers0.Add(@handler); }
return new(@handler);
}

internal new global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler1 GetHashCode()
{
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
var handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler1();
if (this.Expectations.handlers1 is null) { this.Expectations.handlers1 = new(handler); }
else { this.Expectations.handlers1.Add(handler); }
return new(handler);
}

internal new global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler2 ToString()
{
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
var handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler2();
if (this.Expectations.handlers2 is null) { this.Expectations.handlers2 = new(handler); }
else { this.Expectations.handlers2.Add(handler); }
return new(handler);
}

private global::BindableReactivePropertyCreateExpectations<T> Expectations { get; }
}

internal global::BindableReactivePropertyCreateExpectations<T>.MethodExpectations Methods { get; }

internal BindableReactivePropertyCreateExpectations() =>
(this.Methods) = (new(this));

internal global::BindableReactiveProperty<T> Instance()
{
if (!this.WasInstanceInvoked)
{
this.WasInstanceInvoked = true;
var @mock = new Mock(this);
this.MockType = @mock.GetType();
return @mock;
}
else
{
throw new global::Rocks.Exceptions.NewMockInstanceException("Can only create a new mock once.");
}
}

internal static class Adornments
{
public interface IAdornmentsForBindableReactiveProperty<TAdornments>
: global::Rocks.IAdornments<TAdornments>
where TAdornments : IAdornmentsForBindableReactiveProperty<TAdornments>
{ }

public sealed class AdornmentsForHandler0
: global::Rocks.Adornments<AdornmentsForHandler0, global::BindableReactivePropertyCreateExpectations<T>.Handler0, global::System.Func<object?, bool>, bool>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler0>
{
public AdornmentsForHandler0(global::BindableReactivePropertyCreateExpectations<T>.Handler0 handler)
: base(handler) { }
}
public sealed class AdornmentsForHandler1
: global::Rocks.Adornments<AdornmentsForHandler1, global::BindableReactivePropertyCreateExpectations<T>.Handler1, global::System.Func<int>, int>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler1>
{
public AdornmentsForHandler1(global::BindableReactivePropertyCreateExpectations<T>.Handler1 handler)
: base(handler) { }
}
public sealed class AdornmentsForHandler2
: global::Rocks.Adornments<AdornmentsForHandler2, global::BindableReactivePropertyCreateExpectations<T>.Handler2, global::System.Func<string?>, string?>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler2>
{
public AdornmentsForHandler2(global::BindableReactivePropertyCreateExpectations<T>.Handler2 handler)
: base(handler) { }
}
}
}

#pragma warning restore CS8618
#pragma warning restore CS8633
#pragma warning restore CS8714
#pragma warning restore CS8775
"""";

var makeGeneratedCode =
"""
// <auto-generated/>

#pragma warning disable CS8618
#pragma warning disable CS8633
#pragma warning disable CS8714
#pragma warning disable CS8775

#nullable enable

[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class BindableReactivePropertyMakeExpectations<T>
{
internal global::BindableReactiveProperty<T> Instance()
{
return new Mock();
}

private sealed class Mock
: global::BindableReactiveProperty<T>
{
public Mock()
{
}

public override bool Equals(object? @obj)
{
return default!;
}
public override int GetHashCode()
{
return default!;
}
public override string? ToString()
{
return default!;
}
}
}

#pragma warning restore CS8618
#pragma warning restore CS8633
#pragma warning restore CS8714
#pragma warning restore CS8775

""";

await TestAssistants.RunGeneratorAsync<RockGenerator>(code,
[
("BindableReactivePropertyT_Rock_Create.g.cs", createGeneratedCode),
("BindableReactivePropertyT_Rock_Make.g.cs", makeGeneratedCode)
],
[]);
}

[Test]
public static async Task GenerateWhenNewPropertyIsIntroducedAsync()
{
Expand Down
19 changes: 6 additions & 13 deletions src/Rocks/Discovery/MockableMethodDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ private static MockableMethods GetMethodsForClass(ITypeSymbol mockType, IAssembl

foreach (var hierarchyMethod in hierarchyMethods)
{
if (hierarchyMethod.IsStatic && hierarchyMethod.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol))
var canBeSeen = hierarchyMethod.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol);

if (canBeSeen)
{
// This is the case where a class does something like this:
// `protected static new string ToString()`
Expand All @@ -67,27 +69,18 @@ private static MockableMethods GetMethodsForClass(ITypeSymbol mockType, IAssembl
methods.Remove(methodToRemove);
}
}
else if (!hierarchyMethod.IsStatic && (!mockType.IsRecord || hierarchyMethod.Name != nameof(Equals)))

if (!hierarchyMethod.IsStatic && (!mockType.IsRecord || hierarchyMethod.Name != nameof(Equals)))
{
if (hierarchyMethod.IsAbstract || hierarchyMethod.IsOverride || hierarchyMethod.IsVirtual)
{
var canBeSeen = hierarchyMethod.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol);

if (!canBeSeen && hierarchyMethod.IsAbstract)
{
inaccessibleAbstractMembers = true;
}
else if (canBeSeen)
{
var methodToRemove = methods.SingleOrDefault(_ => !(_.Value.Match(hierarchyMethod) == MethodMatch.None));

if (methodToRemove is not null)
{
methods.Remove(methodToRemove);
}

if ((methodToRemove is null || !methodToRemove.Value.ContainingType.Equals(hierarchyMethod.ContainingType)) &&
!hierarchyMethod.IsSealed)
if (!hierarchyMethod.IsSealed)
{
methods.Add(new(hierarchyMethod, mockType, RequiresExplicitInterfaceImplementation.No, RequiresOverride.Yes,
objectMethods.Any(_ => hierarchyMethod.Name == _.Name && hierarchyMethod.Parameters.Length == 0) ?
Expand Down
2 changes: 1 addition & 1 deletion src/Rocks/Discovery/MockablePropertyDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static bool AreParametersEqual(IPropertySymbol property1, IPropertySymbol proper
var property1Parameter = property1.Parameters[i];
var property2Parameter = property2.Parameters[i];

if (!property1Parameter.Type.Equals(property2Parameter.Type))
if (!SymbolEqualityComparer.Default.Equals(property1Parameter.Type, property2Parameter.Type))
{
return false;
}
Expand Down

0 comments on commit 9a9156b

Please sign in to comment.