diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs index ace6923f5..425da93fc 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs @@ -793,6 +793,14 @@ private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, o WithType(enumDecl, ref remappedName, ref nativeTypeName); } + else if (cursor is FieldDecl fieldDecl) + { + // A field can have its emitted type overridden via `--with-type Struct.field=Type`. + // The `*` catch-all is intentionally not honored here, as rewriting every field to + // a single type is meaningless; the qualified `Struct.field` key must be explicit. + + WithType(fieldDecl, ref remappedName, ref nativeTypeName, matchStar: false); + } } } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 427ac30e1..9bbe8b72f 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -2399,9 +2399,9 @@ private void WithTestAssertTrue(string actual) } } - private void WithType(NamedDecl namedDecl, ref string integerTypeName, ref string nativeTypeName) + private void WithType(NamedDecl namedDecl, ref string integerTypeName, ref string nativeTypeName, bool matchStar = true) { - if (TryGetRemappedValue(namedDecl, _config._withTypes, out var type, matchStar: true)) + if (TryGetRemappedValue(namedDecl, _config._withTypes, out var type, matchStar)) { if (string.IsNullOrWhiteSpace(nativeTypeName)) { diff --git a/sources/ClangSharpPInvokeGenerator/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs index 404b7d5e9..1d41e0e6d 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.Options.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs @@ -84,7 +84,7 @@ internal static partial class Program private static readonly CommandLineOption s_withSetLastErrors = Multi(s_withSetLastErrorOptionAliases, "Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards."); private static readonly CommandLineOption s_withSuppressGCTransitions = Multi(s_withSuppressGCTransitionOptionAliases, "Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards."); private static readonly CommandLineOption s_withTransparentStructNameValuePairs = Multi(s_withTransparentStructOptionAliases, "A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards."); - private static readonly CommandLineOption s_withTypeNameValuePairs = Multi(s_withTypeOptionAliases, "A type to be used for the given enum declaration during binding generation. Supports wildcards."); + private static readonly CommandLineOption s_withTypeNameValuePairs = Multi(s_withTypeOptionAliases, "A type to be used for the given enum declaration, macro constant, or struct field (using the qualified `Type.field`) during binding generation. Supports wildcards."); private static readonly CommandLineOption s_withUsingNameValuePairs = Multi(s_withUsingOptionAliases, "A using directive to be included for the given remapped declaration name during binding generation. Supports wildcards."); private static readonly CommandLineOption s_helpOption = Flag(s_helpOptionAliases, "Show help and usage information"); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/WithTypeFieldTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/WithTypeFieldTest.cs new file mode 100644 index 000000000..870b2cebb --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/WithTypeFieldTest.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ClangSharp.UnitTests; + +/// +/// Regression tests for https://github.com/dotnet/ClangSharp/issues/559. +/// The --with-type option is extended to accept a field-qualified key (Struct.field=Type) +/// so the emitted type of an individual struct field can be overridden. The original Clang type is +/// preserved as a [NativeTypeName] attribute and unrelated fields are left untouched. +/// +[Platform("win")] +public sealed class WithTypeFieldTest : PInvokeGeneratorTest +{ + [Test] + public Task OverridesFieldType() + { + var inputContents = @"struct Something +{ + int type; + int other; +}; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct Something + { + [NativeTypeName(""int"")] + public SomethingType type; + + public int other; + } +} +"; + + var withTypes = new Dictionary + { + ["Something.type"] = "SomethingType", + }; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, withTypes: withTypes); + } +}