Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config option: strip-enum-member-type-name #527

Merged
merged 6 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ Options:
generate-unmanaged-constants Unmanaged constants should be generated using static ref readonly properties. This is currently experimental.
generate-vtbl-index-attribute [VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method.

# Stripping Options

strip-enum-member-type-name Strips the enum type name from the beginning of its member names.

# Logging Options

log-exclusions A list of excluded declaration types should be generated. This will also log if the exclusion was due to an exact or partial match.
Expand Down
20 changes: 13 additions & 7 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using ClangSharp.Abstractions;
using ClangSharp.CSharp;
using static ClangSharp.Interop.CX_CastKind;
Expand Down Expand Up @@ -294,6 +295,11 @@ private void VisitEnumConstantDecl(EnumConstantDecl enumConstantDecl)
parentName = _outputBuilder.Name;
}

if (Config.StripEnumMemberTypeName)
{
escapedName = PrefixAndStrip(escapedName, parentName, trimChar: '_');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, was busy at Microsoft Build.

The rest of the changes look good, but trimChar: '_' is going to cause some problems and needs an extra handler. In particular we know that there exist enums of the form DXGI_FORMAT_420_OPAQUE and so removing the prefix DXGI_FORMAT + _ will cause an invalid member name to be generated.

I'm going to merge this as is given that these are rare and there is a trivial workaround where devs can simply remap that specific member. However, if you have time it'd be great to at least add in a diagnostic when such a member is encountered and possibly add back the leading _ to ensure we still get "valid" codegen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, Looks like we first need a check for leading digits (i think that's the only problem from the subset of what's valid in C/C++ and C# at that point) and then another call to EscapeName in case we ended up with a keyword after stripping.
I'll make the changes.

In case of leading digits it's fine to just prepend with @ like EscapeName does?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of leading digits it's fine to just prepend with @ like EscapeName does?

Unfortunately no, @ only works on valid identifiers and is really just to remove ambiguity with respect to language keywords. You'd have to reinsert a leading _ to make it valid if the first character triggers the char.IsDigit check.

}

var kind = isAnonymousEnum ? ValueKind.Primitive : ValueKind.Enumerator;
var flags = ValueFlags.Constant;

Expand Down Expand Up @@ -535,12 +541,12 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
if ((cxxMethodDecl is not null) && cxxMethodDecl.IsVirtual)
{
isVirtual = true;
escapedName = PrefixAndStripName(name, GetOverloadIndex(cxxMethodDecl));
escapedName = PrefixAndStripMethodName(name, GetOverloadIndex(cxxMethodDecl));
}
else
{
isVirtual = false;
escapedName = EscapeAndStripName(name);
escapedName = EscapeAndStripMethodName(name);
}

var returnType = functionDecl.ReturnType;
Expand Down Expand Up @@ -2024,7 +2030,7 @@ void OutputMarkerInterface(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethodD

var desc = new FunctionOrDelegateDesc {
AccessSpecifier = AccessSpecifier.Public,
EscapedName = EscapeAndStripName(name),
EscapedName = EscapeAndStripMethodName(name),
IsMemberFunction = true,
NativeTypeName = nativeTypeName,
HasFnPtrCodeGen = !_config.ExcludeFnptrCodegen,
Expand Down Expand Up @@ -2112,7 +2118,7 @@ void OutputVtblEntry(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethodDecl)
}

var remappedName = FixupNameForMultipleHits(cxxMethodDecl);
var escapedName = EscapeAndStripName(remappedName);
var escapedName = EscapeAndStripMethodName(remappedName);

var desc = new FieldDesc
{
Expand Down Expand Up @@ -2181,7 +2187,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
var desc = new FunctionOrDelegateDesc {
AccessSpecifier = AccessSpecifier.Public,
IsAggressivelyInlined = _config.GenerateAggressiveInlining,
EscapedName = EscapeAndStripName(name),
EscapedName = EscapeAndStripMethodName(name),
ParentName = parentName,
IsMemberFunction = true,
IsInherited = isInherited,
Expand Down Expand Up @@ -2261,7 +2267,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
{
body.Write("Marshal.GetDelegateForFunctionPointer<");
body.BeginMarker("delegate");
body.Write(PrefixAndStripName(name, GetOverloadIndex(cxxMethodDecl)));
body.Write(PrefixAndStripMethodName(name, GetOverloadIndex(cxxMethodDecl)));
body.EndMarker("delegate");
body.Write(">(");
}
Expand All @@ -2270,7 +2276,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
{
body.Write("lpVtbl->");
body.BeginMarker("vtbl", new KeyValuePair<string, object>("explicit", true));
body.Write(EscapeAndStripName(remappedName));
body.Write(EscapeAndStripMethodName(remappedName));
body.EndMarker("vtbl");
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ private void VisitDeclRefExpr(DeclRefExpr declRefExpr)

if (declRefExpr.Decl is FunctionDecl)
{
escapedName = EscapeAndStripName(name);
escapedName = EscapeAndStripMethodName(name);
}
}

Expand Down
61 changes: 38 additions & 23 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,18 +1454,18 @@ static void GenerateTransparentStructs(PInvokeGenerator generator, Stream? strea
return type.Contains('*', StringComparison.Ordinal)
? (8, 4, +1)
: type switch {
"sbyte" => (1, 1, -1),
"byte" => (1, 1, +1),
"short" => (2, 2, -1),
"ushort" => (2, 2, +1),
"int" => (4, 4, -1),
"uint" => (4, 4, +1),
"nint" => (8, 4, -1),
"nuint" => (8, 4, +1),
"long" => (8, 8, -1),
"ulong" => (8, 8, +1),
_ => (0, 0, 0),
};
"sbyte" => (1, 1, -1),
"byte" => (1, 1, +1),
"short" => (2, 2, -1),
"ushort" => (2, 2, +1),
"int" => (4, 4, -1),
"uint" => (4, 4, +1),
"nint" => (8, 4, -1),
"nuint" => (8, 4, +1),
"long" => (8, 8, -1),
"ulong" => (8, 8, +1),
_ => (0, 0, 0),
};
}

static void OutputConversions(StreamWriter sw, string indentString, string name, string type, PInvokeGeneratorTransparentStructKind kind, string target)
Expand Down Expand Up @@ -2326,13 +2326,9 @@ private static string EscapeName(string name)
}
}

private string EscapeAndStripName(string name)
private string EscapeAndStripMethodName(string name)
{
if (name.StartsWith(_config.MethodPrefixToStrip, StringComparison.Ordinal))
{
name = name[_config.MethodPrefixToStrip.Length..];
}

name = PrefixAndStrip(name, _config.MethodPrefixToStrip);
return EscapeName(name);
}

Expand Down Expand Up @@ -5407,7 +5403,7 @@ private static bool IsTransparentStructBoolean(PInvokeGeneratorTransparentStruct
=> kind is PInvokeGeneratorTransparentStructKind.Boolean;

private static bool IsTransparentStructHandle(PInvokeGeneratorTransparentStructKind kind)
=> kind is PInvokeGeneratorTransparentStructKind.Handle
=> kind is PInvokeGeneratorTransparentStructKind.Handle
or PInvokeGeneratorTransparentStructKind.HandleWin32;

private static bool IsTransparentStructHexBased(PInvokeGeneratorTransparentStructKind kind)
Expand Down Expand Up @@ -6153,7 +6149,7 @@ private bool NeedsReturnFixup(CXXMethodDecl cxxMethodDecl)

private static bool NeedsNewKeyword(string name)
{
return name.Equals("Equals",StringComparison.Ordinal)
return name.Equals("Equals", StringComparison.Ordinal)
|| name.Equals("GetHashCode", StringComparison.Ordinal)
|| name.Equals("GetType", StringComparison.Ordinal)
|| name.Equals("MemberwiseClone", StringComparison.Ordinal)
Expand Down Expand Up @@ -6198,13 +6194,32 @@ private void ParenthesizeStmt(Stmt stmt)
}
}

private string PrefixAndStripName(string name, uint overloadIndex)
/// <summary>
/// Checks whether the specified name starts with a prefix, optionally trims a separator character following the prefix and returns the remainder.
/// </summary>
/// <param name="name">The name to strip.</param>
/// <param name="prefix">The prefix to strip from <paramref name="name"/>.</param>
/// <param name="trimChar">Additional separator that may follow <paramref name="prefix"/> which should also be trimmed away.</param>
/// <returns>
/// <paramref name="name"/> if it does not start with <paramref name="prefix"/>;
/// otherwise,
/// the remainder of <paramref name="name"/> after stripping <paramref name="prefix"/> and all immediate following occurences of <paramref name="trimChar"/> from it beginning.
/// </returns>
private static string PrefixAndStrip(string name, string prefix, char trimChar = '\0')
{
if (name.StartsWith(_config.MethodPrefixToStrip, StringComparison.Ordinal))
var nameSpan = name.AsSpan();
if (nameSpan.StartsWith(prefix, StringComparison.Ordinal))
{
name = name[_config.MethodPrefixToStrip.Length..];
nameSpan = nameSpan[prefix.Length..];
nameSpan = nameSpan.TrimStart(trimChar);
return nameSpan.ToString();
}
return name;
}

private string PrefixAndStripMethodName(string name, uint overloadIndex)
{
name = PrefixAndStrip(name, _config.MethodPrefixToStrip);
return $"_{name}{((overloadIndex != 0) ? overloadIndex.ToString(CultureInfo.InvariantCulture) : "")}";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ public IReadOnlyCollection<string> ExcludedNames

public bool GenerateVtblIndexAttribute => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateVtblIndexAttribute);

public bool StripEnumMemberTypeName => _options.HasFlag(PInvokeGeneratorConfigurationOptions.StripEnumMemberTypeName);

public string HeaderText => _headerText;

[AllowNull]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,6 @@ public enum PInvokeGeneratorConfigurationOptions : long
GenerateCallConvMemberFunction = 1L << 37,

GenerateGenericPointerWrapper = 1L << 38,

StripEnumMemberTypeName = 1L << 39,
}
14 changes: 14 additions & 0 deletions sources/ClangSharpPInvokeGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ public static class Program
new TwoColumnHelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."),
new TwoColumnHelpRow("generate-vtbl-index-attribute", "[VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method."),

new TwoColumnHelpRow("", ""),
new TwoColumnHelpRow("# Stripping Options", ""),
new TwoColumnHelpRow("", ""),

new TwoColumnHelpRow("strip-enum-member-type-name", "Strips the enum type name from the beginning of its member names."),

new TwoColumnHelpRow("", ""),
new TwoColumnHelpRow("# Logging Options", ""),
new TwoColumnHelpRow("", ""),
Expand Down Expand Up @@ -617,6 +623,14 @@ public static void Run(InvocationContext context)
break;
}

// Strip Options

case "strip-enum-member-type-name":
{
configOptions |= PInvokeGeneratorConfigurationOptions.StripEnumMemberTypeName;
break;
}

// Legacy Options

case "default-remappings":
Expand Down