Skip to content

Commit 2cceaa6

Browse files
authored
Merge pull request #727 from microsoft/fix719
Generate decimal/DECIMAL converters
2 parents ea3d883 + 1e1abee commit 2cceaa6

File tree

5 files changed

+118
-7
lines changed

5 files changed

+118
-7
lines changed

src/Microsoft.Windows.CsWin32/FastSyntaxFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ internal static SyntaxToken XmlTextNewLine(string text, bool continueXmlDocument
260260

261261
internal static MethodDeclarationSyntax MethodDeclaration(SyntaxList<AttributeListSyntax> attributeLists, SyntaxTokenList modifiers, TypeSyntax returnType, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, SyntaxToken identifier, TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses, BlockSyntax body, SyntaxToken semicolonToken) => SyntaxFactory.MethodDeclaration(attributeLists, modifiers, returnType.WithTrailingTrivia(TriviaList(Space)), explicitInterfaceSpecifier, identifier, typeParameterList, parameterList, constraintClauses, body, semicolonToken);
262262

263-
internal static MemberDeclarationSyntax? ParseMemberDeclaration(string text) => SyntaxFactory.ParseMemberDeclaration(text);
263+
internal static MemberDeclarationSyntax? ParseMemberDeclaration(string text, ParseOptions? options) => SyntaxFactory.ParseMemberDeclaration(text, options: options);
264264

265265
internal static SingleVariableDesignationSyntax SingleVariableDesignation(SyntaxToken identifier) => SyntaxFactory.SingleVariableDesignation(identifier);
266266

src/Microsoft.Windows.CsWin32/Generator.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,7 +2563,7 @@ private static NativeArrayInfo DecodeNativeArrayInfoAttribute(CustomAttribute na
25632563
return sr.ReadToEnd().Replace("\r\n", "\n").Replace("\t", string.Empty);
25642564
}
25652565

2566-
private static bool TryFetchTemplate(string name, Generator? visibilityModifier, [NotNullWhen(true)] out MemberDeclarationSyntax? member)
2566+
private static bool TryFetchTemplate(string name, Generator? generator, [NotNullWhen(true)] out MemberDeclarationSyntax? member)
25672567
{
25682568
string? template = FetchTemplateText(name);
25692569
if (template == null)
@@ -2572,8 +2572,15 @@ private static bool TryFetchTemplate(string name, Generator? visibilityModifier,
25722572
return false;
25732573
}
25742574

2575-
member = ParseMemberDeclaration(template) ?? throw new GenerationFailedException($"Unable to parse a type from a template: {name}");
2576-
member = visibilityModifier?.ElevateVisibility(member) ?? member;
2575+
member = ParseMemberDeclaration(template, generator?.parseOptions) ?? throw new GenerationFailedException($"Unable to parse a type from a template: {name}");
2576+
2577+
// Strip out #if/#else/#endif trivia, which was already evaluated with the parse options we passed in.
2578+
if (generator?.parseOptions is not null)
2579+
{
2580+
member = (MemberDeclarationSyntax)member.Accept(DirectiveTriviaRemover.Instance)!;
2581+
}
2582+
2583+
member = generator?.ElevateVisibility(member) ?? member;
25772584
return true;
25782585
}
25792586

@@ -3966,6 +3973,7 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle
39663973
case "RECT":
39673974
case "SIZE":
39683975
case "SYSTEMTIME":
3976+
case "DECIMAL":
39693977
members.AddRange(this.ExtractMembersFromTemplate(name.Identifier.ValueText));
39703978
break;
39713979
default:
@@ -6852,6 +6860,22 @@ internal Grouping(TKey key, IEnumerable<TElement> values)
68526860
}
68536861
}
68546862

6863+
private class DirectiveTriviaRemover : CSharpSyntaxRewriter
6864+
{
6865+
internal static readonly DirectiveTriviaRemover Instance = new();
6866+
6867+
private DirectiveTriviaRemover()
6868+
{
6869+
}
6870+
6871+
public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia) =>
6872+
trivia.IsKind(SyntaxKind.IfDirectiveTrivia) ||
6873+
trivia.IsKind(SyntaxKind.ElseDirectiveTrivia) ||
6874+
trivia.IsKind(SyntaxKind.EndIfDirectiveTrivia) ||
6875+
trivia.IsKind(SyntaxKind.DisabledTextTrivia)
6876+
? default : trivia;
6877+
}
6878+
68556879
private class WhitespaceRewriter : CSharpSyntaxRewriter
68566880
{
68576881
private readonly List<SyntaxTrivia> indentationLevels = new List<SyntaxTrivia> { default };
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
internal partial struct DECIMAL
2+
{
3+
public DECIMAL(decimal value)
4+
{
5+
unchecked
6+
{
7+
const int SignMask = (int)0x80000000;
8+
#if NET5_0_OR_GREATER
9+
Span<int> bits = stackalloc int[4];
10+
decimal.GetBits(value, bits);
11+
#else
12+
int[] bits = decimal.GetBits(value);
13+
#endif
14+
uint lo32 = (uint)bits[0];
15+
uint mid32 = (uint)bits[1];
16+
uint hi32 = (uint)bits[2];
17+
byte scale = (byte)(bits[3] >> 16);
18+
byte sign = (bits[3] & SignMask) == SignMask ? (byte)0x80 : (byte)0x00;
19+
this.Anonymous2 = new _Anonymous2_e__Union() { Anonymous = new _Anonymous2_e__Union._Anonymous_e__Struct() { Lo32 = lo32, Mid32 = mid32 } };
20+
this.Hi32 = hi32;
21+
this.Anonymous1 = new _Anonymous1_e__Union() { Anonymous = new _Anonymous1_e__Union._Anonymous_e__Struct() { scale = scale, sign = sign } };
22+
this.wReserved = 0;
23+
}
24+
}
25+
26+
public static implicit operator decimal(DECIMAL value)
27+
{
28+
return new decimal(
29+
(int)value.Anonymous2.Anonymous.Lo32,
30+
(int)value.Anonymous2.Anonymous.Mid32,
31+
(int)value.Hi32,
32+
value.Anonymous1.Anonymous.sign == 0x80,
33+
value.Anonymous1.Anonymous.scale);
34+
}
35+
36+
#if NET5_0_OR_GREATER
37+
public static implicit operator DECIMAL(decimal value) => new DECIMAL(value);
38+
#endif
39+
}

test/GenerationSandbox.Tests/BasicTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ public BasicTests(ITestOutputHelper logger)
2626

2727
internal delegate uint GetTickCountDelegate();
2828

29+
public static object[][] InterestingDecimalValue => new object[][]
30+
{
31+
new object[] { 0.0m },
32+
new object[] { 1.2m },
33+
new object[] { -1.2m },
34+
new object[] { decimal.MinValue },
35+
new object[] { decimal.MaxValue },
36+
};
37+
2938
[Fact]
3039
public void GetTickCount_Nonzero()
3140
{
@@ -105,6 +114,21 @@ public unsafe void BSTR_AsSpan()
105114
}
106115
}
107116

117+
[Theory]
118+
[MemberData(nameof(InterestingDecimalValue))]
119+
public void DecimalConversion(decimal value)
120+
{
121+
DECIMAL nativeDecimal = new(value);
122+
decimal valueRoundTripped = nativeDecimal;
123+
Assert.Equal(value, valueRoundTripped);
124+
125+
#if NET5_0_OR_GREATER
126+
nativeDecimal = value;
127+
valueRoundTripped = nativeDecimal;
128+
Assert.Equal(value, valueRoundTripped);
129+
#endif
130+
}
131+
108132
[Fact]
109133
public void HandlesOverrideEquals()
110134
{

test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
using Microsoft.CodeAnalysis.CSharp.Syntax;
1313
using Microsoft.CodeAnalysis.Testing;
1414
using Microsoft.CodeAnalysis.Text;
15-
using VerifyTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<
16-
Microsoft.Windows.CsWin32.SourceGenerator,
17-
Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
15+
using VerifyTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<Microsoft.Windows.CsWin32.SourceGenerator, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
1816

1917
public class GeneratorTests : IDisposable, IAsyncLifetime
2018
{
@@ -42,6 +40,7 @@ public class GeneratorTests : IDisposable, IAsyncLifetime
4240

4341
private readonly ITestOutputHelper logger;
4442
private readonly Dictionary<string, CSharpCompilation> starterCompilations = new();
43+
private readonly Dictionary<string, string[]> preprocessorSymbolsByTfm = new();
4544
private CSharpCompilation compilation;
4645
private CSharpParseOptions parseOptions;
4746
private Generator? generator;
@@ -110,6 +109,20 @@ public async Task InitializeAsync()
110109
this.starterCompilations.Add("net6.0-x86", await this.CreateCompilationAsync(MyReferenceAssemblies.Net.Net60, Platform.X86));
111110
this.starterCompilations.Add("net6.0-x64", await this.CreateCompilationAsync(MyReferenceAssemblies.Net.Net60, Platform.X64));
112111

112+
foreach (string tfm in this.starterCompilations.Keys)
113+
{
114+
if (tfm.StartsWith("net6"))
115+
{
116+
AddSymbols("NET5_0_OR_GREATER", "NET6_0_OR_GREATER", "NET6_0");
117+
}
118+
else
119+
{
120+
AddSymbols();
121+
}
122+
123+
void AddSymbols(params string[] symbols) => this.preprocessorSymbolsByTfm.Add(tfm, symbols);
124+
}
125+
113126
this.compilation = this.starterCompilations["netstandard2.0"];
114127
}
115128

@@ -557,6 +570,17 @@ public void ComOutPtrTypedAsOutObject()
557570
Assert.Contains(this.FindGeneratedMethod(methodName), m => m.ParameterList.Parameters.Last() is { } last && last.Modifiers.Any(SyntaxKind.OutKeyword) && last.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.ObjectKeyword } });
558571
}
559572

573+
[Theory, CombinatorialData]
574+
public void Decimal([CombinatorialValues("net472", "net6.0")] string tfm)
575+
{
576+
this.compilation = this.starterCompilations[tfm];
577+
this.parseOptions = this.parseOptions.WithPreprocessorSymbols(this.preprocessorSymbolsByTfm[tfm]);
578+
this.generator = this.CreateGenerator();
579+
Assert.True(this.generator.TryGenerate("DECIMAL", CancellationToken.None));
580+
this.CollectGeneratedCode(this.generator);
581+
this.AssertNoDiagnostics();
582+
}
583+
560584
[Fact]
561585
public void ComOutPtrTypedAsIntPtr()
562586
{

0 commit comments

Comments
 (0)