Skip to content

Commit ee19cc8

Browse files
authored
Merge pull request #21 from bmazzarol/feat/support-non-static
feat: allow `static` to be dropped from refinement methods
2 parents 71a6968 + 40bc338 commit ee19cc8

File tree

5 files changed

+168
-3
lines changed

5 files changed

+168
-3
lines changed

Tuxedo.Examples/Examples.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Examples;
1010
public readonly partial struct PositiveInt
1111
{
1212
[Refinement("Must be positive", Name = nameof(PositiveInt))]
13-
private static bool IsPositive(int value) => value > 0;
13+
private bool IsPositive(int value) => value > 0;
1414
}
1515

1616
/// <summary>

Tuxedo.Examples/Tuxedo.Examples.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
89
</PropertyGroup>
910
<ItemGroup>
1011
<ProjectReference Include="..\Tuxedo.SourceGenerator\Tuxedo.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>

Tuxedo.SourceGenerator/Generators/RefinementSourceGenerator.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4242
private static bool IsRefinementMethod(SyntaxNode s, CancellationToken cancellationToken)
4343
{
4444
return s is MethodDeclarationSyntax mds
45-
&& mds.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))
45+
// the method is either static or within the struct we are refining
46+
&& IsStaticOrWithinGeneratedType(mds)
4647
// and returns a bool or a string
4748
&& (
4849
string.Equals(mds.ReturnType.ToString(), "bool", StringComparison.Ordinal)
@@ -58,6 +59,24 @@ private static bool IsRefinementMethod(SyntaxNode s, CancellationToken cancellat
5859
);
5960
}
6061

62+
private static bool IsStaticOrWithinGeneratedType(MethodDeclarationSyntax mds)
63+
{
64+
var attributeParts = new RefinementAttributeParts(mds);
65+
return
66+
// either the method is static
67+
mds.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))
68+
// or the method is within the struct we are refining
69+
|| mds.Ancestors()
70+
.OfType<StructDeclarationSyntax>()
71+
.Any(tds =>
72+
string.Equals(
73+
tds.Identifier.Text,
74+
attributeParts.Name,
75+
StringComparison.Ordinal
76+
)
77+
);
78+
}
79+
6180
private static RefinedTypeDetails BuildRefinedTypeDetails(
6281
GeneratorAttributeSyntaxContext ctx,
6382
CancellationToken token
@@ -87,7 +106,8 @@ CancellationToken token
87106
var containingType = methodSymbol.ContainingType;
88107
var @class = containingType.ToDisplayString();
89108
var name = methodDeclarationSyntax.Identifier.Text;
90-
var predicate = $"{@class}.{name}";
109+
var isStatic = methodSymbol.IsStatic;
110+
var predicate = isStatic ? $"{@class}.{name}" : name;
91111

92112
// get the attribute details
93113
var attributeParts = new RefinementAttributeParts(methodDeclarationSyntax);
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//HintName: Odd.g.cs
2+
// <auto-generated/>
3+
#nullable enable
4+
5+
using System;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Numerics;
8+
using Tuxedo;
9+
10+
namespace <global namespace>;
11+
12+
/// <summary>
13+
/// A refined T based on the IsValid refinement predicate
14+
/// </summary>
15+
[RefinedType]
16+
public readonly partial struct Odd<T> : IEquatable<Odd<T>>
17+
where T : INumberBase<T>
18+
{
19+
private readonly T? _value;
20+
21+
/// <summary>
22+
/// The underlying T
23+
/// </summary>
24+
public T Value => _value ?? throw new InvalidOperationException("Do not use the default value, please use the Parse and TryParse methods to construct a Odd");
25+
26+
/// <summary>
27+
/// Implicit conversion from the Odd&lt;T&gt; to a T
28+
/// </summary>
29+
/// <param name="this">the Odd&lt;T&gt;</param>
30+
/// <returns>underlying T</returns>
31+
public static implicit operator T(Odd<T> @this)
32+
{
33+
return @this.Value;
34+
}
35+
36+
private Odd(T value)
37+
{
38+
_value = value;
39+
}
40+
41+
/// <summary>
42+
/// Explicit conversion from a T to a Odd&lt;T&gt;
43+
/// </summary>
44+
/// <param name="value">raw T</param>
45+
/// <returns>refined Odd&lt;T&gt;</returns>
46+
/// <exception cref="ArgumentOutOfRangeException">if the IsValid refinement fails</exception>
47+
public static explicit operator Odd<T>(T value)
48+
{
49+
return Parse(value);
50+
}
51+
52+
/// <summary>
53+
/// Refines the T or throws
54+
/// </summary>
55+
/// <param name="value">raw T</param>
56+
/// <returns>refined Odd&lt;T&gt;</returns>
57+
/// <exception cref="ArgumentOutOfRangeException">if the IsValid refinement fails</exception>
58+
public static Odd<T> Parse(T value)
59+
{
60+
return TryParse(value, out var result, out var failureMessage) ? result : throw new ArgumentOutOfRangeException(nameof(value), value, failureMessage);
61+
}
62+
63+
/// <summary>
64+
/// Try and refine the T against the IsValid refinement
65+
/// </summary>
66+
/// <param name="value">raw T</param>
67+
/// <param name="refined">refined Odd&lt;T&gt; when true</param>
68+
/// <param name="failureMessage">error message when false</param>
69+
/// <returns>true if refined, false otherwise</returns>
70+
public static bool TryParse(
71+
T value,
72+
out Odd<T> refined,
73+
[NotNullWhen(false)] out string? failureMessage
74+
)
75+
{
76+
if (IsValid(value))
77+
{
78+
refined = new Odd<T>(value);
79+
failureMessage = null;
80+
return true;
81+
}
82+
83+
refined = default;
84+
failureMessage = $"The number must be an odd number, but was '{value}'";
85+
return false;
86+
}
87+
88+
// <inheritdoc />
89+
public bool Equals(Odd<T> other)
90+
{
91+
return Nullable.Equals(_value, other._value);
92+
}
93+
94+
/// <inheritdoc />
95+
public override bool Equals(object? obj)
96+
{
97+
return obj is Odd<T> other && Equals(other);
98+
}
99+
100+
/// <inheritdoc />
101+
public static bool operator ==(Odd<T> left, Odd<T> right)
102+
{
103+
return left.Equals(right);
104+
}
105+
106+
/// <inheritdoc />
107+
public static bool operator !=(Odd<T> left, Odd<T> right)
108+
{
109+
return !(left == right);
110+
}
111+
112+
/// <inheritdoc />
113+
public override int GetHashCode()
114+
{
115+
return HashCode.Combine(_value);
116+
}
117+
118+
/// <summary>
119+
/// Returns the string representation of the underlying T
120+
/// </summary>
121+
public override string ToString()
122+
{
123+
return Value.ToString() ?? string.Empty;
124+
}
125+
}

Tuxedo.Tests/OddNumberExample.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,23 @@ public readonly partial struct Odd<T>
113113
""".BuildDriver();
114114
return Verify(driver).IgnoreStandardSupportCode();
115115
}
116+
117+
[Fact(
118+
DisplayName = "OddT refinement snapshot is correct with generics and constraints on the class and no static modifier"
119+
)]
120+
public Task Case8()
121+
{
122+
var driver = """
123+
using Tuxedo;
124+
using System.Numerics;
125+
126+
public readonly partial struct Odd<T>
127+
where T : INumberBase<T>
128+
{
129+
[Refinement("The number must be an odd number, but was '{value}'", Name = "Odd")]
130+
private bool IsValid(T value) => T.IsOddInteger(value);
131+
}
132+
""".BuildDriver();
133+
return Verify(driver).IgnoreStandardSupportCode();
134+
}
116135
}

0 commit comments

Comments
 (0)