Skip to content

Commit 7b21a1e

Browse files
committed
#336 Fixed the property case
1 parent 97171f7 commit 7b21a1e

File tree

4 files changed

+322
-26
lines changed

4 files changed

+322
-26
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
I think the issue is line 52 in `MockablePropertyDiscovery`. It shouldn't be limited to just `static` properties. **Any** property can hide one we already found with `new`. Probably need to do the same thing with methods.
2+
3+
Also, `AreParametersEqual()` should use `SymbolEqualityComparer` on line 30. Change+test separately.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-

1+


src/Rocks.Tests/Generators/InheritanceGeneratorTests.cs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,309 @@ namespace Rocks.Tests.Generators;
44

55
public static class InheritanceGeneratorTests
66
{
7+
[Test]
8+
public static async Task GenerateWhenNewPropertyIsIntroducedAsync()
9+
{
10+
var code =
11+
"""
12+
#nullable enable
13+
14+
using Rocks;
15+
using System;
16+
17+
[assembly: Rock(typeof(BindableReactiveProperty<>), BuildType.Create | BuildType.Make)]
18+
19+
public class ReactiveProperty<T>
20+
{
21+
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
22+
public virtual T Value { get; set; }
23+
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
24+
}
25+
26+
public class BindableReactiveProperty<T>
27+
: ReactiveProperty<T>
28+
{
29+
public new T Value
30+
{
31+
get => base.Value;
32+
set => base.Value = value;
33+
}
34+
}
35+
""";
36+
37+
var createGeneratedCode =
38+
""""
39+
// <auto-generated/>
40+
41+
#pragma warning disable CS8618
42+
#pragma warning disable CS8633
43+
#pragma warning disable CS8714
44+
#pragma warning disable CS8775
45+
46+
#nullable enable
47+
48+
using Rocks.Extensions;
49+
50+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
51+
internal sealed class BindableReactivePropertyCreateExpectations<T>
52+
: global::Rocks.Expectations
53+
{
54+
internal sealed class Handler0
55+
: global::Rocks.Handler<global::System.Func<object?, bool>, bool>
56+
{
57+
public global::Rocks.Argument<object?> @obj { get; set; }
58+
}
59+
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler0>? @handlers0;
60+
internal sealed class Handler1
61+
: global::Rocks.Handler<global::System.Func<int>, int>
62+
{ }
63+
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler1>? @handlers1;
64+
internal sealed class Handler2
65+
: global::Rocks.Handler<global::System.Func<string?>, string?>
66+
{ }
67+
private global::Rocks.Handlers<global::BindableReactivePropertyCreateExpectations<T>.Handler2>? @handlers2;
68+
69+
public override void Verify()
70+
{
71+
if (this.WasInstanceInvoked)
72+
{
73+
var failures = new global::System.Collections.Generic.List<string>();
74+
75+
if (this.handlers0 is not null) { failures.AddRange(this.Verify(this.handlers0, 0)); }
76+
if (this.handlers1 is not null) { failures.AddRange(this.Verify(this.handlers1, 1)); }
77+
if (this.handlers2 is not null) { failures.AddRange(this.Verify(this.handlers2, 2)); }
78+
79+
if (failures.Count > 0)
80+
{
81+
throw new global::Rocks.Exceptions.VerificationException(failures);
82+
}
83+
}
84+
}
85+
86+
private sealed class Mock
87+
: global::BindableReactiveProperty<T>
88+
{
89+
public Mock(global::BindableReactivePropertyCreateExpectations<T> @expectations)
90+
{
91+
this.Expectations = @expectations;
92+
}
93+
94+
[global::Rocks.MemberIdentifier(0)]
95+
public override bool Equals(object? @obj)
96+
{
97+
if (this.Expectations.handlers0 is not null)
98+
{
99+
foreach (var @handler in this.Expectations.handlers0)
100+
{
101+
if (@handler.@obj.IsValid(@obj!))
102+
{
103+
@handler.CallCount++;
104+
var @result = @handler.Callback is not null ?
105+
@handler.Callback(@obj!) : @handler.ReturnValue;
106+
return @result!;
107+
}
108+
}
109+
110+
throw new global::Rocks.Exceptions.ExpectationException(
111+
$"""
112+
No handlers match for {this.GetType().GetMemberDescription(0)}
113+
obj: {@obj.FormatValue()}
114+
""");
115+
}
116+
else
117+
{
118+
return base.Equals(@obj: @obj!);
119+
}
120+
}
121+
122+
[global::Rocks.MemberIdentifier(1)]
123+
public override int GetHashCode()
124+
{
125+
if (this.Expectations.handlers1 is not null)
126+
{
127+
var @handler = this.Expectations.handlers1.First;
128+
@handler.CallCount++;
129+
var @result = @handler.Callback is not null ?
130+
@handler.Callback() : @handler.ReturnValue;
131+
return @result!;
132+
}
133+
else
134+
{
135+
return base.GetHashCode();
136+
}
137+
}
138+
139+
[global::Rocks.MemberIdentifier(2)]
140+
public override string? ToString()
141+
{
142+
if (this.Expectations.handlers2 is not null)
143+
{
144+
var @handler = this.Expectations.handlers2.First;
145+
@handler.CallCount++;
146+
var @result = @handler.Callback is not null ?
147+
@handler.Callback() : @handler.ReturnValue;
148+
return @result!;
149+
}
150+
else
151+
{
152+
return base.ToString();
153+
}
154+
}
155+
156+
private global::BindableReactivePropertyCreateExpectations<T> Expectations { get; }
157+
}
158+
159+
internal sealed class MethodExpectations
160+
{
161+
internal MethodExpectations(global::BindableReactivePropertyCreateExpectations<T> expectations) =>
162+
this.Expectations = expectations;
163+
164+
internal global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler0 Equals(global::Rocks.Argument<object?> @obj)
165+
{
166+
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
167+
global::System.ArgumentNullException.ThrowIfNull(@obj);
168+
169+
var @handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler0
170+
{
171+
@obj = @obj,
172+
};
173+
174+
if (this.Expectations.handlers0 is null) { this.Expectations.handlers0 = new(@handler); }
175+
else { this.Expectations.handlers0.Add(@handler); }
176+
return new(@handler);
177+
}
178+
179+
internal new global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler1 GetHashCode()
180+
{
181+
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
182+
var handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler1();
183+
if (this.Expectations.handlers1 is null) { this.Expectations.handlers1 = new(handler); }
184+
else { this.Expectations.handlers1.Add(handler); }
185+
return new(handler);
186+
}
187+
188+
internal new global::BindableReactivePropertyCreateExpectations<T>.Adornments.AdornmentsForHandler2 ToString()
189+
{
190+
global::Rocks.Exceptions.ExpectationException.ThrowIf(this.Expectations.WasInstanceInvoked);
191+
var handler = new global::BindableReactivePropertyCreateExpectations<T>.Handler2();
192+
if (this.Expectations.handlers2 is null) { this.Expectations.handlers2 = new(handler); }
193+
else { this.Expectations.handlers2.Add(handler); }
194+
return new(handler);
195+
}
196+
197+
private global::BindableReactivePropertyCreateExpectations<T> Expectations { get; }
198+
}
199+
200+
internal global::BindableReactivePropertyCreateExpectations<T>.MethodExpectations Methods { get; }
201+
202+
internal BindableReactivePropertyCreateExpectations() =>
203+
(this.Methods) = (new(this));
204+
205+
internal global::BindableReactiveProperty<T> Instance()
206+
{
207+
if (!this.WasInstanceInvoked)
208+
{
209+
this.WasInstanceInvoked = true;
210+
var @mock = new Mock(this);
211+
this.MockType = @mock.GetType();
212+
return @mock;
213+
}
214+
else
215+
{
216+
throw new global::Rocks.Exceptions.NewMockInstanceException("Can only create a new mock once.");
217+
}
218+
}
219+
220+
internal static class Adornments
221+
{
222+
public interface IAdornmentsForBindableReactiveProperty<TAdornments>
223+
: global::Rocks.IAdornments<TAdornments>
224+
where TAdornments : IAdornmentsForBindableReactiveProperty<TAdornments>
225+
{ }
226+
227+
public sealed class AdornmentsForHandler0
228+
: global::Rocks.Adornments<AdornmentsForHandler0, global::BindableReactivePropertyCreateExpectations<T>.Handler0, global::System.Func<object?, bool>, bool>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler0>
229+
{
230+
public AdornmentsForHandler0(global::BindableReactivePropertyCreateExpectations<T>.Handler0 handler)
231+
: base(handler) { }
232+
}
233+
public sealed class AdornmentsForHandler1
234+
: global::Rocks.Adornments<AdornmentsForHandler1, global::BindableReactivePropertyCreateExpectations<T>.Handler1, global::System.Func<int>, int>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler1>
235+
{
236+
public AdornmentsForHandler1(global::BindableReactivePropertyCreateExpectations<T>.Handler1 handler)
237+
: base(handler) { }
238+
}
239+
public sealed class AdornmentsForHandler2
240+
: global::Rocks.Adornments<AdornmentsForHandler2, global::BindableReactivePropertyCreateExpectations<T>.Handler2, global::System.Func<string?>, string?>, IAdornmentsForBindableReactiveProperty<AdornmentsForHandler2>
241+
{
242+
public AdornmentsForHandler2(global::BindableReactivePropertyCreateExpectations<T>.Handler2 handler)
243+
: base(handler) { }
244+
}
245+
}
246+
}
247+
248+
#pragma warning restore CS8618
249+
#pragma warning restore CS8633
250+
#pragma warning restore CS8714
251+
#pragma warning restore CS8775
252+
"""";
253+
254+
var makeGeneratedCode =
255+
"""
256+
// <auto-generated/>
257+
258+
#pragma warning disable CS8618
259+
#pragma warning disable CS8633
260+
#pragma warning disable CS8714
261+
#pragma warning disable CS8775
262+
263+
#nullable enable
264+
265+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
266+
internal sealed class BindableReactivePropertyMakeExpectations<T>
267+
{
268+
internal global::BindableReactiveProperty<T> Instance()
269+
{
270+
return new Mock();
271+
}
272+
273+
private sealed class Mock
274+
: global::BindableReactiveProperty<T>
275+
{
276+
public Mock()
277+
{
278+
}
279+
280+
public override bool Equals(object? @obj)
281+
{
282+
return default!;
283+
}
284+
public override int GetHashCode()
285+
{
286+
return default!;
287+
}
288+
public override string? ToString()
289+
{
290+
return default!;
291+
}
292+
}
293+
}
294+
295+
#pragma warning restore CS8618
296+
#pragma warning restore CS8633
297+
#pragma warning restore CS8714
298+
#pragma warning restore CS8775
299+
300+
""";
301+
302+
await TestAssistants.RunGeneratorAsync<RockGenerator>(code,
303+
[
304+
("BindableReactivePropertyT_Rock_Create.g.cs", createGeneratedCode),
305+
("BindableReactivePropertyT_Rock_Make.g.cs", makeGeneratedCode)
306+
],
307+
[]);
308+
}
309+
7310
[Test]
8311
public static async Task GenerateWhenMethodIsIntroducedAsync()
9312
{

src/Rocks/Discovery/MockablePropertyDiscovery.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ static bool AreParametersEqual(IPropertySymbol property1, IPropertySymbol proper
4949
foreach (var hierarchyProperty in hierarchyType.GetMembers().OfType<IPropertySymbol>()
5050
.Where(_ => _.IsIndexer || _.CanBeReferencedByName))
5151
{
52-
if (hierarchyProperty.IsStatic && hierarchyProperty.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol))
52+
var canBeSeen = hierarchyProperty.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol);
53+
54+
if (canBeSeen)
5355
{
5456
// This is the case where a class does something like this:
5557
// `protected static new int Data { get; }`
@@ -66,44 +68,32 @@ static bool AreParametersEqual(IPropertySymbol property1, IPropertySymbol proper
6668
properties.Remove(propertyToRemove);
6769
}
6870
}
69-
else if (!hierarchyProperty.IsStatic)
70-
{
71-
var canBeSeen = hierarchyProperty.CanBeSeenByContainingAssembly(containingAssemblyOfInvocationSymbol);
7271

72+
if (!hierarchyProperty.IsStatic)
73+
{
7374
if (!canBeSeen && hierarchyProperty.IsAbstract)
7475
{
7576
inaccessibleAbstractMembers = true;
7677
}
7778
else if (canBeSeen)
7879
{
79-
if (hierarchyProperty.IsAbstract || hierarchyProperty.IsOverride || hierarchyProperty.IsVirtual)
80+
if ((hierarchyProperty.IsAbstract || hierarchyProperty.IsOverride || hierarchyProperty.IsVirtual) &&
81+
!hierarchyProperty.IsSealed)
8082
{
81-
var propertyToRemove = properties.SingleOrDefault(_ => _.Value.Name == hierarchyProperty.Name &&
82-
!_.Value.ContainingType.Equals(hierarchyProperty.ContainingType) &&
83-
AreParametersEqual(_.Value, hierarchyProperty));
83+
var result = new MockablePropertyResult(
84+
hierarchyProperty, mockType, RequiresExplicitInterfaceImplementation.No, RequiresOverride.Yes, memberIdentifier);
85+
properties.Add(result);
8486

85-
if (propertyToRemove is not null)
87+
if (hierarchyProperty.ContainingType.TypeKind == TypeKind.Interface && hierarchyProperty.IsVirtual)
8688
{
87-
properties.Remove(propertyToRemove);
89+
shims.Add(hierarchyProperty.ContainingType);
8890
}
8991

90-
if (!hierarchyProperty.IsSealed)
91-
{
92-
var result = new MockablePropertyResult(
93-
hierarchyProperty, mockType, RequiresExplicitInterfaceImplementation.No, RequiresOverride.Yes, memberIdentifier);
94-
properties.Add(result);
95-
96-
if (hierarchyProperty.ContainingType.TypeKind == TypeKind.Interface && hierarchyProperty.IsVirtual)
97-
{
98-
shims.Add(hierarchyProperty.ContainingType);
99-
}
92+
memberIdentifier++;
10093

94+
if (result.Accessors == PropertyAccessor.GetAndSet || result.Accessors == PropertyAccessor.GetAndInit)
95+
{
10196
memberIdentifier++;
102-
103-
if (result.Accessors == PropertyAccessor.GetAndSet || result.Accessors == PropertyAccessor.GetAndInit)
104-
{
105-
memberIdentifier++;
106-
}
10797
}
10898
}
10999
}

0 commit comments

Comments
 (0)