diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 7647508d..05d1ad2f 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -108,6 +108,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("///")); @@ -288,6 +293,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 +324,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; @@ -849,6 +856,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})."); @@ -1599,10 +1614,26 @@ internal void GetBaseTypeInfo(TypeDefinition typeDef, out StringHandle baseTypeN { case "PCWSTR": specialDeclaration = this.FetchTemplate($"{specialName}"); + + if (this.canUseSpan) + { + // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); + specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( + this.CreateAsSpanMethodOverValueAndLength(MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))))); + } + this.TryGenerateType("PWSTR"); // the template references this type break; case "PCSTR": specialDeclaration = this.FetchTemplate($"{specialName}"); + + if (this.canUseSpan) + { + // internal ReadOnlySpan AsSpan() => this.Value is null ? default(ReadOnlySpan) : new ReadOnlySpan(this.Value, this.Length); + specialDeclaration = ((TypeDeclarationSyntax)specialDeclaration).AddMembers( + this.CreateAsSpanMethodOverValueAndLength(MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.ByteKeyword))))); + } + this.TryGenerateType("PSTR"); // the template references this type break; default: @@ -3718,16 +3749,28 @@ 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))); + if (this.canUseSpan) + { + // internal Span AsSpan() => this.Value is null ? default : new Span(this.Value, this.Length); + 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")); - ExpressionSyntax spanCreation = ObjectCreationExpression(spanChar).AddArgumentListArguments(Argument(thisValue), Argument(thisLength)); - ExpressionSyntax conditional = ConditionalExpression(thisValueIsNull, DefaultExpression(spanChar), spanCreation); - yield return MethodDeclaration(spanChar, Identifier("AsSpan")) + + // 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(conditional)) - .WithSemicolonToken(SemicolonWithLineFeed); -#pragma warning restore SA1114 // Parameter list should follow declaration + .WithExpressionBody(ArrowExpressionClause(ConditionalExpression( + condition: IsPatternExpression(thisValue, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), + whenTrue: DefaultExpression(spanType), + whenFalse: ObjectCreationExpression(spanType).AddArgumentListArguments(Argument(thisValue), Argument(thisLength))))) + .WithSemicolonToken(SemicolonWithLineFeed) + .WithLeadingTrivia(StrAsSpanComment); } private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) @@ -3753,21 +3796,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; @@ -3777,20 +3816,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/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 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 074bb71d..d9325871 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")); } @@ -792,6 +792,40 @@ 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)); + } + + [Theory] + [InlineData("BOOL")] + [InlineData("HRESULT")] + [InlineData("NTSTATUS")] + [InlineData("PCSTR")] + [InlineData("PCWSTR")] + [InlineData("PWSTR")] + public void SynthesizedTypesWorkInNet35(string synthesizedTypeName) + { + this.compilation = this.starterCompilations["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. /// @@ -1082,12 +1116,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); @@ -1339,12 +1373,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); @@ -1649,12 +1683,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); @@ -1958,13 +1992,12 @@ internal int Length public override string ToString() => this.Value is null ? null : new string(this.Value); + 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); - - - private string DebuggerDisplay => this.ToString(); } } } @@ -2127,6 +2160,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); } } @@ -2427,7 +2463,7 @@ private static class MyReferenceAssemblies internal static class NetFramework { - internal static readonly ReferenceAssemblies Net40 = ReferenceAssemblies.NetFramework.Net40.Default.AddPackages(AdditionalPackages); + internal static readonly ReferenceAssemblies Net35 = ReferenceAssemblies.NetFramework.Net35.Default.AddPackages(AdditionalPackages); } internal static class Net