From 4f4b24121d40c553f53d9026335e8b901a243f91 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 8 Oct 2021 17:52:37 -0400 Subject: [PATCH 1/9] Enable types from templates to be directly requested --- src/Microsoft.Windows.CsWin32/Generator.cs | 8 ++++++++ .../GeneratorTests.cs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 526e7621..74062eb1 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -849,6 +849,14 @@ public bool TryGenerateType(string possiblyQualifiedName, out IReadOnlyList this.RequestSpecialTypeDefStruct(typeName, out fullyQualifiedName)); + preciseApi = ImmutableList.Create(fullyQualifiedName!); + return true; + } + if (foundApiWithMismatchedPlatform) { throw new PlatformIncompatibleException($"The requested API ({possiblyQualifiedName}) was found but is not available given the target platform ({this.compilation?.Options.Platform})."); diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index f2e9cdd4..6a8f4a7b 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -791,6 +791,22 @@ internal enum FILE_CREATE_FLAGS this.AssertNoDiagnostics(); } + [Theory] + [InlineData("BOOL")] + [InlineData("HRESULT")] + [InlineData("NTSTATUS")] + [InlineData("PCSTR")] + [InlineData("PCWSTR")] + [InlineData("PWSTR")] + public void SynthesizedTypesCanBeDirectlyRequested(string synthesizedTypeName) + { + this.generator = this.CreateGenerator(); + Assert.True(this.generator.TryGenerate(synthesizedTypeName, CancellationToken.None)); + this.CollectGeneratedCode(this.generator); + this.AssertNoDiagnostics(); + Assert.Single(this.FindGeneratedType(synthesizedTypeName)); + } + /// /// Validates that where MemoryMarshal.CreateSpan isn't available, a substitute indexer is offered. /// @@ -2432,6 +2448,8 @@ private static class MyReferenceAssemblies internal static class NetFramework { + internal static readonly ReferenceAssemblies Net35 = ReferenceAssemblies.NetFramework.Net35.Default.AddPackages(AdditionalPackages); + internal static readonly ReferenceAssemblies Net40 = ReferenceAssemblies.NetFramework.Net40.Default.AddPackages(AdditionalPackages); } From 3c446df8f7c7a3884d81f8283654f6e105b8e7eb Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 8 Oct 2021 17:55:47 -0400 Subject: [PATCH 2/9] Test that synthesized types work on net35 --- .../GeneratorTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 6a8f4a7b..1814dc29 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -807,6 +807,24 @@ public void SynthesizedTypesCanBeDirectlyRequested(string synthesizedTypeName) Assert.Single(this.FindGeneratedType(synthesizedTypeName)); } + [Theory] + [InlineData("BOOL")] + [InlineData("HRESULT")] + [InlineData("NTSTATUS")] + [InlineData("PCSTR")] + [InlineData("PCWSTR")] + [InlineData("PWSTR")] + public async Task SynthesizedTypesWorkInNet35(string synthesizedTypeName) + { + this.compilation = await this.CreateCompilationAsync(MyReferenceAssemblies.NetFramework.Net35); + this.generator = this.CreateGenerator(); + + Assert.True(this.generator.TryGenerate(synthesizedTypeName, CancellationToken.None)); + this.CollectGeneratedCode(this.generator); + this.AssertNoDiagnostics(); + Assert.Single(this.FindGeneratedType(synthesizedTypeName)); + } + /// /// Validates that where MemoryMarshal.CreateSpan isn't available, a substitute indexer is offered. /// From 616c7bef5679a42b79d3738cb08dd62e929e3830 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Oct 2021 11:56:56 -0400 Subject: [PATCH 3/9] Enable use of APIs using BOOL on net35 --- src/Microsoft.Windows.CsWin32/Generator.cs | 33 ++++++++++--------- .../GeneratorTests.cs | 18 +++++----- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 74062eb1..5477095e 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -3740,21 +3740,17 @@ private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) .WithExpressionBody(ArrowExpressionClause(fieldAccessExpression)).WithSemicolonToken(SemicolonWithLineFeed) .AddModifiers(TokenWithSpace(this.Visibility))); - static InvocationExpressionSyntax UnsafeAs(SyntaxKind fromType, SyntaxKind toType, IdentifierNameSyntax localSource) => - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nameof(Unsafe)), - GenericName(nameof(Unsafe.As), TypeArgumentList().AddArguments(PredefinedType(Token(fromType)), PredefinedType(Token(toType))))), - ArgumentList().AddArguments(Argument(localSource).WithRefKindKeyword(Token(SyntaxKind.RefKeyword)))); - - // BOOL(bool value) => this.value = Unsafe.As(ref value); + // unsafe BOOL(bool value) => this.value = *(sbyte*)&value; IdentifierNameSyntax valueParameter = IdentifierName("value"); - ExpressionSyntax boolToInt = UnsafeAs(SyntaxKind.BoolKeyword, SyntaxKind.SByteKeyword, valueParameter); + ExpressionSyntax boolToSByte = PrefixUnaryExpression( + SyntaxKind.PointerIndirectionExpression, + CastExpression( + PointerType(PredefinedType(TokenWithNoSpace(SyntaxKind.SByteKeyword))), + PrefixUnaryExpression(SyntaxKind.AddressOfExpression, valueParameter))); members = members.Add(ConstructorDeclaration(name.Identifier) - .AddModifiers(TokenWithSpace(this.Visibility)) + .AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.UnsafeKeyword)) .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword)))) - .WithExpressionBody(ArrowExpressionClause(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, fieldAccessExpression, boolToInt).WithOperatorToken(TokenWithSpaces(SyntaxKind.EqualsToken)))) + .WithExpressionBody(ArrowExpressionClause(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, fieldAccessExpression, boolToSByte).WithOperatorToken(TokenWithSpaces(SyntaxKind.EqualsToken)))) .WithSemicolonToken(SemicolonWithLineFeed)); // BOOL(int value) => this.value = value; @@ -3764,20 +3760,25 @@ static InvocationExpressionSyntax UnsafeAs(SyntaxKind fromType, SyntaxKind toTyp .WithExpressionBody(ArrowExpressionClause(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, fieldAccessExpression, valueParameter).WithOperatorToken(TokenWithSpaces(SyntaxKind.EqualsToken)))) .WithSemicolonToken(SemicolonWithLineFeed)); - // public static implicit operator bool(BOOL value) + // public unsafe static implicit operator bool(BOOL value) // { // sbyte v = checked((sbyte)value.value); - // return Unsafe.As(ref v); + // return *(bool*)&v; // } IdentifierNameSyntax localVarName = IdentifierName("v"); + ExpressionSyntax sbyteToBool = PrefixUnaryExpression( + SyntaxKind.PointerIndirectionExpression, + CastExpression( + PointerType(PredefinedType(TokenWithNoSpace(SyntaxKind.BoolKeyword))), + PrefixUnaryExpression(SyntaxKind.AddressOfExpression, localVarName))); var implicitBOOLtoBoolBody = Block().AddStatements( LocalDeclarationStatement(VariableDeclaration(PredefinedType(Token(SyntaxKind.SByteKeyword)))).AddDeclarationVariables( VariableDeclarator(localVarName.Identifier).WithInitializer(EqualsValueClause(CheckedExpression(SyntaxKind.CheckedExpression, CastExpression(PredefinedType(Token(SyntaxKind.SByteKeyword)), MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, fieldName)))))), - ReturnStatement(UnsafeAs(SyntaxKind.SByteKeyword, SyntaxKind.BoolKeyword, localVarName))); + ReturnStatement(sbyteToBool)); members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), PredefinedType(Token(SyntaxKind.BoolKeyword))) .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(name.WithTrailingTrivia(TriviaList(Space)))) .WithBody(implicitBOOLtoBoolBody) - .AddModifiers(TokenWithSpace(SyntaxKind.PublicKeyword), TokenWithSpace(SyntaxKind.StaticKeyword))); // operators MUST be public + .AddModifiers(TokenWithSpace(SyntaxKind.PublicKeyword), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.UnsafeKeyword))); // operators MUST be public // public static implicit operator BOOL(bool value) => new BOOL(value); members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), name) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 1814dc29..ff623eda 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -1121,12 +1121,12 @@ internal readonly partial struct BOOL private readonly int value; internal int Value => this.value; - internal BOOL(bool value) => this.value = Unsafe.As(ref value); + internal unsafe BOOL(bool value) => this.value = *(sbyte*)&value; internal BOOL(int value) => this.value = value; - public static implicit operator bool(BOOL value) + public static unsafe implicit operator bool(BOOL value) { sbyte v = checked((sbyte)value.value); - return Unsafe.As(ref v); + return *(bool*)&v; } public static implicit operator BOOL(bool value) => new BOOL(value); public static explicit operator BOOL(int value) => new BOOL(value); @@ -1378,12 +1378,12 @@ internal readonly partial struct BOOL private readonly int value; internal int Value => this.value; - internal BOOL(bool value) => this.value = Unsafe.As(ref value); + internal unsafe BOOL(bool value) => this.value = *(sbyte*)&value; internal BOOL(int value) => this.value = value; - public static implicit operator bool(BOOL value) + public static unsafe implicit operator bool(BOOL value) { sbyte v = checked((sbyte)value.value); - return Unsafe.As(ref v); + return *(bool*)&v; } public static implicit operator BOOL(bool value) => new BOOL(value); public static explicit operator BOOL(int value) => new BOOL(value); @@ -1688,12 +1688,12 @@ internal readonly partial struct BOOL private readonly int value; internal int Value => this.value; - internal BOOL(bool value) => this.value = Unsafe.As(ref value); + internal unsafe BOOL(bool value) => this.value = *(sbyte*)&value; internal BOOL(int value) => this.value = value; - public static implicit operator bool(BOOL value) + public static unsafe implicit operator bool(BOOL value) { sbyte v = checked((sbyte)value.value); - return Unsafe.As(ref v); + return *(bool*)&v; } public static implicit operator BOOL(bool value) => new BOOL(value); public static explicit operator BOOL(int value) => new BOOL(value); From 6a61d08c350f3a867c8ca5ae76f320e8e71ecb71 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Oct 2021 12:00:21 -0400 Subject: [PATCH 4/9] Enable use of APIs using PWSTR on net35 --- src/Microsoft.Windows.CsWin32/Generator.cs | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 5477095e..b23e316d 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -288,6 +288,7 @@ public class Generator : IDisposable private readonly GeneratorOptions options; private readonly CSharpCompilation? compilation; private readonly CSharpParseOptions? parseOptions; + private readonly bool canUseSpan; private readonly bool canCallCreateSpan; private readonly bool getDelegateForFunctionPointerGenericExists; private readonly bool generateSupportedOSPlatformAttributes; @@ -318,6 +319,7 @@ public Generator(string metadataLibraryPath, Docs? docs, GeneratorOptions option this.parseOptions = parseOptions; this.volatileCode = new(this.committedCode); + this.canUseSpan = this.compilation?.GetTypeByMetadataName(typeof(Span<>).FullName) is not null; this.canCallCreateSpan = this.compilation?.GetTypeByMetadataName(typeof(MemoryMarshal).FullName)?.GetMembers("CreateSpan").Any() is true; this.getDelegateForFunctionPointerGenericExists = this.compilation?.GetTypeByMetadataName(typeof(Marshal).FullName)?.GetMembers(nameof(Marshal.GetDelegateForFunctionPointer)).Any(m => m is IMethodSymbol { IsGenericMethod: true }) is true; this.generateDefaultDllImportSearchPathsAttribute = this.compilation?.GetTypeByMetadataName(typeof(DefaultDllImportSearchPathsAttribute).FullName) is object; @@ -3705,15 +3707,18 @@ private IEnumerable CreateAdditionalTypeDefPWSTRMembers .AddArgumentListArguments(Argument(thisValue))))) .WithSemicolonToken(SemicolonWithLineFeed); - // internal Span AsSpan() => this.Value is null ? default : new Span(this.Value, this.Length); - TypeSyntax spanChar = MakeSpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); - ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); - ExpressionSyntax spanCreation = ObjectCreationExpression(spanChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength)); - ExpressionSyntax conditional = ConditionalExpression(thisValueIsNull, DefaultExpression(spanChar), spanCreation); - yield return MethodDeclaration(spanChar, Identifier("AsSpan")) - .AddModifiers(TokenWithSpace(this.Visibility)) - .WithExpressionBody(ArrowExpressionClause(conditional)) - .WithSemicolonToken(SemicolonWithLineFeed); + if (this.canUseSpan) + { + // internal Span AsSpan() => this.Value is null ? default : new Span(this.Value, this.Length); + TypeSyntax spanChar = MakeSpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); + ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); + ExpressionSyntax spanCreation = ObjectCreationExpression(spanChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength)); + ExpressionSyntax conditional = ConditionalExpression(thisValueIsNull, DefaultExpression(spanChar), spanCreation); + yield return MethodDeclaration(spanChar, Identifier("AsSpan")) + .AddModifiers(TokenWithSpace(this.Visibility)) + .WithExpressionBody(ArrowExpressionClause(conditional)) + .WithSemicolonToken(SemicolonWithLineFeed); + } #pragma warning restore SA1114 // Parameter list should follow declaration } From a331ca017f584d39d4d1d12d984c807094c0efd9 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Thu, 23 Sep 2021 13:41:55 -0400 Subject: [PATCH 5/9] Enable editing template source files without a hassle due to mixing tabs and spaces --- src/Microsoft.Windows.CsWin32/templates/.editorconfig | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/Microsoft.Windows.CsWin32/templates/.editorconfig diff --git a/src/Microsoft.Windows.CsWin32/templates/.editorconfig b/src/Microsoft.Windows.CsWin32/templates/.editorconfig new file mode 100644 index 00000000..0a56872a --- /dev/null +++ b/src/Microsoft.Windows.CsWin32/templates/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +indent_style = tab From 03ae63e6649451bd73c4a299be7be44aa10d4c82 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Oct 2021 12:03:38 -0400 Subject: [PATCH 6/9] Enable use of APIs using PCSTR/PCWSTR on net35 --- src/Microsoft.Windows.CsWin32/Generator.cs | 38 +++++++++++++++++++ .../templates/PCSTR.cs | 5 --- .../templates/PCWSTR.cs | 5 --- .../GeneratorTests.cs | 8 +--- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index b23e316d..35aeca3a 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -1610,10 +1610,48 @@ internal void GetBaseTypeInfo(TypeDefinition typeDef, out StringHandle baseTypeN { case "PCWSTR": specialDeclaration = this.FetchTemplate($"{specialName}"); + + if (this.canUseSpan) + { + TypeSyntax readOnlySpanOfChar = MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); + ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); + ExpressionSyntax thisValueIsNull = IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))); + ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); + + // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); + specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( + MethodDeclaration(readOnlySpanOfChar, Identifier("AsSpan")) + .AddModifiers(TokenWithSpace(this.Visibility)) + .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( + condition: thisValueIsNull, + whenTrue: DefaultExpression(readOnlySpanOfChar), + whenFalse: ObjectCreationExpression(readOnlySpanOfChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) + .WithSemicolonToken(SemicolonWithLineFeed)); + } + this.TryGenerateType("PWSTR"); // the template references this type break; case "PCSTR": specialDeclaration = this.FetchTemplate($"{specialName}"); + + if (this.canUseSpan) + { + TypeSyntax readOnlySpanOfByte = MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.ByteKeyword))); + ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); + ExpressionSyntax thisValueIsNull = IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))); + ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); + + // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); + specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( + MethodDeclaration(readOnlySpanOfByte, Identifier("AsSpan")) + .AddModifiers(TokenWithSpace(this.Visibility)) + .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( + condition: thisValueIsNull, + whenTrue: DefaultExpression(readOnlySpanOfByte), + whenFalse: ObjectCreationExpression(readOnlySpanOfByte).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) + .WithSemicolonToken(SemicolonWithLineFeed)); + } + this.TryGenerateType("PSTR"); // the template references this type break; default: diff --git a/src/Microsoft.Windows.CsWin32/templates/PCSTR.cs b/src/Microsoft.Windows.CsWin32/templates/PCSTR.cs index 0e64e72c..641932e7 100644 --- a/src/Microsoft.Windows.CsWin32/templates/PCSTR.cs +++ b/src/Microsoft.Windows.CsWin32/templates/PCSTR.cs @@ -39,10 +39,5 @@ internal int Length /// A , or if is . public override string ToString() => this.Value is null ? null : new string((sbyte*)this.Value, 0, this.Length, global::System.Text.Encoding.UTF8); - /// - /// Returns a span of the characters in this string. - /// - internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); - private string DebuggerDisplay => this.ToString(); } diff --git a/src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs b/src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs index 2b5492b9..d85312e4 100644 --- a/src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs +++ b/src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs @@ -39,10 +39,5 @@ internal int Length /// A , or if is . public override string ToString() => this.Value is null ? null : new string(this.Value); - /// - /// Returns a span of the characters in this string. - /// - internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); - private string DebuggerDisplay => this.ToString(); } diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index ff623eda..69c3a68f 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -1997,13 +1997,9 @@ internal int Length public override string ToString() => this.Value is null ? null : new string(this.Value); - /// - /// Returns a span of the characters in this string. - /// - internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); - - private string DebuggerDisplay => this.ToString(); + + internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); } } } From 29fe4d37248f71fa193acce4a48e05597097909c Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Oct 2021 12:49:02 -0400 Subject: [PATCH 7/9] Deduplicate --- src/Microsoft.Windows.CsWin32/Generator.cs | 50 ++++++++-------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 35aeca3a..b5513302 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -1613,20 +1613,9 @@ internal void GetBaseTypeInfo(TypeDefinition typeDef, out StringHandle baseTypeN if (this.canUseSpan) { - TypeSyntax readOnlySpanOfChar = MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); - ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); - ExpressionSyntax thisValueIsNull = IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))); - ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); - // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( - MethodDeclaration(readOnlySpanOfChar, Identifier("AsSpan")) - .AddModifiers(TokenWithSpace(this.Visibility)) - .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( - condition: thisValueIsNull, - whenTrue: DefaultExpression(readOnlySpanOfChar), - whenFalse: ObjectCreationExpression(readOnlySpanOfChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) - .WithSemicolonToken(SemicolonWithLineFeed)); + this.CreateAsSpanMethodOverValueAndLength(MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))))); } this.TryGenerateType("PWSTR"); // the template references this type @@ -1636,20 +1625,9 @@ internal void GetBaseTypeInfo(TypeDefinition typeDef, out StringHandle baseTypeN if (this.canUseSpan) { - TypeSyntax readOnlySpanOfByte = MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.ByteKeyword))); - ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); - ExpressionSyntax thisValueIsNull = IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))); - ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); - // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( - MethodDeclaration(readOnlySpanOfByte, Identifier("AsSpan")) - .AddModifiers(TokenWithSpace(this.Visibility)) - .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( - condition: thisValueIsNull, - whenTrue: DefaultExpression(readOnlySpanOfByte), - whenFalse: ObjectCreationExpression(readOnlySpanOfByte).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) - .WithSemicolonToken(SemicolonWithLineFeed)); + this.CreateAsSpanMethodOverValueAndLength(MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.ByteKeyword))))); } this.TryGenerateType("PSTR"); // the template references this type @@ -3748,18 +3726,26 @@ private IEnumerable CreateAdditionalTypeDefPWSTRMembers if (this.canUseSpan) { // internal Span AsSpan() => this.Value is null ? default : new Span(this.Value, this.Length); - TypeSyntax spanChar = MakeSpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); - ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); - ExpressionSyntax spanCreation = ObjectCreationExpression(spanChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength)); - ExpressionSyntax conditional = ConditionalExpression(thisValueIsNull, DefaultExpression(spanChar), spanCreation); - yield return MethodDeclaration(spanChar, Identifier("AsSpan")) - .AddModifiers(TokenWithSpace(this.Visibility)) - .WithExpressionBody(ArrowExpressionClause(conditional)) - .WithSemicolonToken(SemicolonWithLineFeed); + yield return this.CreateAsSpanMethodOverValueAndLength(MakeSpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword)))); } #pragma warning restore SA1114 // Parameter list should follow declaration } + private MethodDeclarationSyntax CreateAsSpanMethodOverValueAndLength(TypeSyntax spanType) + { + ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); + ExpressionSyntax thisLength = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Length")); + + // internal X AsSpan() => this.Value is null ? default(X) : new X(this.Value, this.Length); + return MethodDeclaration(spanType, Identifier("AsSpan")) + .AddModifiers(TokenWithSpace(this.Visibility)) + .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( + condition: IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), + whenTrue: DefaultExpression(spanType), + whenFalse: ObjectCreationExpression(spanType).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) + .WithSemicolonToken(SemicolonWithLineFeed); + } + private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) { IdentifierNameSyntax name = IdentifierName("BOOL"); From 2165b7fc1a4b0b495663bb15f5dd8b97e7a6fab9 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Oct 2021 13:11:50 -0400 Subject: [PATCH 8/9] Use net35 (EOL in 2029) instead of net40 (EOL in 2016) --- .../GeneratorTests.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 69c3a68f..34426ddb 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -56,14 +56,14 @@ public GeneratorTests(ITestOutputHelper logger) public static IEnumerable TFMData => new object[][] { - new object[] { "net40" }, + new object[] { "net35" }, new object[] { "netstandard2.0" }, new object[] { "net5.0" }, }; public async Task InitializeAsync() { - this.starterCompilations.Add("net40", await this.CreateCompilationAsync(MyReferenceAssemblies.NetFramework.Net40)); + this.starterCompilations.Add("net35", await this.CreateCompilationAsync(MyReferenceAssemblies.NetFramework.Net35)); this.starterCompilations.Add("netstandard2.0", await this.CreateCompilationAsync(MyReferenceAssemblies.NetStandard20)); this.starterCompilations.Add("net5.0", await this.CreateCompilationAsync(MyReferenceAssemblies.Net.Net50)); this.starterCompilations.Add("net5.0-x86", await this.CreateCompilationAsync(MyReferenceAssemblies.Net.Net50, Platform.X86)); @@ -112,7 +112,7 @@ public void SimplestMethod(string tfm) Assert.DoesNotContain(generatedMethod.AttributeLists, al => IsAttributePresent(al, "SupportedOSPlatform")); } - if (tfm != "net40") + if (tfm != "net35") { Assert.Contains(generatedMethod.AttributeLists, al => IsAttributePresent(al, "DefaultDllImportSearchPaths")); } @@ -814,9 +814,9 @@ public void SynthesizedTypesCanBeDirectlyRequested(string synthesizedTypeName) [InlineData("PCSTR")] [InlineData("PCWSTR")] [InlineData("PWSTR")] - public async Task SynthesizedTypesWorkInNet35(string synthesizedTypeName) + public void SynthesizedTypesWorkInNet35(string synthesizedTypeName) { - this.compilation = await this.CreateCompilationAsync(MyReferenceAssemblies.NetFramework.Net35); + this.compilation = this.starterCompilations["net35"]; this.generator = this.CreateGenerator(); Assert.True(this.generator.TryGenerate(synthesizedTypeName, CancellationToken.None)); @@ -2463,8 +2463,6 @@ private static class MyReferenceAssemblies internal static class NetFramework { internal static readonly ReferenceAssemblies Net35 = ReferenceAssemblies.NetFramework.Net35.Default.AddPackages(AdditionalPackages); - - internal static readonly ReferenceAssemblies Net40 = ReferenceAssemblies.NetFramework.Net40.Default.AddPackages(AdditionalPackages); } internal static class Net From bc4d5b51ff82eb43ecc654fd9c7453f2c9342f94 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 30 Oct 2021 15:07:44 -0400 Subject: [PATCH 9/9] Add back the AsSpan XML doc comment --- src/Microsoft.Windows.CsWin32/Generator.cs | 8 +++++++- test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index b5513302..22b20502 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -110,6 +110,11 @@ public class Generator : IDisposable /// /// Thrown when is less than 0 or greater than . /// +"); + + private static readonly SyntaxTriviaList StrAsSpanComment = ParseLeadingTrivia(@"/// +/// Returns a span of the characters in this string. +/// "); private static readonly XmlTextSyntax DocCommentStart = XmlText(" ").WithLeadingTrivia(DocumentationCommentExterior("///")); @@ -3743,7 +3748,8 @@ private MethodDeclarationSyntax CreateAsSpanMethodOverValueAndLength(TypeSyntax condition: IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), whenTrue: DefaultExpression(spanType), whenFalse: ObjectCreationExpression(spanType).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) - .WithSemicolonToken(SemicolonWithLineFeed); + .WithSemicolonToken(SemicolonWithLineFeed) + .WithLeadingTrivia(StrAsSpanComment); } private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 34426ddb..18be2318 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -1999,6 +1999,9 @@ internal int Length private string DebuggerDisplay => this.ToString(); + /// + /// Returns a span of the characters in this string. + /// internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); } } @@ -2162,6 +2165,9 @@ internal int Length public override string ToString() => this.Value is null ? null : new string(this.Value); + /// + /// Returns a span of the characters in this string. + /// internal Span AsSpan() => this.Value is null ? default(Span) : new Span(this.Value, this.Length); } }