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