diff --git a/.silktouch/2b50d7d447412fec.stout b/.silktouch/2b50d7d447412fec.stout
new file mode 100644
index 0000000000..10237a99ca
Binary files /dev/null and b/.silktouch/2b50d7d447412fec.stout differ
diff --git a/.silktouch/72b687a9cceac28c.stout b/.silktouch/72b687a9cceac28c.stout
new file mode 100644
index 0000000000..641872d24c
Binary files /dev/null and b/.silktouch/72b687a9cceac28c.stout differ
diff --git a/.silktouch/82fde6eb3b68e085.stout b/.silktouch/82fde6eb3b68e085.stout
index 3a6d84ec73..3a1c211031 100644
Binary files a/.silktouch/82fde6eb3b68e085.stout and b/.silktouch/82fde6eb3b68e085.stout differ
diff --git a/.silktouch/91c9aa14a031651f.stout b/.silktouch/91c9aa14a031651f.stout
index 09581e3291..7948b40ed8 100644
Binary files a/.silktouch/91c9aa14a031651f.stout and b/.silktouch/91c9aa14a031651f.stout differ
diff --git a/.silktouch/c8c046b328b09d23.stout b/.silktouch/c8c046b328b09d23.stout
index b8a027b827..6caec14f29 100644
Binary files a/.silktouch/c8c046b328b09d23.stout and b/.silktouch/c8c046b328b09d23.stout differ
diff --git a/eng/silktouch/sdl/SDL3/generate.rsp b/eng/silktouch/sdl/SDL3/generate.rsp
index 280ff68328..90701b84b3 100644
--- a/eng/silktouch/sdl/SDL3/generate.rsp
+++ b/eng/silktouch/sdl/SDL3/generate.rsp
@@ -2,6 +2,7 @@
@../remap.rsp
--exclude
SDL_SetX11EventHook
+SDL_SetWindowsMessageHook
SDL_FUNCTION
SDL_SIZE_MAX
SDL_memcpy
@@ -12,6 +13,9 @@ SDL_EndThreadFunction
SDL_fabsf
SDL_size_add_check_overflow_builtin
SDL_size_mul_check_overflow_builtin
+SDL_FILE
+SDL_LINE
+SDL_NULL_WHILE_LOOP_CONDITION
--file
sdl-SDL.h
--methodClassName
diff --git a/eng/submodules/openal-soft b/eng/submodules/openal-soft
index 9c50193236..6e0d0b39b3 160000
--- a/eng/submodules/openal-soft
+++ b/eng/submodules/openal-soft
@@ -1 +1 @@
-Subproject commit 9c50193236ad4d8c68f390a3151f8253709baabd
+Subproject commit 6e0d0b39b3d9e5b027f01bdd2fbf18ca8ce5a2db
diff --git a/generator.json b/generator.json
index 914ed1551a..e99c8cdf09 100644
--- a/generator.json
+++ b/generator.json
@@ -40,12 +40,15 @@
"Mods": [
"AddIncludes",
"ClangScraper",
+ "MarkNativeNames",
"ExtractNestedTyping",
"TransformHandles",
"TransformFunctions",
"TransformProperties",
"PrettifyNames",
- "AddVTables"
+ "TransformEnums",
+ "AddVTables",
+ "StripAttributes"
],
"ClangScraper": {
"ClangSharpResponseFiles": [
@@ -54,14 +57,32 @@
"InputSourceRoot": "sources/SDL",
"InputTestRoot": "tests/SDL"
},
+ "ExtractNestedTyping": {
+ "DelegateSuffixOrder": 1
+ },
"TransformHandles": {
"AssumeMissingTypesOpaque": true,
- "UseDSL": true
+ "UseDSL": true,
+ "HandleSuffixOrder": 1
},
"TransformFunctions": {
"BoolTypes": {
"SDL_bool": null
}
+ },
+ "PrettifyNames": {
+ "GlobalPrefixHints": ["SDL"]
+ },
+ "TransformEnums": {
+ "AddNoneMemberToFlags": true,
+ "RewriteMemberValues": true
+ },
+ "StripAttributes": {
+ "Remove": [
+ "NativeTypeName",
+ "NameAffix",
+ "Transformed"
+ ]
}
},
"OpenGL": {
@@ -71,13 +92,16 @@
"Mods": [
"AddIncludes",
"ClangScraper",
+ "MarkNativeNames",
"AddApiProfiles",
"BakeSourceSets",
"MixKhronosData",
"AddOpaqueStructs",
"TransformFunctions",
"PrettifyNames",
- "AddVTables"
+ "TransformEnums",
+ "AddVTables",
+ "StripAttributes"
],
"ClangScraper": {
"ClangSharpResponseFiles": [
@@ -123,14 +147,16 @@
]
},
"MixKhronosData": {
- "UseDataTypeTrimmings": true,
"SpecPath": "eng/submodules/opengl/xml/gl.xml",
+ "Namespace": "Silk.NET.OpenGL",
"TypeMap": {
"TraceMaskMESA": "uint",
"PathRenderingTokenNV": "byte",
"PathCoordType": "byte"
},
- "Namespace": "Silk.NET.OpenGL"
+ "TrimFunctionDataTypes": true,
+ "TrimEnumTypeNonExclusiveVendors": true,
+ "TrimEnumMemberImpliedVendors": true
},
"AddVTables": {
"VTables": [
@@ -151,13 +177,27 @@
"LongAcronymThreshold": 4,
"GlobalPrefixHints": ["gl"],
"PrefixOverrides": {
- "SyncObjectMask": "GL_SYNC"
+ "SyncObjectMask": "GL_SYNC",
+ "OcclusionQueryParameterNameNV": "GL",
+ "TexStorageAttribs": "GL",
+ "ContainerType": "GL"
}
},
+ "TransformEnums": {
+ "AddNoneMemberToFlags": true,
+ "RewriteMemberValues": true
+ },
"TransformFunctions": {
"BoolTypes": {
"GLboolean": null
}
+ },
+ "StripAttributes": {
+ "Remove": [
+ "NativeTypeName",
+ "NameAffix",
+ "Transformed"
+ ]
}
},
"OpenAL": {
@@ -167,6 +207,7 @@
"Mods": [
"AddIncludes",
"ClangScraper",
+ "MarkNativeNames",
"ChangeNativeClass",
"AddApiProfiles",
"MixKhronosData",
@@ -175,7 +216,9 @@
"InterceptNativeFunctions",
"TransformFunctions",
"PrettifyNames",
- "AddVTables"
+ "TransformEnums",
+ "AddVTables",
+ "StripAttributes"
],
"ClangScraper": {
"ClangSharpResponseFiles": [
@@ -191,12 +234,7 @@
}
},
"MixKhronosData": {
- "UseDataTypeTrimmings": true,
"SpecPath": "eng/submodules/openal-soft/registry/xml/al.xml",
- "EnumNativeTypeNames": {
- "ALenum": "ALEnum",
- "ALCenum": "ALCEnum"
- },
"Namespace": "Silk.NET.OpenAL",
"NonStandardExtensionNomenclature": true,
"Vendors": [
@@ -204,9 +242,15 @@
"LOKI",
"EXT"
],
- "IgnoreNonVendorSuffixes": [
+ "ExcludeVendorSuffixIdentification": [
+ "ALC_INVALID_CONTEXT"
+ ],
+ "NonVendorSuffixes": [
"Direct"
- ]
+ ],
+ "TrimFunctionDataTypes": true,
+ "TrimEnumTypeNonExclusiveVendors": true,
+ "TrimEnumMemberImpliedVendors": true
},
"InterceptNativeFunctions": {
"NativeFunctionNames": [
@@ -255,6 +299,10 @@
"EAXSetBufferModeDirect": "EAXSetBufferModeDirect"
}
},
+ "TransformEnums": {
+ "AddNoneMemberToFlags": true,
+ "RewriteMemberValues": true
+ },
"TransformFunctions": {
"BoolTypes": {
"ALboolean": null,
@@ -264,7 +312,18 @@
},
"TransformHandles": {
"AssumeMissingTypesOpaque": true,
- "UseDSL": true
+ "UseDSL": true,
+ "HandleSuffixOrder": 1
+ },
+ "ExtractNestedTyping": {
+ "DelegateSuffixOrder": 1
+ },
+ "StripAttributes": {
+ "Remove": [
+ "NativeTypeName",
+ "NameAffix",
+ "Transformed"
+ ]
}
},
"Vulkan": {
@@ -274,16 +333,18 @@
"Mods": [
"AddIncludes",
"ClangScraper",
- "AddApiProfiles",
- "MixKhronosData",
+ "MarkNativeNames",
"ExtractNestedTyping",
"TransformHandles",
+ "MixKhronosData",
+ "AddApiProfiles",
"InterceptNativeFunctions",
"TransformFunctions",
"TransformProperties",
"PrettifyNames",
"TransformEnums",
- "AddVTables"
+ "AddVTables",
+ "StripAttributes"
],
"ClangScraper": {
"ClangSharpResponseFiles": [
@@ -300,13 +361,22 @@
}
]
},
+ "ExtractNestedTyping": {
+ "DelegateSuffixOrder": 1
+ },
"MixKhronosData": {
"SpecPath": "eng/submodules/vulkan/xml/vk.xml",
"Namespace": "Silk.NET.Vulkan",
"FlagsTypes": [
"VkFlags",
"VkFlags64"
- ]
+ ],
+ "ExcludeVendorSuffixIdentification": [
+ "VK_VENDOR_ID_VIV",
+ "VK_VENDOR_ID_VSI",
+ "VK_VENDOR_ID_MESA"
+ ],
+ "TrimEnumMemberImpliedVendors": true
},
"InterceptNativeFunctions": {
"NativeFunctionNames": [
@@ -321,13 +391,15 @@
},
"TransformHandles": {
"AssumeMissingTypesOpaque": true,
- "UseDSL": true
+ "UseDSL": true,
+ "HandleSuffixOrder": 1
},
"PrettifyNames": {
"LongAcronymThreshold": 4,
- "GlobalPrefixHints": ["vk"],
+ "GlobalPrefixHints": ["PFN_vk","vk"],
"PrefixOverrides": {
- "VkPipelineCreateFlags2": "VK_PIPELINE_CREATE_2"
+ "VkPipelineCreateFlags2": "VK_PIPELINE_CREATE_2",
+ "VkMemoryDecompressionMethodFlagsEXT": "VK_MEMORY_DECOMPRESSION_METHOD"
}
},
"TransformEnums": {
@@ -359,6 +431,13 @@
"GenerateFactoryPartial": true
}
]
+ },
+ "StripAttributes": {
+ "Remove": [
+ "NativeTypeName",
+ "NameAffix",
+ "Transformed"
+ ]
}
}
}
diff --git a/sources/Core/Core/Annotations/NameAffixAttribute.cs b/sources/Core/Core/Annotations/NameAffixAttribute.cs
new file mode 100644
index 0000000000..773c6f3722
--- /dev/null
+++ b/sources/Core/Core/Annotations/NameAffixAttribute.cs
@@ -0,0 +1,74 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Core;
+
+///
+/// Attribute used by the SilkTouch bindings generator to store information about identified name affixes.
+///
+[AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Delegate
+ | AttributeTargets.Enum
+ | AttributeTargets.Field
+ | AttributeTargets.Method
+ | AttributeTargets.Parameter
+ | AttributeTargets.Property
+ | AttributeTargets.Struct,
+ AllowMultiple = true,
+ Inherited = true
+)]
+[Conditional("DEBUG")]
+public sealed class NameAffixAttribute : Attribute
+{
+ ///
+ /// Creates a new NameAffix attribute.
+ ///
+ public NameAffixAttribute(string type, string affix, int order, int discriminatorPriority)
+ {
+ Type = type;
+ Affix = affix;
+ Order = order;
+ DiscriminatorPriority = discriminatorPriority;
+ }
+
+ ///
+ /// The type of affix. Either "Prefix" or "Suffix".
+ ///
+ public string Type { get; }
+
+ ///
+ /// The affix of the identifier.
+ ///
+ public string Affix { get; }
+
+ ///
+ /// The order with which the affix is applied.
+ ///
+ /// Negative means the affix is not reapplied after trimming.
+ /// Higher means the affix is applied first.
+ ///
+ /// Affixes with the same order have ties broken using the order they are declared on the identifier.
+ /// First declared are applied first.
+ ///
+ ///
+ /// SilkTouch mods setting this property should use either -1, 0, or a value specified through the mod's configuration.
+ /// This is to reduce the hardcoding of values and to make the mods more applicable to different sets of bindings.
+ ///
+ public int Order { get; }
+
+ ///
+ /// The priority with which the affix is used
+ /// to create secondary names in case of conflicts.
+ ///
+ /// Negative means the affix is required, but won't be used to create secondaries.
+ /// Non-negative means the affix is optional, but will be used to create secondaries.
+ /// Higher means the names created using the affix is tried first.
+ ///
+ /// Affixes with the same priority are applied together as a group.
+ ///
+ ///
+ /// SilkTouch mods setting this property should use either -1, 0, or a value specified through the mod's configuration.
+ /// This is to reduce the hardcoding of values and to make the mods more applicable to different sets of bindings.
+ ///
+ public int DiscriminatorPriority { get; }
+}
diff --git a/sources/Core/Core/Annotations/NativeNameAttribute.cs b/sources/Core/Core/Annotations/NativeNameAttribute.cs
new file mode 100644
index 0000000000..6cb2db2721
--- /dev/null
+++ b/sources/Core/Core/Annotations/NativeNameAttribute.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Core;
+
+///
+/// Stores the native name of the identifier that this attribute is placed on.
+///
+[AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Delegate
+ | AttributeTargets.Enum
+ | AttributeTargets.Field
+ | AttributeTargets.Method
+ | AttributeTargets.Parameter
+ | AttributeTargets.Property
+ | AttributeTargets.Struct,
+ AllowMultiple = false,
+ Inherited = true
+)]
+public sealed class NativeNameAttribute : Attribute
+{
+ /// The native name of the identifier.
+ public NativeNameAttribute(string name) => Name = name;
+
+ /// The native name of the identifier.
+ public string Name { get; }
+}
diff --git a/sources/Core/Core/Annotations/SupportedApiProfileAttribute.cs b/sources/Core/Core/Annotations/SupportedApiProfileAttribute.cs
index d55997bf36..083d0d5948 100644
--- a/sources/Core/Core/Annotations/SupportedApiProfileAttribute.cs
+++ b/sources/Core/Core/Annotations/SupportedApiProfileAttribute.cs
@@ -19,7 +19,8 @@ namespace Silk.NET.Core;
| AttributeTargets.Method
| AttributeTargets.Module
| AttributeTargets.Property
- | AttributeTargets.Struct,
+ | AttributeTargets.Struct
+ | AttributeTargets.Delegate,
AllowMultiple = true,
Inherited = false
)]
diff --git a/sources/Core/Core/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/sources/Core/Core/PublicAPI/net10.0/PublicAPI.Unshipped.txt
index d6847290ae..938a5e97bc 100644
--- a/sources/Core/Core/PublicAPI/net10.0/PublicAPI.Unshipped.txt
+++ b/sources/Core/Core/PublicAPI/net10.0/PublicAPI.Unshipped.txt
@@ -153,6 +153,12 @@ Silk.NET.Core.MaybeBool.MaybeBool() -> void
Silk.NET.Core.MaybeBool.MaybeBool(T Value) -> void
Silk.NET.Core.MaybeBool.Value.get -> T
Silk.NET.Core.MaybeBool.Value.init -> void
+Silk.NET.Core.NameAffixAttribute
+Silk.NET.Core.NameAffixAttribute.Affix.get -> string!
+Silk.NET.Core.NameAffixAttribute.DiscriminatorPriority.get -> int
+Silk.NET.Core.NameAffixAttribute.NameAffixAttribute(string! type, string! affix, int order, int discriminatorPriority) -> void
+Silk.NET.Core.NameAffixAttribute.Order.get -> int
+Silk.NET.Core.NameAffixAttribute.Type.get -> string!
Silk.NET.Core.NativeFunctionAttribute
Silk.NET.Core.NativeFunctionAttribute.EntryPoint -> string?
Silk.NET.Core.NativeFunctionAttribute.NativeFunctionAttribute(string! dllName) -> void
@@ -160,6 +166,9 @@ Silk.NET.Core.NativeFunctionAttribute.Value.get -> string!
Silk.NET.Core.NativeInheritanceAttribute
Silk.NET.Core.NativeInheritanceAttribute.Name.get -> string!
Silk.NET.Core.NativeInheritanceAttribute.NativeInheritanceAttribute(string! name) -> void
+Silk.NET.Core.NativeNameAttribute
+Silk.NET.Core.NativeNameAttribute.Name.get -> string!
+Silk.NET.Core.NativeNameAttribute.NativeNameAttribute(string! name) -> void
Silk.NET.Core.NativeTypeNameAttribute
Silk.NET.Core.NativeTypeNameAttribute.Name.get -> string!
Silk.NET.Core.NativeTypeNameAttribute.NativeTypeNameAttribute(string! name) -> void
diff --git a/sources/SilkTouch/SilkTouch/Clang/WindowsStdIncludeResolver.cs b/sources/SilkTouch/SilkTouch/Clang/WindowsStdIncludeResolver.cs
index 21638c0b02..3553dcf0a7 100644
--- a/sources/SilkTouch/SilkTouch/Clang/WindowsStdIncludeResolver.cs
+++ b/sources/SilkTouch/SilkTouch/Clang/WindowsStdIncludeResolver.cs
@@ -37,6 +37,6 @@ public virtual IEnumerable GetStandardIncludes()
}
_logger.LogWarning("Failed to resolve VS, but OS is Windows!");
- return Enumerable.Empty();
+ return [];
}
}
diff --git a/sources/SilkTouch/SilkTouch/JobDependencies.cs b/sources/SilkTouch/SilkTouch/JobDependencies.cs
index 5c51d3341c..1b468f1156 100644
--- a/sources/SilkTouch/SilkTouch/JobDependencies.cs
+++ b/sources/SilkTouch/SilkTouch/JobDependencies.cs
@@ -28,6 +28,6 @@ public IEnumerable Get(string? key) =>
: null
)
.OfType()
- : Enumerable.Empty();
+ : [];
}
}
diff --git a/sources/SilkTouch/SilkTouch/Mods/AddIncludes.cs b/sources/SilkTouch/SilkTouch/Mods/AddIncludes.cs
index 97aea64869..11de1ecadb 100644
--- a/sources/SilkTouch/SilkTouch/Mods/AddIncludes.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/AddIncludes.cs
@@ -63,27 +63,18 @@ public async Task> BeforeScrapeAsync(string key, List $"--include-directory={x}")
- ?? Enumerable.Empty()
- );
- cmdLineArgs.AddRange(
- cfg.AdditionalIncludes?.Select(x => $"--include-directory={x}")
- ?? Enumerable.Empty()
- );
+
+ cmdLineArgs.InsertRange(0, cfg.PriorityIncludes?.Select(x => $"--include-directory={x}") ?? []);
+ cmdLineArgs.AddRange(cfg.AdditionalIncludes?.Select(x => $"--include-directory={x}") ?? []);
+
if (!cfg.SuppressStdIncludes)
{
cmdLineArgs.AddRange(stdResolver.GetStandardIncludes());
}
var matcher = new Matcher();
- matcher.AddIncludePatterns(
- cfg.RemoveMatchingIncludes?.Where(x => x[0] != '!') ?? Enumerable.Empty()
- );
- matcher.AddExcludePatterns(
- cfg.RemoveMatchingIncludes?.Where(x => x[0] == '!') ?? Enumerable.Empty()
- );
+ matcher.AddIncludePatterns(cfg.RemoveMatchingIncludes?.Where(x => x[0] != '!') ?? []);
+ matcher.AddExcludePatterns(cfg.RemoveMatchingIncludes?.Where(x => x[0] == '!') ?? []);
for (var j = 0; j < cmdLineArgs.Count; j++)
{
string? path = null;
diff --git a/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs b/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs
index e4c907671d..e1ad5d8d85 100644
--- a/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs
@@ -751,9 +751,7 @@ out var callConv
|| !node.Modifiers.Any(SyntaxKind.StaticKeyword)
|| (
(node.Body is not null || node.ExpressionBody is not null)
- && !node.AttributeLists.Any(x =>
- x.Attributes.Any(y => y.IsAttribute("Silk.NET.Core.Transformed"))
- )
+ && !node.AttributeLists.ContainsAttribute("Silk.NET.Core.Transformed")
)
|| parent is null
)
@@ -779,13 +777,9 @@ out var callConv
x.WithAttributes(
SeparatedList(
x.Attributes.Where(y =>
- !y.IsAttribute(
- "System.Runtime.InteropServices.DllImport"
- )
+ !y.IsAttribute("System.Runtime.InteropServices.DllImport")
&& !y.IsAttribute("Silk.NET.Core.NativeFunction")
- && !y.IsAttribute(
- "System.Runtime.CompilerServices.MethodImpl"
- )
+ && !y.IsAttribute("System.Runtime.CompilerServices.MethodImpl")
)
)
)
diff --git a/sources/SilkTouch/SilkTouch/Mods/Bakery/DefaultBakeStrategy.cs b/sources/SilkTouch/SilkTouch/Mods/Bakery/DefaultBakeStrategy.cs
index e11aac3239..40d609aeda 100644
--- a/sources/SilkTouch/SilkTouch/Mods/Bakery/DefaultBakeStrategy.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/Bakery/DefaultBakeStrategy.cs
@@ -80,7 +80,7 @@ public virtual MemberDeclarationSyntax BakeMember(
ty.BaseList.Types.Concat(
((BaseTypeDeclarationSyntax)existing.Value.Syntax)
.BaseList
- ?.Types ?? Enumerable.Empty()
+ ?.Types ?? []
)
.DistinctBy(x => x.ToString())
)
@@ -197,9 +197,7 @@ node is BaseMethodDeclarationSyntax meth
node.AttributeLists.Select(x =>
x.WithAttributes(
SeparatedList(
- x.Attributes.Where(y =>
- y.IsAttribute("Silk.NET.Core.SupportedApiAttribute")
- )
+ x.Attributes.Where(y => y.IsAttribute("Silk.NET.Core.SupportedApiAttribute"))
)
)
)
diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/AttributeUtils.cs b/sources/SilkTouch/SilkTouch/Mods/Common/AttributeUtils.cs
new file mode 100644
index 0000000000..33dd125185
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Mods/Common/AttributeUtils.cs
@@ -0,0 +1,490 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Silk.NET.SilkTouch.Naming;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Silk.NET.SilkTouch.Mods;
+
+///
+/// Common utilities related to manipulating attributes.
+///
+public static class AttributeUtils
+{
+ ///
+ /// Determines (naively) whether the given attribute syntax represents the specified attribute.
+ ///
+ /// The attribute syntax to check.
+ ///
+ /// The fully-qualified attribute name including the namespace but without the Attribute suffix.
+ ///
+ /// Whether it is probably the specified attribute.
+ public static bool IsAttribute(this AttributeSyntax node, string fullNameWithoutSuffix)
+ {
+ var sep = node.Name.ToString().Split("::").Last();
+ var name = fullNameWithoutSuffix.Split('.').Last();
+ return sep == name
+ || sep == $"{name}Attribute"
+ || sep.EndsWith(fullNameWithoutSuffix)
+ || sep.EndsWith($"{fullNameWithoutSuffix}Attribute");
+ }
+
+ ///
+ /// Checks whether the given attribute syntax lists contains the specified attribute.
+ ///
+ /// The attribute syntax lists to check.
+ ///
+ /// The fully-qualified attribute name including the namespace but without the Attribute suffix.
+ ///
+ public static bool ContainsAttribute(this IEnumerable attributeLists, string fullNameWithoutSuffix) =>
+ attributeLists.Any(list => list.Attributes.Any(attribute => attribute.IsAttribute(fullNameWithoutSuffix)));
+
+ ///
+ /// Modifies the s the method may have to make them resistant to method identifier
+ /// changes.
+ ///
+ /// The original method.
+ /// The modified method.
+ public static MethodDeclarationSyntax WithRenameSafeAttributeLists(
+ this MethodDeclarationSyntax node
+ ) =>
+ node.WithAttributeLists(
+ List(
+ node.AttributeLists.Select(x =>
+ x.WithAttributes(
+ SeparatedList(
+ x.Attributes.Select(y =>
+ y.IsAttribute("System.Runtime.InteropServices.DllImport")
+ && (
+ y.ArgumentList?.Arguments.All(z =>
+ z.NameEquals?.Name.ToString() != "EntryPoint"
+ ) ?? true
+ )
+ ? y.AddArgumentListArguments(
+ AttributeArgument(
+ LiteralExpression(
+ SyntaxKind.StringLiteralExpression,
+ Literal(node.Identifier.ToString())
+ )
+ )
+ .WithNameEquals(NameEquals("EntryPoint"))
+ )
+ : y
+ )
+ )
+ )
+ )
+ )
+ );
+
+ ///
+ /// Adds to the given 's attribute lists.
+ ///
+ /// The method.
+ /// The modified method.
+ public static MethodDeclarationSyntax AddMaxOpt(this MethodDeclarationSyntax meth) =>
+ meth.AddAttributeLists(MaxOpt);
+
+ ///
+ /// Gets the library name and entry-point for the given attribute list, or returns false if this is not a native
+ /// interop function.
+ ///
+ /// The attribute lists.
+ /// The library name.
+ /// The entry-point.
+ /// The calling convention (if any).
+ /// Whether the native function info was found.
+ public static bool GetNativeFunctionInfo(
+ this IEnumerable attrLists,
+ [NotNullWhen(true)] out string? libraryName,
+ out string? entryPoint,
+ out string? callConv
+ )
+ {
+ foreach (
+ var attr in from attrList in attrLists
+ from attr in attrList.Attributes
+ where
+ attr.IsAttribute("System.Runtime.InteropServices.DllImport")
+ || attr.IsAttribute("Silk.NET.Core.NativeFunction")
+ select attr
+ )
+ {
+ libraryName =
+ (attr.ArgumentList?.Arguments.First().Expression as LiteralExpressionSyntax)
+ ?.Token
+ .ValueText
+ ?? throw new InvalidOperationException("DllImport was found but was not valid");
+ entryPoint = (
+ attr.ArgumentList?.Arguments.FirstOrDefault(x =>
+ x.NameEquals is not null
+ && x.NameEquals.Name.ToString() == nameof(DllImportAttribute.EntryPoint)
+ )?.Expression as LiteralExpressionSyntax
+ )
+ ?.Token
+ .ValueText;
+ callConv = (
+ attr.ArgumentList?.Arguments.FirstOrDefault(x =>
+ x.NameEquals is not null
+ && x.NameEquals.Name.ToString() == nameof(DllImportAttribute.CallingConvention)
+ )?.Expression as MemberAccessExpressionSyntax
+ )?.Name.ToString();
+ return true;
+ }
+
+ libraryName = entryPoint = callConv = null;
+ return false;
+ }
+
+ ///
+ /// Adds a NativeFunctionAttribute if a is present on the
+ /// .
+ ///
+ /// The method to modify.
+ /// The original method.
+ /// The (possibly) modified method.
+ /// If a DllImportAttribute was found but wasn't valid.
+ public static MethodDeclarationSyntax AddNativeFunction(
+ this MethodDeclarationSyntax meth,
+ MethodDeclarationSyntax original
+ )
+ {
+ if (
+ original.AttributeLists.GetNativeFunctionInfo(
+ out var libName,
+ out var entryPoint,
+ out _
+ )
+ )
+ {
+ return meth.AddAttributeLists(
+ AttributeList(
+ SingletonSeparatedList(
+ Attribute(IdentifierName("NativeFunction"))
+ .WithArgumentList(
+ AttributeArgumentList(
+ SeparatedList(
+ new[]
+ {
+ AttributeArgument(
+ LiteralExpression(
+ SyntaxKind.StringLiteralExpression,
+ Literal(libName)
+ )
+ ),
+ AttributeArgument(
+ LiteralExpression(
+ SyntaxKind.StringLiteralExpression,
+ Literal(
+ entryPoint ?? meth.Identifier.ToString()
+ )
+ )
+ )
+ .WithNameEquals(NameEquals("EntryPoint")),
+ }
+ )
+ )
+ )
+ )
+ )
+ );
+ }
+
+ return meth;
+ }
+
+ ///
+ /// Adds a name prefix attribute to the given attribute list.
+ ///
+ ///
+ /// See for what the parameters do.
+ ///
+ public static SyntaxList AddNamePrefix(this IEnumerable attributeLists, string prefix, int order, int discriminatorPriority = -1)
+ => attributeLists.AddNamePrefixOrSuffix("Prefix", prefix, order, discriminatorPriority);
+
+ ///
+ /// Adds a name suffix attribute to the given attribute list.
+ ///
+ ///
+ /// See for what the parameters do.
+ ///
+ public static SyntaxList AddNameSuffix(this IEnumerable attributeLists, string suffix, int order, int discriminatorPriority = -1)
+ => attributeLists.AddNamePrefixOrSuffix("Suffix", suffix, order, discriminatorPriority);
+
+ private static SyntaxList AddNamePrefixOrSuffix(this IEnumerable attributeLists, string type, string affix, int order, int discriminatorPriority)
+ {
+ var typeArgument = AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal($"\"{type}\"", type)));
+ var affixArgument = AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal($"\"{affix}\"", affix)));
+ var orderArgument = AttributeArgument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(order)));
+ var discriminatorPriorityArgument = AttributeArgument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(discriminatorPriority)));
+ var argumentList = AttributeArgumentList([typeArgument, affixArgument, orderArgument, discriminatorPriorityArgument]);
+
+ var attribute = AttributeList([
+ Attribute(IdentifierName("NameAffix"), argumentList),
+ ]);
+
+ return [
+ // Add attribute to the top of the attribute lists
+ // This is important and ensures that affixes added later are applied first
+ // Affixes that are added later are usually affixes on the inside of the name
+ // This ensures we apply affixes of the same priority starting from the inside first
+ attribute,
+ ..attributeLists,
+ ];
+ }
+
+ ///
+ /// Gets the native name or returns the specified default.
+ ///
+ ///
+ /// The default usually should be the node's identifier.
+ ///
+ public static string GetNativeNameOrDefault(this IEnumerable attributeLists, SyntaxToken identifier)
+ {
+ if (TryGetNativeName(attributeLists, out var nativeName))
+ {
+ return nativeName;
+ }
+
+ return identifier.ToString();
+ }
+
+ ///
+ /// Gets the native name or returns the specified default.
+ ///
+ ///
+ /// The default usually should be the node's identifier.
+ ///
+ public static string GetNativeNameOrDefault(this IEnumerable attributeLists, string defaultName)
+ {
+ if (TryGetNativeName(attributeLists, out var nativeName))
+ {
+ return nativeName;
+ }
+
+ return defaultName;
+ }
+
+ ///
+ /// Gets the value of the native name attribute from the given attribute list.
+ ///
+ public static bool TryGetNativeName(this IEnumerable attributeLists, [NotNullWhen(true)] out string? nativeName)
+ {
+ var nativeNameAttribute = attributeLists.SelectMany(list => list.Attributes).FirstOrDefault(attribute => attribute.IsAttribute("Silk.NET.Core.NativeName"));
+ if (nativeNameAttribute == null)
+ {
+ nativeName = null;
+ return false;
+ }
+
+ var arg = nativeNameAttribute.ArgumentList?.Arguments[0];
+ nativeName = (arg?.Expression as LiteralExpressionSyntax)?.Token.Value as string;
+
+ return nativeName != null;
+ }
+
+ ///
+ /// Sets or replaces the native name attribute in the given attribute list.
+ ///
+ public static SyntaxList WithNativeName(this IEnumerable attributeLists, string nativeName)
+ {
+ var nativeNameAttribute = AttributeList([
+ Attribute(
+ IdentifierName("NativeName"),
+ AttributeArgumentList([
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal($"\"{nativeName}\"", nativeName))),
+ ])),
+ ]);
+
+ return List(attributeLists.Select(list => {
+ var attributes = list.Attributes;
+ attributes = [..attributes.Where(attribute => !attribute.IsAttribute("Silk.NET.Core.NativeName"))];
+
+ return attributes.Count == 0 ? null : list.WithAttributes(attributes);
+ })
+ .Where(list => list != null)
+ .Cast()
+ .Prepend(nativeNameAttribute));
+ }
+
+ ///
+ /// Retrieves the native type name within the given attribute list.
+ ///
+ /// The attributes.
+ /// The required attribute target/context.
+ /// The native type name.
+ public static string? GetNativeTypeName(
+ this IEnumerable attrs,
+ SyntaxKind? requireContext = null
+ ) =>
+ attrs
+ .SelectMany(x =>
+ (x.Target is null && requireContext is null)
+ || (requireContext is { } rc && (x.Target?.Identifier.IsKind(rc) ?? false))
+ ? x.Attributes
+ : []
+ )
+ .FirstOrDefault(x => x.IsAttribute("Silk.NET.Core.NativeTypeName"))
+ ?.ArgumentList?.Arguments.Select(x =>
+ x.Expression.IsKind(SyntaxKind.StringLiteralExpression)
+ ? (x.Expression as LiteralExpressionSyntax)?.Token.Value
+ : null
+ )
+ .OfType()
+ .FirstOrDefault();
+
+ ///
+ /// Gets the native element type name indicated by the given attributes.
+ ///
+ /// The attributes.
+ /// The parsed native type info. Invalid if this method returns false.
+ /// Whether the type name was successfully parsed.
+ ///
+ /// This does not handle all of the possible cases.
+ ///
+ public static bool TryParseNativeTypeName(
+ this IEnumerable attrs,
+ out NativeTypeNameInfo info
+ )
+ {
+ var nativeTypeNameString = attrs.GetNativeTypeName();
+ var nativeTypeName = nativeTypeNameString.AsSpan();
+ if (nativeTypeName.Length == 0)
+ {
+ // Name does not exist
+ info = default;
+ return false;
+ }
+
+ switch (nativeTypeName)
+ {
+ // Handle case: "#define NAME VALUE"
+ case {} when nativeTypeName.StartsWith("#define "):
+ {
+ // Trim off the #define
+ var nativeTypeSpan = nativeTypeName["#define ".Length..].Trim();
+
+ var indexOfFirstSpace = nativeTypeSpan.IndexOf(' ');
+ if (indexOfFirstSpace < 0)
+ {
+ info = new NativeTypeNameInfo()
+ {
+ Name = nativeTypeName.ToString(),
+ Value = null,
+ IndirectionLevels = 0,
+ IsDefine = true,
+ IsConst = true,
+ };
+
+ return true;
+ }
+
+ var name = nativeTypeSpan[..indexOfFirstSpace];
+ var value = nativeTypeSpan[indexOfFirstSpace..];
+
+ info = new NativeTypeNameInfo()
+ {
+ Name = name.ToString(),
+ Value = value.ToString(),
+ IndirectionLevels = 0,
+ IsDefine = true,
+ IsConst = true,
+ };
+
+ return true;
+ }
+
+ // Handle cases: "const NAME **", "NAME **"
+ case {}:
+ {
+ // Trim off the const
+ var isConst = false;
+ if (nativeTypeName.StartsWith("const "))
+ {
+ nativeTypeName = nativeTypeName["const ".Length..].Trim();
+ isConst = true;
+ }
+
+ // Isolate the name
+ var indirectionLevels = nativeTypeName.Count('*');
+ if (nativeTypeName.IndexOf('*') is not -1 and var idx)
+ {
+ nativeTypeName = nativeTypeName[..idx];
+ }
+ nativeTypeName = nativeTypeName.Trim();
+
+ if (nativeTypeName.ContainsAnyExcept(NameUtils.IdentifierChars))
+ {
+ // Unsupported format
+ // Eg: const uint32_t[8]
+ info = default;
+ return false;
+ }
+
+ info = new NativeTypeNameInfo()
+ {
+ Name = nativeTypeName.ToString(),
+ Value = null,
+ IndirectionLevels = indirectionLevels,
+ IsDefine = false,
+ IsConst = isConst,
+ };
+
+ return true;
+ }
+ }
+ }
+
+ ///
+ /// Gets a native type name for a parameter.
+ ///
+ /// The parameter.
+ /// The native type name.
+ public static string? GetNativeTypeName(this ParameterSyntax syntax) =>
+ syntax.AttributeLists.GetNativeTypeName();
+
+ ///
+ /// Gets a native type name for the return type of a method.
+ ///
+ /// The method.
+ /// The native type name.
+ public static string? GetNativeReturnTypeName(this BaseMethodDeclarationSyntax syntax) =>
+ syntax.AttributeLists.GetNativeTypeName(SyntaxKind.ReturnKeyword);
+
+ ///
+ /// Gets an attribute list representing a with
+ /// and
+ /// set.
+ ///
+ public static readonly AttributeListSyntax MaxOpt = AttributeList(
+ SingletonSeparatedList(
+ Attribute(IdentifierName("MethodImpl"))
+ .WithArgumentList(
+ AttributeArgumentList(
+ SingletonSeparatedList(
+ AttributeArgument(
+ BinaryExpression(
+ SyntaxKind.BitwiseOrExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("MethodImplOptions"),
+ IdentifierName("AggressiveInlining")
+ ),
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("MethodImplOptions"),
+ IdentifierName("AggressiveOptimization")
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+}
diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs
index 1ab4436420..1e6cf120f8 100644
--- a/sources/SilkTouch/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs
@@ -95,10 +95,7 @@ public static SyntaxList GetUsings(
.WithLeadingTrivia(
usingsToAdd
.Select(y => y.Value.GetLeadingTrivia())
- .Concat(
- comp?.Members.Select(y => y.GetLeadingTrivia())
- ?? Enumerable.Empty()
- )
+ .Concat(comp?.Members.Select(y => y.GetLeadingTrivia()) ?? [])
.OrderByDescending(y =>
y.Count(z =>
z.Kind()
diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/ModLoader.cs b/sources/SilkTouch/SilkTouch/Mods/Common/ModLoader.cs
index 86fe871a66..8556bd7539 100644
--- a/sources/SilkTouch/SilkTouch/Mods/Common/ModLoader.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/Common/ModLoader.cs
@@ -32,6 +32,8 @@ public class ModLoader
nameof(ClangScraper) => typeof(ClangScraper),
nameof(ChangeNativeClass) => typeof(ChangeNativeClass),
nameof(InterceptNativeFunctions) => typeof(InterceptNativeFunctions),
+ nameof(MarkNativeNames) => typeof(MarkNativeNames),
+ nameof(StripAttributes) => typeof(StripAttributes),
_ => null,
};
}
diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs b/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs
index eb5b2180b5..cbdb8ee0c2 100644
--- a/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs
@@ -1,16 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
+using System.Diagnostics;
using System.Text.RegularExpressions;
using ClangSharp;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Silk.NET.SilkTouch.Naming;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Silk.NET.SilkTouch.Mods;
@@ -133,7 +126,7 @@ public static string DiscrimStr(
) =>
(modifiers?.Any(SyntaxKind.StaticKeyword) ?? false ? "static " : string.Empty)
+ $"{DiscrimStr(modifiers, returnType)} {identifier}{tParams}"
- + $"({string.Join(", ", @params?.Select(DiscrimStr) ?? Enumerable.Empty())})";
+ + $"({string.Join(", ", @params?.Select(DiscrimStr) ?? [])})";
///
/// Gets a string that can be used to discriminate a function-like element for baking purposes.
@@ -160,44 +153,6 @@ public static string DiscrimStr(
public static string DiscrimStr(BaseParameterSyntax param) =>
DiscrimStr(param.Modifiers, param.Type);
- ///
- /// Modifies the s the method may have to make them resistant to method identifier
- /// changes.
- ///
- /// The original method.
- /// The modified method.
- public static MethodDeclarationSyntax WithRenameSafeAttributeLists(
- this MethodDeclarationSyntax node
- ) =>
- node.WithAttributeLists(
- List(
- node.AttributeLists.Select(x =>
- x.WithAttributes(
- SeparatedList(
- x.Attributes.Select(y =>
- y.IsAttribute("System.Runtime.InteropServices.DllImport")
- && (
- y.ArgumentList?.Arguments.All(z =>
- z.NameEquals?.Name.ToString() != "EntryPoint"
- ) ?? true
- )
- ? y.AddArgumentListArguments(
- AttributeArgument(
- LiteralExpression(
- SyntaxKind.StringLiteralExpression,
- Literal(node.Identifier.ToString())
- )
- )
- .WithNameEquals(NameEquals("EntryPoint"))
- )
- : y
- )
- )
- )
- )
- )
- );
-
///
/// Gets the relative path for this document.
///
@@ -525,284 +480,6 @@ this PInvokeGeneratorConfiguration cfg
return options;
}
- ///
- /// Determines (naively) whether the given attribute syntax represents the specified attribute.
- ///
- /// The attribute syntax.
- ///
- /// The fully-qualified attribute name including the namespace but without the Attribute suffix.
- ///
- /// Whether it is probably the specified attribute.
- public static bool IsAttribute(this AttributeSyntax node, string fullNameWithoutSuffix)
- {
- var sep = node.Name.ToString().Split("::").Last();
- var name = fullNameWithoutSuffix.Split('.').Last();
- return sep == name
- || sep == $"{name}Attribute"
- || sep.EndsWith(fullNameWithoutSuffix)
- || sep.EndsWith($"{fullNameWithoutSuffix}Attribute");
- }
-
- ///
- /// Adds to the given 's attribute lists.
- ///
- /// The method.
- /// The modified method.
- public static MethodDeclarationSyntax AddMaxOpt(this MethodDeclarationSyntax meth) =>
- meth.AddAttributeLists(MaxOpt);
-
- ///
- /// Gets the library name and entry-point for the given attribute list, or returns false if this is not a native
- /// interop function.
- ///
- /// The attribute lists.
- /// The library name.
- /// The entry-point.
- /// The calling convention (if any).
- /// Whether the native function info was found.
- public static bool GetNativeFunctionInfo(
- this IEnumerable attrLists,
- [NotNullWhen(true)] out string? libraryName,
- out string? entryPoint,
- out string? callConv
- )
- {
- foreach (
- var attr in from attrList in attrLists
- from attr in attrList.Attributes
- where
- attr.IsAttribute("System.Runtime.InteropServices.DllImport")
- || attr.IsAttribute("Silk.NET.Core.NativeFunction")
- select attr
- )
- {
- libraryName =
- (attr.ArgumentList?.Arguments.First().Expression as LiteralExpressionSyntax)
- ?.Token
- .ValueText
- ?? throw new InvalidOperationException("DllImport was found but was not valid");
- entryPoint = (
- attr.ArgumentList?.Arguments.FirstOrDefault(x =>
- x.NameEquals is not null
- && x.NameEquals.Name.ToString() == nameof(DllImportAttribute.EntryPoint)
- )?.Expression as LiteralExpressionSyntax
- )
- ?.Token
- .ValueText;
- callConv = (
- attr.ArgumentList?.Arguments.FirstOrDefault(x =>
- x.NameEquals is not null
- && x.NameEquals.Name.ToString() == nameof(DllImportAttribute.CallingConvention)
- )?.Expression as MemberAccessExpressionSyntax
- )?.Name.ToString();
- return true;
- }
-
- libraryName = entryPoint = callConv = null;
- return false;
- }
-
- ///
- /// Adds a NativeFunctionAttribute if a is present on the
- /// .
- ///
- /// The method to modify.
- /// The original method.
- /// The (possibly) modified method.
- /// If a DllImportAttribute was found but wasn't valid.
- public static MethodDeclarationSyntax AddNativeFunction(
- this MethodDeclarationSyntax meth,
- MethodDeclarationSyntax original
- )
- {
- if (
- original.AttributeLists.GetNativeFunctionInfo(
- out var libName,
- out var entryPoint,
- out _
- )
- )
- {
- return meth.AddAttributeLists(
- AttributeList(
- SingletonSeparatedList(
- Attribute(IdentifierName("NativeFunction"))
- .WithArgumentList(
- AttributeArgumentList(
- SeparatedList(
- new[]
- {
- AttributeArgument(
- LiteralExpression(
- SyntaxKind.StringLiteralExpression,
- Literal(libName)
- )
- ),
- AttributeArgument(
- LiteralExpression(
- SyntaxKind.StringLiteralExpression,
- Literal(
- entryPoint ?? meth.Identifier.ToString()
- )
- )
- )
- .WithNameEquals(NameEquals("EntryPoint")),
- }
- )
- )
- )
- )
- )
- );
- }
-
- return meth;
- }
-
- ///
- /// Retrieves the native type name within the given attribute list.
- ///
- /// The attributes.
- /// The required attribute target/context.
- /// The native type name.
- public static string? GetNativeTypeName(
- this IEnumerable attrs,
- SyntaxKind? requireContext = null
- ) =>
- attrs
- .SelectMany(x =>
- (x.Target is null && requireContext is null)
- || (requireContext is { } rc && (x.Target?.Identifier.IsKind(rc) ?? false))
- ? x.Attributes
- : Enumerable.Empty()
- )
- .FirstOrDefault(x => x.IsAttribute("Silk.NET.Core.NativeTypeName"))
- ?.ArgumentList?.Arguments.Select(x =>
- x.Expression.IsKind(SyntaxKind.StringLiteralExpression)
- ? (x.Expression as LiteralExpressionSyntax)?.Token.Value
- : null
- )
- .OfType()
- .FirstOrDefault();
-
- ///
- /// Gets the native element type name indicated by the given attributes.
- ///
- /// The attributes.
- /// The parsed native type info. Invalid if this method returns false.
- /// Whether the type name was successfully parsed.
- ///
- /// This does not handle all of the possible cases.
- ///
- public static bool TryParseNativeTypeName(
- this IEnumerable attrs,
- out NativeTypeNameInfo info
- )
- {
- var nativeTypeNameString = attrs.GetNativeTypeName();
- var nativeTypeName = nativeTypeNameString.AsSpan();
- if (nativeTypeName.Length == 0)
- {
- // Name does not exist
- info = default;
- return false;
- }
-
- switch (nativeTypeName)
- {
- // Handle case: "#define NAME VALUE"
- case {} when nativeTypeName.StartsWith("#define "):
- {
- // Trim off the #define
- var nativeTypeSpan = nativeTypeName["#define ".Length..].Trim();
-
- var indexOfFirstSpace = nativeTypeSpan.IndexOf(' ');
- if (indexOfFirstSpace < 0)
- {
- info = new NativeTypeNameInfo()
- {
- Name = nativeTypeName.ToString(),
- Value = null,
- IndirectionLevels = 0,
- IsDefine = true,
- IsConst = true,
- };
-
- return true;
- }
-
- var name = nativeTypeSpan[..indexOfFirstSpace];
- var value = nativeTypeSpan[indexOfFirstSpace..];
-
- info = new NativeTypeNameInfo()
- {
- Name = name.ToString(),
- Value = value.ToString(),
- IndirectionLevels = 0,
- IsDefine = true,
- IsConst = true,
- };
-
- return true;
- }
-
- // Handle cases: "const NAME **", "NAME **"
- case {}:
- {
- // Trim off the const
- var isConst = false;
- if (nativeTypeName.StartsWith("const "))
- {
- nativeTypeName = nativeTypeName["const ".Length..].Trim();
- isConst = true;
- }
-
- // Isolate the name
- var indirectionLevels = nativeTypeName.Count('*');
- if (nativeTypeName.IndexOf('*') is not -1 and var idx)
- {
- nativeTypeName = nativeTypeName[..idx];
- }
- nativeTypeName = nativeTypeName.Trim();
-
- if (nativeTypeName.ContainsAnyExcept(NameUtils.IdentifierChars))
- {
- // Unsupported format
- // Eg: const uint32_t[8]
- info = default;
- return false;
- }
-
- info = new NativeTypeNameInfo()
- {
- Name = nativeTypeName.ToString(),
- Value = null,
- IndirectionLevels = indirectionLevels,
- IsDefine = false,
- IsConst = isConst,
- };
-
- return true;
- }
- }
- }
-
- ///
- /// Gets a native type name for a parameter.
- ///
- /// The parameter.
- /// The native type name.
- public static string? GetNativeTypeName(this ParameterSyntax syntax) =>
- syntax.AttributeLists.GetNativeTypeName();
-
- ///
- /// Gets a native type name for the return type of a method.
- ///
- /// The method.
- /// The native type name.
- public static string? GetNativeReturnTypeName(this BaseMethodDeclarationSyntax syntax) =>
- syntax.AttributeLists.GetNativeTypeName(SyntaxKind.ReturnKeyword);
-
///
/// Determines whether this is a representing an integral type.
///
@@ -839,36 +516,4 @@ public static IEnumerable MemberIdentifiers(this MemberDeclarationSyntax
EnumMemberDeclarationSyntax em => [em.Identifier.ToString()],
_ => [],
};
-
- ///
- /// Gets an attribute list representing a with
- /// and
- /// set.
- ///
- public static readonly AttributeListSyntax MaxOpt = AttributeList(
- SingletonSeparatedList(
- Attribute(IdentifierName("MethodImpl"))
- .WithArgumentList(
- AttributeArgumentList(
- SingletonSeparatedList(
- AttributeArgument(
- BinaryExpression(
- SyntaxKind.BitwiseOrExpression,
- MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName("MethodImplOptions"),
- IdentifierName("AggressiveInlining")
- ),
- MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName("MethodImplOptions"),
- IdentifierName("AggressiveOptimization")
- )
- )
- )
- )
- )
- )
- )
- );
}
diff --git a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs
index ead999ee7b..7c2966b839 100644
--- a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs
@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Silk.NET.SilkTouch.Naming;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
@@ -34,13 +35,27 @@ namespace Silk.NET.SilkTouch.Mods;
///
///
///
-public partial class ExtractNestedTyping(ILogger logger) : Mod
+[ModConfiguration]
+public partial class ExtractNestedTyping(ILogger logger, IOptionsSnapshot cfg) : Mod
{
+ ///
+ /// ExtractNestedTyping configuration.
+ ///
+ public record Configuration
+ {
+ ///
+ /// The order with which the -Delegate suffix is applied.
+ ///
+ public int DelegateSuffixOrder { get; init; } = 0;
+ }
+
///
public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
{
await base.ExecuteAsync(ctx, ct);
+ var config = cfg.Get(ctx.JobKey);
+
var project = ctx.SourceProject;
if (project == null)
{
@@ -62,7 +77,7 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct =
}
// Second pass to modify existing files as per our discovery.
- var rewriter = new Rewriter(logger);
+ var rewriter = new Rewriter(logger, config.DelegateSuffixOrder);
// rewriter.FunctionPointerTypes = walker.GetFunctionPointerTypes();
var (enums, constants) = walker.GetExtractedEnums();
rewriter.ConstantsToRemove = constants;
@@ -236,7 +251,7 @@ string nativeTypeName
}
}
- partial class Rewriter(ILogger logger) : CSharpSyntaxRewriter
+ partial class Rewriter(ILogger logger, int delegateSuffixPriority) : CSharpSyntaxRewriter
{
private Dictionary _typeRenames = [];
@@ -508,13 +523,15 @@ _fallbackFromOuterFunctionPointer is not null
var (pfn, @delegate) = CreateFunctionPointerTypes(
currentNativeTypeName,
$"{currentNativeTypeName}Delegate",
- currentNativeTypeName == fallback
- ? SingletonList(
- AttributeList(
- SingletonSeparatedList(Attribute(IdentifierName("Transformed")))
- )
- )
- : default,
+ (currentNativeTypeName == fallback
+ ? SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Transformed")))))
+ : default)
+ .WithNativeName(currentNativeTypeName),
+ (currentNativeTypeName == fallback
+ ? SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Transformed")))))
+ : default)
+ .WithNativeName(currentNativeTypeName)
+ .AddNameSuffix("Delegate", delegateSuffixPriority),
node
);
FunctionPointerTypes[currentNativeTypeName] = pfnInfo = (pfn, @delegate, [], []);
@@ -754,7 +771,8 @@ DelegateDeclarationSyntax Delegate
) CreateFunctionPointerTypes(
string pfnName,
string delegateName,
- SyntaxList attrLists,
+ SyntaxList pfnAttrLists,
+ SyntaxList delegateAttrLists,
FunctionPointerTypeSyntax rawPfn
)
{
@@ -774,7 +792,7 @@ FunctionPointerTypeSyntax rawPfn
)
)
)
- .WithAttributeLists(attrLists)
+ .WithAttributeLists(pfnAttrLists)
.WithMembers(
List(
[
@@ -935,7 +953,7 @@ FunctionPointerTypeSyntax rawPfn
.WithModifiers(
TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.UnsafeKeyword))
)
- .WithAttributeLists(attrLists)
+ .WithAttributeLists(delegateAttrLists)
.WithParameterList(
ParameterList(
SeparatedList(
diff --git a/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs
new file mode 100644
index 0000000000..d3c6ccf53d
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Silk.NET.SilkTouch.Mods;
+
+///
+/// Marks identifiers with the [NativeName] attribute.
+///
+///
+/// This mod is currently kept pretty dumb and just applies [NativeName] attributes to almost everything.
+/// Syntax nodes not output by ClangSharp are intentionally not processed.
+/// This mod is best placed directly after ClangScraper.
+///
+public class MarkNativeNames : IMod
+{
+ ///
+ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
+ {
+ var proj = ctx.SourceProject;
+ if (proj == null)
+ {
+ return;
+ }
+
+ var compilation = await proj.GetCompilationAsync(ct);
+ if (compilation == null)
+ {
+ return;
+ }
+
+ var rewriter = new Rewriter();
+ foreach (var docId in proj.DocumentIds)
+ {
+ var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing");
+ proj = doc.WithSyntaxRoot(
+ rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace()
+ ?? throw new InvalidOperationException("Visit returned null.")
+ ).Project;
+ }
+
+ ctx.SourceProject = proj;
+ }
+
+ private class Rewriter : ModCSharpSyntaxRewriter
+ {
+ private SyntaxList TryAddNativeNameAttribute(SyntaxList attributeLists, SyntaxToken identifier)
+ {
+ if (attributeLists.TryGetNativeName(out _))
+ {
+ return attributeLists;
+ }
+
+ return attributeLists.WithNativeName(identifier.Text);
+ }
+
+ // ----- Types -----
+
+ ///
+ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Identifier));
+ node = (StructDeclarationSyntax)base.VisitStructDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Identifier));
+ node = (EnumDeclarationSyntax)base.VisitEnumDeclaration(node)!;
+ return node;
+ }
+
+ // ----- Members -----
+
+ ///
+ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Identifier));
+ node = (EnumMemberDeclarationSyntax)base.VisitEnumMemberDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Identifier));
+ node = (PropertyDeclarationSyntax)base.VisitPropertyDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Identifier));
+ node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
+ {
+ // This just uses the first declared field's identifier in cases where a field declares multiple variables
+ // Eg: int a, b;
+ node = node.WithAttributeLists(TryAddNativeNameAttribute(node.AttributeLists, node.Declaration.Variables.First().Identifier));
+ node = (FieldDeclarationSyntax)base.VisitFieldDeclaration(node)!;
+ return node;
+ }
+ }
+}
diff --git a/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs b/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs
index 3a914d0ee7..66692f52b5 100644
--- a/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs
@@ -1,11 +1,8 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Xml.Linq;
-using Humanizer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -16,7 +13,6 @@
using Silk.NET.SilkTouch.Mods.Transformation;
using Silk.NET.SilkTouch.Naming;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-using Type = System.Type;
namespace Silk.NET.SilkTouch.Mods;
@@ -38,19 +34,8 @@ public partial class MixKhronosData(
IApiMetadataProvider>
{
internal ConcurrentDictionary Jobs = new();
- private static readonly ICulturedStringTransformer _transformer = new NameUtils.NameTransformer(
- 4
- );
private static readonly char[] _listSeparators = { ',', '|', '+' };
- private static readonly Dictionary _defaultEnumNativeTypeNameMaps =
- new()
- {
- { "GLenum", "GLEnum" },
- { "EGLenum", "EGLEnum" },
- { "GLbitfield", "GLEnum" },
- };
-
internal class JobData
{
///
@@ -99,7 +84,7 @@ public Dictionary<
///
/// The vendors contributing to the specification. This is in extension form e.g. Microsoft is MSFT.
///
- public HashSet? Vendors { get; set; }
+ public HashSet Vendors { get; init; } = [];
///
/// A map of containing symbol names (i.e. function or struct) and applicable symbol names (i.e. field name,
@@ -153,31 +138,15 @@ public record Configuration
public string? SpecPath { get; init; }
///
- /// Whether OpenGL-style data type suffixes should be trimmed.
- ///
- public bool UseDataTypeTrimmings { get; init; }
-
- ///
- /// Whether the extension vendor suffixes should be trimmed.
- ///
- public ExtensionVendorTrimmingMode UseExtensionVendorTrimmings { get; init; }
-
- ///
- /// A map of native type names to group names.
+ /// Default namespace for enums.
///
- public Dictionary EnumNativeTypeNames { get; init; } =
- _defaultEnumNativeTypeNameMaps;
+ public string? Namespace { get; init; }
///
/// A map of native type names to C# type names. This is mostly used for determining enum backing types.
///
public Dictionary? TypeMap { get; init; }
- ///
- /// Default namespace for enums.
- ///
- public string? Namespace { get; init; }
-
///
/// The base type used for flags/bitmask enums.
/// For example, VkFlags and VkFlags64 for Vulkan.
@@ -200,68 +169,71 @@ public record Configuration
public List? Vendors { get; init; }
///
- /// Additional suffixes that may follow a data type suffix but precede a vendor suffix that should be ignored
- /// when determining a data type suffix to trim when is on. For example,
- /// Direct for OpenAL.
+ /// The order with which vendor suffixes are applied.
///
- public List? IgnoreNonVendorSuffixes { get; init; }
- }
+ public int VendorSuffixOrder { get; init; } = 0;
- ///
- /// Modes for trimming extension vendor names.
- ///
- [JsonConverter(typeof(ExtensionVendorTrimmingModeJsonConverter))]
- public enum ExtensionVendorTrimmingMode
- {
///
- /// Do not trim extension vendors from names. Note that matching vendors may still be used to determine the
- /// offset of data type suffixes.
+ /// The set of identifiers that should be excluded from vendor suffix identification.
///
- None,
+ ///
+ /// See .
+ ///
+ public HashSet ExcludeVendorSuffixIdentification { get; init; } = [];
///
- /// Trim all extension vendor names.
+ /// Additional suffixes that may follow a data type suffix but precede a vendor suffix that should be ignored
+ /// when determining a data type suffix to trim when is on. For example,
+ /// Direct for OpenAL.
///
- All,
+ public List NonVendorSuffixes { get; init; } = [];
///
- /// Only trim Khronos/first-party extension vendor names i.e. KHR and ARB.
+ /// The order with which non-vendor suffixes are applied.
///
- KhronosOnly,
- }
-
- private class ExtensionVendorTrimmingModeJsonConverter
- : JsonConverter
- {
- public override ExtensionVendorTrimmingMode Read(
- ref Utf8JsonReader reader,
- Type typeToConvert,
- JsonSerializerOptions options
- )
- {
- if (reader.TokenType == JsonTokenType.True)
- {
- return ExtensionVendorTrimmingMode.All;
- }
+ public int NonVendorSuffixOrder { get; init; } = 0;
- if (reader.GetString() is { } str)
- {
- return Enum.Parse(str);
- }
+ ///
+ /// Whether OpenGL-style data type suffixes should be trimmed.
+ ///
+ public bool TrimFunctionDataTypes { get; init; }
- return ExtensionVendorTrimmingMode.None;
- }
+ ///
+ /// Whether enum type vendor suffixes should be identified and trimmed
+ /// if the enum type contains members that don't match the exclusive vendor.
+ ///
+ ///
+ /// For context, OpenGL has a problem where an enum group starts out as ARB but never gets promoted,
+ /// and then contains other vendor enums or even core enums. OpenAL is similar. Vulkan does not have this problem.
+ ///
+ ///
+ /// BufferUsageARB in OpenGL is an ARB suffixed enum that contains GL_STREAM_DRAW which is a core enum.
+ /// In this case, ARB is the exclusive vendor suffix, but it is contradicted by the existence of a non-suffixed enum member.
+ /// This implies that BufferUsageARB was incorrectly promoted and that we should remove its vendor suffix.
+ /// Enabling this option will trim BufferUsageARB as BufferUsage.
+ ///
+ public bool TrimEnumTypeNonExclusiveVendors { get; init; } = false;
- public override void Write(
- Utf8JsonWriter writer,
- ExtensionVendorTrimmingMode value,
- JsonSerializerOptions options
- ) => writer.WriteStringValue(value.ToString());
+ ///
+ /// Whether enum members should have their vendor suffixes trimmed
+ /// if they share a vendor suffix with the containing type.
+ ///
+ ///
+ /// VkPresentModeKHR in Vulkan is a KHR suffixed enum that contains VK_PRESENT_MODE_IMMEDIATE_KHR.
+ /// The KHR suffix is useful in native code because it is not always immediately clear which enum group the enum member belongs to.
+ /// However, in C#, enum members are always part of an enum type, and as such, we can assume that if an enum member belongs
+ /// to a KHR enum type, then the non-suffixed enum member is also a KHR enum member.
+ ///
+ /// Enabling this option will trim VK_PRESENT_MODE_IMMEDIATE_KHR as VK_PRESENT_MODE_IMMEDIATE.
+ ///
+ public bool TrimEnumMemberImpliedVendors { get; init; } = false;
}
///
- // non-versioned trimmer (and needs to be a big number to come after the default trimmers)
- public Version Version { get; } = new(42, 42, 42, 42);
+ Version INameTrimmer.Version { get; } = new(0, 0, 0);
+
+ ///
+ int INameTrimmer.Order => (int)TrimmerOrder.MixKhronosData;
///
public async Task InitializeAsync(IModContext ctx, CancellationToken ct = default)
@@ -303,8 +275,7 @@ public async Task InitializeAsync(IModContext ctx, CancellationToken ct = defaul
var profiles = supportedApiProfiles.SelectMany(x => x.Value).Select(x => x.Profile).ToHashSet();
- job.Vendors =
- [
+ job.Vendors.UnionWith([
.. xml.Element("registry")
?.Element("tags")
?.Elements("tag")
@@ -327,7 +298,7 @@ .. currentConfig.NonStandardExtensionNomenclature
// Eg: GL_NV_command_list -> NV
.Select(name => name.Value.Split('_')[1].ToUpper()) ?? [],
.. currentConfig.Vendors ?? [],
- ];
+ ]);
job.DeprecatedAliases = xml.Descendants()
.Where(x => x.Attribute("deprecated")?.Value == "aliased" && x.Attribute("name") != null)
@@ -375,6 +346,7 @@ .. currentConfig.NonStandardExtensionNomenclature
///
public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
{
+ var currentConfig = cfg.Get(ctx.JobKey);
var jobData = Jobs[ctx.JobKey];
var proj = ctx.SourceProject;
@@ -417,7 +389,18 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
).Project;
}
- // Rename documents
+ // Rewrite phase 3
+ var rewriter3 = new RewriterPhase3(jobData, currentConfig);
+ foreach (var docId in proj.DocumentIds)
+ {
+ var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing");
+ proj = doc.WithSyntaxRoot(
+ rewriter3.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace()
+ ?? throw new InvalidOperationException("Visit returned null.")
+ ).Project;
+ }
+
+ // Rename documents to account for FlagBits/Flags differences
foreach (var docId in proj.DocumentIds)
{
var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing");
@@ -447,8 +430,16 @@ public Task> BeforeScrapeAsync(string key, List
return Task.FromResult(rsps);
}
+ /// The name of the group. This is the name used for the C# enum.
+ /// The native name of the group, if available. This is the name used for the [NativeName] attribute.
+ ///
+ ///
+ ///
+ ///
+ ///
internal record EnumGroup(
string Name,
+ string? NativeName,
string? Type,
List Enums,
bool KnownBitmask,
@@ -1208,12 +1199,10 @@ var ext in xml.Element("registry")?.Element("extensions")?.Elements("extension")
}
}
- private bool _outputVendorInformationWarning = false;
-
///
public void Trim(NameTrimmerContext context)
{
- if (context.Names is null || context.JobKey is null)
+ if (context.JobKey is null)
{
return;
}
@@ -1225,297 +1214,21 @@ public void Trim(NameTrimmerContext context)
);
}
- if (job.Vendors?.Count is 0 or null && !_outputVendorInformationWarning)
- {
- _outputVendorInformationWarning = true;
- logger.LogWarning(
- "No vendor information present, assuming no XML was provided? Extension trimming will be skipped."
- );
- }
-
- // NameTrimmer trims member names by looking for a common prefix and removing it
- // This sometimes trims too much and leads to only the vendor suffix remaining
- // This is bad because the next block of code removes the vendor suffix, leaving only an empty string or underscore
- // This means we have to rewind back to the previous name (minus the prefix, such as GL_)
- var rewind = false;
- if (context.Container is not null && job.Groups.ContainsKey(context.Container))
- {
- foreach (var (_, (current, previous)) in context.Names)
- {
- var prev = previous?.FirstOrDefault();
- if (prev is not null && (job.Vendors?.Contains(current.Trim('_')) ?? false)
- )
- {
- rewind = true;
-
- break;
- }
- }
- }
-
- if (rewind)
- {
- foreach (var (original, (current, previous)) in context.Names)
- {
- var prev = previous?.FirstOrDefault() ?? original;
- var prevList = previous ?? [];
- var next = prev[(prev.IndexOf('_') + 1)..];
- if (next == prev)
- {
- prevList.Remove(prev);
- }
- else if (!prevList.Contains(prev))
- {
- prevList.Add(prev);
- }
-
- context.Names[original] = (prev[(prev.IndexOf('_') + 1)..], prevList);
- }
- }
-
- // Trim _T from the end of names
- // This is targeted towards Vulkan handle type names, which end in _T
+ // Prevent enums like GLEnum from having their prefix trimmed
if (context.Container is null)
{
foreach (var (original, (current, previous)) in context.Names)
{
- if (current.EndsWith("_T"))
+ foreach (var name in (IEnumerable)[current, .. previous])
{
- var newPrim = current[..^2];
- var newPrev = previous ?? [];
- newPrev.Add(current);
-
- context.Names[original] = (newPrim, newPrev);
- }
- }
- }
-
- // OpenGL has a problem where an enum starts out as ARB but never gets promoted, and then contains other vendor
- // enums or even core enums. This removes the vendor suffix where it is not necessary e.g. BufferUsageARB
- // becomes BufferUsage.
- if (context.Container is null && job.Vendors is not null)
- {
- foreach (var (original, (current, previous)) in context.Names)
- {
- if (job.Groups.TryGetValue(current, out var groupInfo))
- {
- var vendorSuffix =
- groupInfo.ExclusiveVendor ?? job.Vendors.FirstOrDefault(current.EndsWith);
- vendorSuffix = vendorSuffix?[(vendorSuffix.LastIndexOf('_') + 1)..];
- var notSafeToTrim =
- job.Groups.Count(x =>
- x.Key.StartsWith(current[..^(vendorSuffix?.Length ?? 0)])
- ) > 1;
- if (
- vendorSuffix is null
- || !job.Vendors.Contains(vendorSuffix)
- || !current.EndsWith(vendorSuffix)
- || !groupInfo.Enums.All(x => x.Identifier.ToString().EndsWith(vendorSuffix))
- )
+ if (job.Groups.TryGetValue(name, out var group)
+ && name == $"{group.Namespace}Enum")
{
- vendorSuffix = null;
- }
-
- job.Groups[current] = groupInfo = groupInfo with
- {
- ExclusiveVendor = vendorSuffix,
- };
-
- if (notSafeToTrim)
- {
- continue;
- }
-
- // If the vendor suffix is not equal to our exclusive vendor, then it must not be exclusive
- // therefore we should remove the suffix.
- foreach (var vendor in job.Vendors)
- {
- if (current.EndsWith(vendor) && groupInfo.ExclusiveVendor != vendor)
- {
- var sec = previous ?? [];
- sec.Add(current);
- context.Names[original] = (current[..^vendor.Length], sec);
- break;
- }
- }
- }
- }
- }
-
- // Trim the extension vendor names
- foreach (var (original, (current, previous)) in context.Names)
- {
- // GLEnum is obviously trimmed, and we don't really want to do that.
- if (context.Container is null)
- {
- var changed = false;
- foreach (var name in (IEnumerable)[current, .. previous ?? []])
- {
- if (
- job.Groups.TryGetValue(name, out var group)
- && name == $"{group.Namespace}Enum"
- )
- {
- context.Names[original] = (name, []);
- changed = true;
+ context.Names[original] = new CandidateNames(name, []);
break;
}
}
-
- if (changed)
- {
- continue;
- }
- }
-
- var newCurrent = current;
- List? newPrev = null;
- string? identifiedVendor = null;
- var trimVendor = false;
- foreach (var vendor in job.Vendors ?? Enumerable.Empty())
- {
- if (!current.EndsWith(vendor))
- {
- continue;
- }
-
- newCurrent = current[..^vendor.Length];
- var newOriginal = original[..^vendor.Length];
- // Sometimes we should keep the vendor prefix so we prefer the promoted functions.
- // ----------vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv--------------------------------------
- trimVendor =
- !context.Names.ContainsKey(newOriginal)
- && (
- job.Configuration.UseExtensionVendorTrimmings
- == ExtensionVendorTrimmingMode.All
- || (
- job.Configuration.UseExtensionVendorTrimmings
- == ExtensionVendorTrimmingMode.KhronosOnly
- && vendor is "KHR" or "ARB"
- )
- || (
- context.Container is not null
- && job.Groups.TryGetValue(context.Container, out var group)
- && group.ExclusiveVendor == vendor
- )
- );
- if (trimVendor)
- {
- newPrev ??= previous ?? [];
- newPrev.Add(current);
- context.Names[original] = (newCurrent, newPrev);
- }
-
- identifiedVendor = vendor;
- break;
- }
-
- // Below is a hack to ensure extension vendors are capitalised for enums (which are all caps and therefore
- // will not be treated as an acronym)
- if (
- current.All(x => !char.IsLetter(x) || char.IsUpper(x))
- && identifiedVendor is not null
- )
- {
- newPrev ??= previous ?? [];
- var pretty = newCurrent.Prettify(_transformer);
-
- // Hack to ensure extension vendors are preserved as acronyms
- if (char.IsUpper(pretty[^1]))
- {
- pretty += ' ';
- }
-
- if (!trimVendor)
- {
- // If we're not trimming the vendor, this hack will be the primary name.
- newPrev.Add(current);
- context.Names[original] = (pretty + identifiedVendor, newPrev);
- }
- else
- {
- // If we are trimming the vendor, if at any point we have to fall back on the untrimmed version
- // we'll want that version to be this hack.
- newPrev.Add(pretty + identifiedVendor);
- context.Names[original] = (pretty, newPrev);
- }
- }
-
- // Another hack to make sure that extension vendors are preserved as acronyms e.g. glTexImage4DSGIS was
- // becoming glTexImage4Dsgis instead of glTexImage4DSGIS
- if (
- current.Any(char.IsLower)
- && char.IsUpper(newCurrent[^1])
- && identifiedVendor is not null
- )
- {
- newPrev ??= previous ?? [];
- if (!trimVendor)
- {
- // If we're not trimming the vendor, this hack will be the primary name.
- newPrev.Add(current);
- context.Names[original] = ($"{newCurrent} {identifiedVendor}", newPrev);
- }
- else
- {
- // If we are trimming the vendor, if at any point we have to fall back on the untrimmed version
- // we'll want that version to be this hack. Note that to do this we actually have to nuke the
- // original name because PrettifyNames orders by match length.
- newPrev.Remove(current);
- newPrev.Add($"{newCurrent} {identifiedVendor}");
- context.Names[original] = (newCurrent, newPrev);
- }
}
-
- // If we have an additional non-vendor suffix (e.g. al*Direct) then let's trim it before we try to do the
- // data type trimming
- string? identifiedSuffix = null;
- foreach (var suffix in job.Configuration.IgnoreNonVendorSuffixes ?? [])
- {
- if (newCurrent.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
- {
- identifiedSuffix = suffix;
- newCurrent = newCurrent[..^suffix.Length];
- break;
- }
- }
-
- if (
- !job.Configuration.UseDataTypeTrimmings // don't trim data types
- || context.Container is null // don't trim type names
- || newCurrent.Count(x => x == '_') > 1 // is probably an enum
- || EndingsToTrim().Match(newCurrent) is not { Success: true } match // we don't have a data type suffix
- || EndingsNotToTrim().IsMatch(newCurrent) // we need to keep it
- )
- {
- continue;
- }
-
- newPrev ??= previous ?? [];
- var newPrim = newCurrent.Remove(match.Index);
- newPrim += identifiedSuffix;
- if (identifiedVendor is not null && trimVendor)
- {
- // If the only difference between this function and other functions that could conflict is the vendor,
- // it would be extremely confusing if the difference between e.g. a NV function and a non-NV function
- // was one had data type suffixes and the other didn't. Therefore, let's add the new name but with the
- // vendor added as the first secondary (e.g. for glVertex2bOES we first try Vertex2OES). If that doesn't
- // work, we still have the original one (modulo GL prefix) that we added to the secondary list when
- // originally trimming the vendor.
- newPrev.Add(newPrim + identifiedVendor);
- }
- else
- {
- // If trimVendor is false, add the vendor back. We're not trimming vendors so the only other secondary
- // we have is the original current name i.e. primary = glVertex2OES, secondary = glVertex2bOES, which
- // WOULDN'T be in the secondary list already per the if trimVendor above. If we're hitting this else
- // because we haven't identified a vendor, then we're just appending null to this string which does
- // nothing and is effectively equivalent to us having primary = glVertex2, secondary = glVertex2b
- newPrim += identifiedVendor;
- newPrev.Add(current);
- }
-
- context.Names[original] = (newPrim, newPrev);
}
}
@@ -1581,7 +1294,7 @@ ref anyNonTrivialParameters
// Are the parameters transformable?
for (var i = 0; i < @params.Count; i++)
{
- var param = current.ParameterList!.Parameters[i];
+ var param = current.ParameterList.Parameters[i];
if (
param.Type is null
|| GetTypeTransformation(
@@ -1807,12 +1520,14 @@ public bool TryGetSymbolMetadata(
/// This regex acts like a whitelist for endings that could have been matched in some way by the main
/// expression, but should be exempt from trimming altogether.
///
+ // Rewindv is to prevent Rewindv from being trimmed as Rewin
+ // TODO: Consider replacing this with something that prevents a list of words from being trimmed into instead of not being trimmed at all
[GeneratedRegex(
"(sh|ib|[tdrey]s|(?(
- FileScopedNamespaceDeclaration(
- ModUtils.NamespaceIntoIdentifierName(ns)
- )
+ FileScopedNamespaceDeclaration(ModUtils.NamespaceIntoIdentifierName(ns))
.WithMembers(
SingletonList(
EnumDeclaration(groupName)
- .WithModifiers(
- TokenList(Token(SyntaxKind.PublicKeyword))
- )
- .WithAttributeLists(
- SingletonList(
- AttributeList(
- SingletonSeparatedList(
- Attribute(
- IdentifierName("Transformed")
- )
- )
- )
- )
- )
- .WithBaseList(
- BaseList(
- SingletonSeparatedList(
- SimpleBaseType(IdentifierName(baseType))
- )
- )
- )
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
+ .WithAttributeLists(attributes)
+ .WithBaseList(BaseList([SimpleBaseType(baseTypeSyntax)]))
.WithMembers(
SeparatedList(
groupInfo.Enums.Select(x =>
- EnumMemberDeclaration(
- x.Identifier.ToString()
- )
- // TODO actually eval the expression to see if necessary?
+ EnumMemberDeclaration(x.Identifier.ToString())
+ .WithAttributeLists(
+ new SyntaxList()
+ .WithNativeName(x.Identifier.Text))
.WithEqualsValue(
x.Initializer?.WithValue(
CheckedExpression(
SyntaxKind.UncheckedExpression,
CastExpression(
- IdentifierName(
- baseType
- ),
+ baseTypeSyntax,
x.Initializer.Value
)
)
@@ -1968,7 +1672,7 @@ private class RewriterPhase1(JobData job, ILogger logger) : CSharpSyntaxRewriter
AllKnownEnums.Add(identifier);
- if (job.Groups.TryGetValue(identifier, out var group)
+ if (job.Groups.TryGetValue(identifier, out _)
&& !node.Ancestors().OfType().Any())
{
AlreadyPresentGroups.Add(identifier);
@@ -2035,7 +1739,7 @@ private class RewriterPhase1(JobData job, ILogger logger) : CSharpSyntaxRewriter
///
private class RewriterPhase2(JobData job, RewriterPhase1 phase1) : CSharpSyntaxRewriter(true)
{
- public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) => IdentifierName(node.Identifier.ToString().Replace("FlagBits", "Flags"));
+ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) => IdentifierName(node.Identifier.ToString().Replace("FlagBits", "Flags"));
public override SyntaxNode? VisitEnumDeclaration(EnumDeclarationSyntax node)
{
@@ -2045,7 +1749,7 @@ private class RewriterPhase2(JobData job, RewriterPhase1 phase1) : CSharpSyntaxR
{
// Remove deprecated aliases
node = node.WithMembers([
- ..node.Members.Where(m => !job.DeprecatedAliases.Contains(m.Identifier.ValueText))
+ ..node.Members.Where(m => !job.DeprecatedAliases.Contains(m.Identifier.ValueText)),
]);
}
@@ -2068,7 +1772,7 @@ private class RewriterPhase2(JobData job, RewriterPhase1 phase1) : CSharpSyntaxR
{
// Remove deprecated aliases
node = node.WithDeclaration(node.Declaration.WithVariables([
- ..node.Declaration.Variables.Where(v => !job.DeprecatedAliases.Contains(v.Identifier.ValueText))
+ ..node.Declaration.Variables.Where(v => !job.DeprecatedAliases.Contains(v.Identifier.ValueText)),
]));
if (node.Declaration.Variables.Count == 0)
@@ -2140,6 +1844,207 @@ private bool TryGetManagedEnumType(SyntaxList attributes, [
}
}
+ ///
+ /// This rewriter identifies and extracts vendor extension suffixes into [NameSuffix] attributes.
+ ///
+ ///
+ /// Yes, this is a 3rd rewriter.
+ ///
+ private class RewriterPhase3(JobData job, Configuration config) : CSharpSyntaxRewriter
+ {
+ private SyntaxList ProcessAndGetNewAttributes(SyntaxList attributeLists, SyntaxToken identifier, bool trimHandleSuffix, MethodDeclarationSyntax? methodDeclaration = null)
+ {
+ // Get the name of the identifier, preferring the native one if available
+ // This name will be modified by the code below as different suffixes are identified
+ var trimmedName = attributeLists.GetNativeNameOrDefault(identifier);
+
+ if (trimHandleSuffix)
+ {
+ const string handleSuffix = "_T";
+ if (trimmedName.EndsWith(handleSuffix))
+ {
+ trimmedName = trimmedName[..^handleSuffix.Length];
+ attributeLists = attributeLists
+ .AddNameSuffix(handleSuffix, -1)
+ .WithNativeName(trimmedName);
+ }
+ }
+
+ if (!config.ExcludeVendorSuffixIdentification.Contains(trimmedName))
+ {
+ // Try to identify vendor suffixes
+ foreach (var vendor in job.Vendors)
+ {
+ if (trimmedName.EndsWith(vendor))
+ {
+ attributeLists = attributeLists.AddNameSuffix(vendor, config.VendorSuffixOrder);
+ trimmedName = trimmedName[..^vendor.Length];
+
+ break;
+ }
+ }
+ }
+
+ if (methodDeclaration != null && methodDeclaration.Parent.IsKind(SyntaxKind.ClassDeclaration))
+ {
+ // Try to identify non-vendor suffixes
+ foreach (var suffix in config.NonVendorSuffixes)
+ {
+ if (trimmedName.EndsWith(suffix))
+ {
+ attributeLists = attributeLists.AddNameSuffix(suffix, config.NonVendorSuffixOrder);
+ trimmedName = trimmedName[..^suffix.Length];
+
+ break;
+ }
+ }
+
+ // Try to identify data type suffixes
+ if (config.TrimFunctionDataTypes)
+ {
+ if (EndingsToTrim().Match(trimmedName) is { Success: true } match // Check if we end in a data type suffix
+ && !EndingsNotToTrim().IsMatch(trimmedName)) // Check if the ending is excluded
+ {
+ var dataTypeSuffix = trimmedName[match.Index..];
+ trimmedName = trimmedName[..match.Index];
+
+ attributeLists = attributeLists.AddNameSuffix(dataTypeSuffix, 0, 0);
+ }
+ }
+ }
+
+ return attributeLists;
+ }
+
+ // ----- Types -----
+
+ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
+ {
+ node = (StructDeclarationSyntax)base.VisitStructDeclaration(node)!;
+ return node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, node.Identifier, true));
+ }
+
+ public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
+ {
+ var variable = node.Declaration.Variables.First();
+ return node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, variable.Identifier, false));
+ }
+
+ public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node) =>
+ node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, node.Identifier, false));
+
+ // Special case for enums since this code needs information about
+ // the enum type name and its member names at the same time
+ // in order to properly trim the type name and member name
+ public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
+ {
+ var typeName = node.AttributeLists.GetNativeNameOrDefault(node.Identifier);
+ var groupInfo = job.Groups.GetValueOrDefault(typeName);
+
+ var typeVendor = job.Vendors.FirstOrDefault(typeName.EndsWith);
+ var hasTypeSuffix = typeVendor != null;
+ var vendorFromTypeNameOrder = config.VendorSuffixOrder;
+
+ // Figure out the enum's exclusive vendor
+ var exclusiveVendor = groupInfo?.ExclusiveVendor ?? typeVendor;
+ if (exclusiveVendor == null || !node.Members.All(member => member.Identifier.Text.EndsWith(exclusiveVendor)))
+ {
+ // Not all enum members share the exclusive vendor
+ // This means the vendor suffix isn't actually exclusive
+ exclusiveVendor = null;
+ }
+
+ // See config option for more info and examples on what this does
+ if (config.TrimEnumTypeNonExclusiveVendors && typeVendor != null)
+ {
+ // Trim if the type vendor suffix does not match the identified exclusive vendor suffix
+ var shouldTrimType = typeVendor != exclusiveVendor;
+
+ // Check if there are other versions of the enum (this includes the core variant and other vendor variants)
+ //
+ // For example, SamplePatternEXT and SamplePatternSGIS are both enum types.
+ // Trimming both would cause conflicts.
+ // Trimming one but not the other would imply one is core and the other is not.
+ var hasMultipleVersions = job.Groups.Count(x => x.Key.StartsWith(typeName[..^typeVendor.Length])) > 1;
+ var isSafeToTrimType = !hasMultipleVersions;
+
+ if (shouldTrimType && isSafeToTrimType)
+ {
+ // Remove the exclusive vendor since it isn't actually exclusive
+ vendorFromTypeNameOrder = -1;
+
+ // Type suffix has been removed
+ hasTypeSuffix = false;
+ }
+ }
+
+ if (typeVendor != null)
+ {
+ // Mark the type vendor suffix as identified
+ node = node.WithAttributeLists(node.AttributeLists.AddNameSuffix(typeVendor, vendorFromTypeNameOrder));
+ }
+
+ // Check if the enum contains unsuffixed members
+ var containsUnsuffixedMembers = node.Members.Any(member =>
+ {
+ var memberName = member.AttributeLists.GetNativeNameOrDefault(member.Identifier);
+ if (job.Vendors.FirstOrDefault(memberName.EndsWith) == null)
+ {
+ return true;
+ }
+
+ return false;
+ });
+
+ // We should not trim member suffixes if the enum type already contains unsuffixed members
+ // We also should not trim member suffixes if the type suffix was removed
+ //
+ // For example (direct conflict with existing):
+ // ConvolutionBorderMode in OpenGL contains GL_REDUCE and GL_REDUCE_EXT.
+ // Trimming EXT from GL_REDUCE_EXT will conflict with GL_REDUCE.
+ //
+ // Another example (possible confusion between core and non-core):
+ // ContextRequest in OpenAL contains ALC_FALSE and ALC_DONT_CARE_SOFT. One is core and one is non-core.
+ // If we trim SOFT from ALC_DONT_CARE_SOFT, it is not immediately obvious that the enum members have different promotion statuses.
+ var canTrimImpliedVendors = hasTypeSuffix && !containsUnsuffixedMembers;
+
+ // Trim the enum members if needed
+ // See config option for more info and examples on what this does
+ if (config.TrimEnumMemberImpliedVendors && typeVendor != null && canTrimImpliedVendors)
+ {
+ node = node.WithMembers([
+ ..node.Members.Select(member => {
+ if (member.AttributeLists.GetNativeNameOrDefault(member.Identifier).EndsWith(typeVendor))
+ {
+ return member.WithAttributeLists(member.AttributeLists.AddNameSuffix(typeVendor, -1));
+ }
+
+ // Default behavior - Identify, but keep the member suffixes
+ return member.WithAttributeLists(ProcessAndGetNewAttributes(member.AttributeLists, member.Identifier, false));
+ }),
+ ]);
+ }
+ else
+ {
+ // Default behavior - Identify, but keep the member suffixes
+ node = (EnumDeclarationSyntax)base.VisitEnumDeclaration(node)!;
+ }
+
+ return node;
+ }
+
+ // ----- Members -----
+
+ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) =>
+ node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, node.Identifier, false));
+
+ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) =>
+ node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, node.Identifier, false));
+
+ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) =>
+ node.WithAttributeLists(ProcessAndGetNewAttributes(node.AttributeLists, node.Identifier, false, node));
+ }
+
[SuppressMessage("ReSharper", "MoveLocalFunctionAfterJumpStatement")]
internal void ReadGroups(XDocument doc, JobData data, HashSet vendors)
{
@@ -2164,11 +2069,13 @@ internal void ReadGroups(XDocument doc, JobData data, HashSet vendors)
enumNamespace is not null && !enumNamespace.All(char.IsUpper)
? enumNamespace
: null;
+ var nativeName = groupName;
// Create an ungrouped group as well i.e. GLEnum, WGLEnum, etc
if (enumNamespace is not null)
{
groupName ??= $"{enumNamespace}Enum";
+ nativeName ??= enumNamespace;
}
// OpenCL enum name
@@ -2178,6 +2085,7 @@ internal void ReadGroups(XDocument doc, JobData data, HashSet vendors)
}
// Vulkan/OpenXR enum name
+ nativeName ??= groupName;
groupName = groupName?.Replace("FlagBits", "Flags");
// Skip Vulkan API Constants since it is not an enum
@@ -2264,6 +2172,7 @@ var group in (groupName is null ? Enumerable.Empty() : [groupName])
: null,
}
: new EnumGroup(
+ group,
group,
anyGLStyleGroups ? "GLenum" : null,
[],
@@ -2282,6 +2191,7 @@ var group in (groupName is null ? Enumerable.Empty() : [groupName])
{
data.Groups[groupName] = new EnumGroup(
groupName,
+ nativeName,
null,
[],
isBitmask,
@@ -2431,6 +2341,7 @@ var @enum in doc.Elements("registry")
if (!data.Groups.ContainsKey(@enum.Value))
{
data.Groups[@enum.Value] = new EnumGroup(
+ @enum.Value,
@enum.Value,
// cl_properties and cl_bitfield are both cl_ulong which is ulong
"ulong",
@@ -2596,6 +2507,7 @@ is var splitList
else
{
data.Groups[groupStr] = new EnumGroup(
+ groupStr,
groupStr,
null,
[],
diff --git a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs
index 080960bbab..08efbb7a80 100644
--- a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs
@@ -1,10 +1,8 @@
-using System.Collections.Concurrent;
-using System.Diagnostics;
+using System.Diagnostics;
+using Humanizer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.FindSymbols;
-using Microsoft.CodeAnalysis.Rename;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Silk.NET.SilkTouch.Clang;
@@ -33,12 +31,12 @@ IEnumerable> trimmerProviders
///
/// Corrections to the automatic prefix determination.
///
- public Dictionary? PrefixOverrides { get; init; }
+ public Dictionary PrefixOverrides { get; init; } = [];
///
/// Manually renamed native names.
///
- public Dictionary? NameOverrides { get; init; }
+ public Dictionary NameOverrides { get; init; } = [];
///
/// The base trimmer version. If null, trimming is disabled.
@@ -79,33 +77,45 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
visitor.Visit(await doc.GetSyntaxRootAsync(ct));
}
- Dictionary<
- string,
- (
- string NewName,
- Dictionary? NonFunctions,
- Dictionary? Functions,
- bool IsEnum
- )
- > types = new();
- var translator = new NameUtils.NameTransformer(cfg.LongAcronymThreshold ?? 3);
+ // The dictionary containing mappings from the original type names to the new names of the type and its members
+ var newNames = new Dictionary();
+
+ var nameTransformer = new NameUtils.NameTransformer(cfg.LongAcronymThreshold ?? 3); // TODO: Change to 2 in next PR to match framework design guidelines
- // If we have a trimmer baseline set, that means the user wants to trim the names as well as prettify them.
- if (cfg.TrimmerBaseline is not null)
+ // Trim the trimmable names if the trimmer baseline is set
+ // Otherwise, we just prettify the trimmable names
+ if (cfg.TrimmerBaseline is null)
+ {
+ // Only prettify the trimmable names
+ foreach (var (name, (nonFunctions, functions)) in visitor.TrimmableTypes)
+ {
+ newNames[name] = new RenamedType(
+ ApplyPrettifyOnlyPipeline(null, name, cfg.NameOverrides, visitor.AffixTypes, nameTransformer),
+ nonFunctions.ToDictionary(x => x, x => ApplyPrettifyOnlyPipeline(name, x, cfg.NameOverrides, visitor.AffixTypes, nameTransformer)),
+ functions.ToDictionary(x => x.Name, x => ApplyPrettifyOnlyPipeline(name, x.Name, cfg.NameOverrides, visitor.AffixTypes, nameTransformer))
+ );
+ }
+ }
+ else
{
+ // Trim and prettify the trimmable names
+
// Get all the trimmers that are above this baseline. We also sort by the version. Why? Because someone
// couldn't be bothered to introduce a weight property. It is also unclear what effect this has on 2.17/2.18
// but to be honest those trimmers aren't used and are only included for posterity and understanding of the
// old logic.
var trimmers = trimmerProviders
.SelectMany(x => x.Get(ctx.JobKey))
- .OrderBy(x => x.Version)
+ .Append(new NameAffixerEarlyTrimmer(visitor.AffixTypes))
+ .Append(new NameAffixerLateTrimmer(visitor.AffixTypes))
+ .Append(new PrettifyNamesTrimmer(nameTransformer))
+ .OrderBy(x => x.Order)
.ToArray();
// Create a type name dictionary to trim the type names.
- var typeNames = visitor.Types.ToDictionary(
+ var typeNames = visitor.TrimmableTypes.ToDictionary(
x => x.Key,
- x => (x.Key, (List?)null)
+ x => new CandidateNames(x.Key, [])
);
// If we don't have a prefix hint and don't have more than one type, we can't determine a prefix so don't
@@ -125,126 +135,101 @@ bool IsEnum
);
}
- // Now rename everything within that type.
+ // Now rename everything within each type.
foreach (var (typeName, (newTypeName, _)) in typeNames)
{
- var (_, (consts, functions, isEnum)) = visitor.Types.First(x => x.Key == typeName);
+ var (_, (consts, functions)) = visitor.TrimmableTypes.First(x => x.Key == typeName);
// Rename the "constants" i.e. all the consts/static readonlys in this type. These are treated
// individually because everything that isn't a constant or a function is only prettified instead of prettified & trimmed.
- var constNames = consts?.ToDictionary(
+ var constNames = consts.ToDictionary(
x => x,
- x => (Primary: x, (List?)null)
+ x => new CandidateNames(x, [])
);
- // Trim the constants if we have any.
- if (constNames is not null)
- {
- Trim(
- new NameTrimmerContext
- {
- Container = typeName,
- Names = constNames,
- Configuration = cfg,
- JobKey = ctx.JobKey,
- NonDeterminant = visitor.NonDeterminant,
- },
- trimmers
- );
- }
- else
- {
- constNames = new Dictionary?)>();
- }
+ // Trim the constants.
+ Trim(
+ new NameTrimmerContext
+ {
+ Container = typeName,
+ Names = constNames,
+ Configuration = cfg,
+ JobKey = ctx.JobKey,
+ NonDeterminant = visitor.NonDeterminant,
+ },
+ trimmers
+ );
// Rename the functions. More often that not functions have different nomenclature to constants, so we
// treat them separately.
var functionNames = functions
- ?.DistinctBy(x => x.Name)
- .ToDictionary(x => x.Name, x => (Primary: x.Name, (List?)null));
+ .DistinctBy(x => x.Name)
+ .ToDictionary(x => x.Name, x => new CandidateNames(x.Name, []));
// Collect the syntax as this is used for conflict resolution in the Trim function.
- var functionSyntax =
- functions is null || functionNames is null
- ? null
- : functionNames.Keys.ToDictionary(
- x => x,
- x => functions.Where(y => y.Name == x).Select(y => y.Syntax)
- );
-
- // Now trim if we have any functions.
- if (functionNames is not null)
- {
- Trim(
- new NameTrimmerContext
- {
- Container = typeName,
- Names = functionNames,
- Configuration = cfg,
- JobKey = ctx.JobKey,
- NonDeterminant = visitor.NonDeterminant,
- },
- trimmers,
- functionSyntax
- );
- }
+ var functionSyntax = functionNames.Keys.ToDictionary(
+ x => x,
+ x => functions.Where(y => y.Name == x).Select(y => y.Syntax)
+ );
- // Add back anything else that isn't a trimming candidate (but should still have a pretty name)
- var prettifiedOnly = visitor.PrettifyOnlyTypes.TryGetValue(typeName, out var val)
- ? val.Select(x => new KeyValuePair?)>(
- x,
- (x, null)
- ))
- : [];
+ // Trim the functions.
+ Trim(
+ new NameTrimmerContext
+ {
+ Container = typeName,
+ Names = functionNames,
+ Configuration = cfg,
+ JobKey = ctx.JobKey,
+ NonDeterminant = visitor.NonDeterminant,
+ },
+ trimmers,
+ functionSyntax
+ );
// Add it to the rewriter's list of names to... rewrite...
- types[typeName] = (
- newTypeName.Prettify(translator, allowAllCaps: true), // <-- lenient about caps for type names
- // TODO deprecate secondaries if they're within the baseline?
- constNames
- .Concat(prettifiedOnly)
- .ToDictionary(x => x.Key, x => x.Value.Primary.Prettify(translator)),
- functionNames?.ToDictionary(
- x => x.Key,
- x => x.Value.Primary.Prettify(translator)
- ),
- isEnum
+ newNames[typeName] = new RenamedType(
+ newTypeName,
+ constNames.ToDictionary(x => x.Key, x => x.Value.Primary),
+ functionNames.ToDictionary(x => x.Key, x => x.Value.Primary)
);
}
}
- else // (there's no trimming baseline)
+
+ // Prettify the prettify only names
+ foreach (var (typeName, memberNames) in visitor.PrettifyOnlyTypes)
{
- // Prettify only if the user has not indicated they want to trim.
- foreach (var (name, (nonFunctions, functions, isEnum)) in visitor.Types)
+ if (!newNames.TryGetValue(typeName, out var renamedType))
{
- types[name] = (
- name.Prettify(translator, allowAllCaps: true), // <-- lenient about caps for type names (e.g. GL)
- nonFunctions?.ToDictionary(x => x, x => x.Prettify(translator)),
- functions?.ToDictionary(x => x.Name, x => x.Name.Prettify(translator)),
- isEnum
- );
+ renamedType = new RenamedType(ApplyPrettifyOnlyPipeline(null, typeName, cfg.NameOverrides, visitor.AffixTypes, nameTransformer), [], []);
}
+
+ foreach (var memberName in memberNames)
+ {
+ renamedType.NonFunctions[memberName] = ApplyPrettifyOnlyPipeline(typeName, memberName, cfg.NameOverrides, visitor.AffixTypes, nameTransformer);
+ }
+
+ newNames[typeName] = renamedType;
}
if (logger.IsEnabled(LogLevel.Debug))
{
- foreach (var (name, (newName, nonFunctions, functions, _)) in types)
+ foreach (var (name, (newName, nonFunctions, functions)) in newNames)
{
logger.LogDebug("{} = {}", name, newName);
- foreach (var (old, @new) in nonFunctions ?? new())
+ foreach (var (old, @new) in nonFunctions)
{
logger.LogDebug("{}.{} = {}.{}", name, old, newName, @new);
}
- foreach (var (old, @new) in functions ?? new())
+ foreach (var (old, @new) in functions)
{
logger.LogDebug("{}.{} = {}.{}", name, old, newName, @new);
}
}
}
- // Before we rename, we should ensure name dependent things are correct e.g. DllImport explicitly specify their
- // EntryPoint
+ // Before we rename, we should ensure name dependent things are correct
+ // e.g. DllImport explicitly specify their EntryPoint
logger.LogDebug("Fixing up attributes for {} to make them safe for rename...", ctx.JobKey);
var rewriter = new RenameSafeAttributeListsRewriter();
var proj = ctx.SourceProject;
@@ -263,21 +248,22 @@ functions is null || functionNames is null
}
}
+ // Find symbols and rename their references
var sw = Stopwatch.StartNew();
logger.LogDebug("Discovering references to symbols to rename for {}...", ctx.JobKey);
ctx.SourceProject = proj;
+
var comp =
await proj.GetCompilationAsync(ct)
- ?? throw new InvalidOperationException(
- "Failed to obtain compilation for source project!"
- );
+ ?? throw new InvalidOperationException("Failed to obtain compilation for source project!");
+
await NameUtils.RenameAllAsync(
ctx,
- types.SelectMany(x =>
+ newNames.SelectMany(x =>
{
var nonFunctionConflicts = x
- .Value.NonFunctions?.Values.Where(y =>
- x.Value.Functions?.ContainsValue(y) ?? false
+ .Value.NonFunctions.Values.Where(y =>
+ x.Value.Functions.ContainsValue(y)
)
.ToHashSet();
return comp.GetSymbolsWithName(x.Key, SymbolFilter.Type, ct)
@@ -286,15 +272,15 @@ await NameUtils.RenameAllAsync(
[
.. Enumerable.SelectMany(
[
- .. x.Value.NonFunctions?.Select(z =>
- nonFunctionConflicts?.Contains(z.Value) ?? false
+ .. x.Value.NonFunctions.Select(z =>
+ nonFunctionConflicts.Contains(z.Value)
? new KeyValuePair(
z.Key,
$"{z.Value}Value"
)
: z
- ) ?? [],
- .. x.Value.Functions ?? [],
+ ),
+ .. x.Value.Functions,
],
z =>
{
@@ -314,6 +300,7 @@ z.MethodKind is MethodKind.Constructor or MethodKind.Destructor
logger,
ct
);
+
logger.LogDebug(
"Reference renaming took {} seconds for {}.",
sw.Elapsed.TotalSeconds,
@@ -326,8 +313,8 @@ z.MethodKind is MethodKind.Constructor or MethodKind.Destructor
{
var doc = proj.GetDocument(docId);
if (
- doc is not { FilePath: not null, Name: not null }
- || types
+ doc is not { FilePath: not null }
+ || newNames
.OrderByDescending(x => x.Key.Length)
.FirstOrDefault(x => doc.FilePath.Contains(x.Key) || doc.Name.Contains(x.Key))
is not { Key: { } oldName, Value.NewName: { } newName }
@@ -336,26 +323,100 @@ z.MethodKind is MethodKind.Constructor or MethodKind.Destructor
continue;
}
- proj = doc.ReplaceNameAndPath(oldName, newName).Project;
+ var originalName = doc.Name;
+ doc = doc.ReplaceNameAndPath(oldName, newName);
+
+ var found = false;
+ if (doc.FilePath is not null)
+ {
+ foreach (var checkDocId in proj.DocumentIds)
+ {
+ if (checkDocId == docId)
+ {
+ continue;
+ }
+
+ var checkDoc = proj.GetDocument(checkDocId);
+ if (checkDoc?.FilePath is null)
+ {
+ continue;
+ }
+
+ if (checkDoc.FilePath == doc.FilePath)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found)
+ {
+ logger.LogError($"{originalName} -> {doc.Name} failed to rename file as a file already exists at {doc.FilePath}");
+ }
+ else
+ {
+ proj = doc.Project;
+ }
}
ctx.SourceProject = proj;
}
- private static readonly SymbolRenameOptions _typeRenameOptions =
- new(
- RenameOverloads: false,
- RenameInStrings: false,
- RenameInComments: false,
- RenameFile: true
- );
- private static readonly SymbolRenameOptions _memberRenameOptions =
- new(
- RenameOverloads: true,
- RenameInStrings: false,
- RenameInComments: false,
- RenameFile: false
- );
+ ///
+ /// Applies the prettify only pipeline.
+ /// This currently consists of checking for name overrides first.
+ /// Then if no override is found, then the name's affixes are removed,
+ /// the name is prettified, and the name's affixes are added back.
+ ///
+ private string ApplyPrettifyOnlyPipeline(
+ string? container,
+ string name,
+ Dictionary nameOverrides,
+ Dictionary affixTypes,
+ ICulturedStringTransformer nameTransformer)
+ {
+ // Check for overrides
+ foreach (var (nativeName, overriddenName) in nameOverrides)
+ {
+ if (nativeName.Contains('.'))
+ {
+ // We're processing a type dictionary, so don't add a member thing.
+ if (container is null)
+ {
+ continue;
+ }
+
+ // Check whether the override is for this type.
+ var span = nativeName.AsSpan();
+ var containerSpan = span[..span.IndexOf('.')];
+ if (!containerSpan.Equals("*", StringComparison.Ordinal) && !containerSpan.Equals(container, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ var nameToAdd = span[(span.IndexOf('.') + 1)..].ToString();
+ if (nameToAdd == name)
+ {
+ return overriddenName;
+ }
+ }
+ else if (nativeName == name)
+ {
+ return overriddenName;
+ }
+ }
+
+ // Be lenient about caps for type names (e.g. GL)
+ var allowAllCaps = container == null;
+
+ var result = name;
+ result = RemoveAffixes(result, container, name, affixTypes, null);
+ result = result.Prettify(nameTransformer, allowAllCaps);
+ result = ApplyAffixes(result, container, name, affixTypes, null);
+
+ return result;
+ }
private void Trim(
NameTrimmerContext context,
@@ -365,11 +426,8 @@ private void Trim(
{
// Ensure the trimmers don't see names that have been manually overridden, as we don't want them to influence
// automatic prefix determination for example
- var namesToTrim = context.Names!;
- foreach (
- var (nativeName, overriddenName) in context.Configuration.NameOverrides
- ?? Enumerable.Empty>()
- )
+ var namesToTrim = context.Names;
+ foreach (var (nativeName, overriddenName) in context.Configuration.NameOverrides)
{
var nameToAdd = nativeName;
if (nativeName.Contains('.'))
@@ -381,11 +439,9 @@ private void Trim(
}
// Check whether the override is for this type.
- var span = context.Container.AsSpan();
- if (
- span[..span.IndexOf('.')] is "*"
- || span[..span.IndexOf('.')] == context.Container
- )
+ var span = nativeName.AsSpan();
+ var containerSpan = span[..span.IndexOf('.')];
+ if (containerSpan.Equals("*", StringComparison.Ordinal) || containerSpan.Equals(context.Container, StringComparison.Ordinal))
{
nameToAdd = span[(span.IndexOf('.') + 1)..].ToString();
}
@@ -412,9 +468,9 @@ private void Trim(
namesToTrim.Remove(nameToAdd);
// Apply the name override to the dictionary we actually use.
- context.Names![nameToAdd] = (
+ context.Names[nameToAdd] = new CandidateNames(
overriddenName,
- [.. v.Secondary ?? Enumerable.Empty(), nameToAdd]
+ [.. v.Secondary, nameToAdd]
);
}
@@ -424,28 +480,25 @@ private void Trim(
trimmer.Trim(context with { Names = namesToTrim });
}
- // Apply changes.
+ // Apply changes
if (namesToTrim != context.Names)
{
foreach (var (evalName, result) in namesToTrim)
{
- context.Names![evalName] = result;
+ context.Names[evalName] = result;
}
}
// Prefer shorter names
- foreach (var (trimmingName, (primary, secondary)) in context.Names!)
+ foreach (var (_, (_, secondary)) in context.Names)
{
- context.Names![trimmingName] = (
- primary,
- secondary?.OrderByDescending(x => x.Length).ToList()
- );
+ secondary.Sort((a, b) => -a.Length.CompareTo(b.Length));
}
// Create a map from primaries to trimming names, to account for multiple overloads with the same primary and
// same trimming name (i.e. it is a generated/transformed overload) but differing discriminators.
var primaries = new Dictionary>();
- foreach (var (trimmingName, (primary, _)) in context.Names!)
+ foreach (var (trimmingName, (primary, _)) in context.Names)
{
var trimmingNamesForPrimary = primaries.TryGetValue(primary, out var tnfp)
? tnfp
@@ -460,11 +513,7 @@ private void Trim(
// Keep track of the method discriminators to determine whether we have incompatible overloads that need to be
// renamed. We keep track of the first trimming name so that we can add it to conflictingTrimmingNames when we
// do discover a conflict (along with the trimming name of the actual conflict).
- var methDiscrims =
- new Dictionary<
- string,
- (string? FirstTrimmingName, List Methods)
- >();
+ var methDiscrims = new Dictionary Methods)>();
var conflictingTrimmingNames = new HashSet();
while (namesToEval.GetEnumerator() is var e && e.MoveNext() && e.Current is var primary)
{
@@ -488,7 +537,7 @@ private void Trim(
foreach (var trimmingNameToEval in trimmingNamesForOldPrimary)
{
// Do we even have a secondary to fall back on if there is a conflict?
- if ((context.Names![trimmingNameToEval].Secondary?.Count ?? 0) == 0)
+ if (context.Names[trimmingNameToEval].Secondary.Count == 0)
{
noSecondaryTrimmingName ??= trimmingNameToEval;
nNoSecondaries++;
@@ -603,16 +652,11 @@ var conflictingTrimmingName in (
{
// Update the output name.
var firstSecondary =
- context.Names![first].Secondary
- ?? throw new InvalidOperationException(
- "More than one trimming name without secondary names."
- );
+ context.Names[first].Secondary
+ ?? throw new InvalidOperationException("More than one trimming name without secondary names.");
var firstNextPrimary = firstSecondary[^1];
firstSecondary.RemoveAt(firstSecondary.Count - 1);
- context.Names![first] = (
- firstNextPrimary,
- firstSecondary.Count == 0 ? null : firstSecondary
- );
+ context.Names[first] = new CandidateNames(firstNextPrimary, firstSecondary);
// Update our primary to trimming name map
var trimmingNamesForFirst = primaries.TryGetValue(
@@ -648,16 +692,11 @@ out var tnff
// Conflict resolution! Update the output name.
var secondary =
- context.Names![conflictingTrimmingName].Secondary
- ?? throw new InvalidOperationException(
- "More than one trimming name without secondary names."
- );
+ context.Names[conflictingTrimmingName].Secondary
+ ?? throw new InvalidOperationException("More than one trimming name without secondary names.");
var nextPrimary = secondary[^1];
secondary.RemoveAt(secondary.Count - 1);
- context.Names![conflictingTrimmingName] = (
- nextPrimary,
- secondary.Count == 0 ? null : secondary
- );
+ context.Names[conflictingTrimmingName] = new CandidateNames(nextPrimary, secondary);
// Update our primary to trimming name map
var trimmingNamesForNewPrimary = primaries.TryGetValue(nextPrimary, out var tnfp)
@@ -686,276 +725,709 @@ out var tnff
}
}
- private class Visitor : CSharpSyntaxWalker
+ ///
+ /// Gets affix data for the specified container and original name of the identifier.
+ ///
+ /// The container name. Either null or the containing type.
+ /// The original name of the identifier. Either the type name or the member name.
+ /// The affix data retrieved by the .
+ /// The name affixes for the specified identifier.
+ private static NameAffix[] GetAffixes(string? container, string originalName, Dictionary affixTypes)
{
- public Dictionary<
- string,
- (
- List? NonFunctions,
- List<(string Name, MethodDeclarationSyntax Syntax)>? Functions,
- bool IsEnum
- )
- > Types = new();
-
- public Dictionary> PrettifyOnlyTypes = new();
- public HashSet NonDeterminant { get; } = [];
- private (
- ClassDeclarationSyntax Class,
- List NonFunctions,
- List<(string Name, MethodDeclarationSyntax Syntax)> Functions
- )? _classInProgress;
- private (EnumDeclarationSyntax Enum, List EnumMembers)? _enumInProgress;
- private FieldDeclarationSyntax? _visitingField = null;
- private bool _prettifyOnly;
+ TypeAffixData typeAffixData;
+ if (container == null)
+ {
+ if (!affixTypes.TryGetValue(originalName, out typeAffixData))
+ {
+ return [];
+ }
- public override void VisitClassDeclaration(ClassDeclarationSyntax node)
+ return typeAffixData.TypeAffixes;
+ }
+
+ if (affixTypes.TryGetValue(container, out typeAffixData))
{
- if (
- _classInProgress is not null // nesting is ignored for now
- || _enumInProgress is not null // class nested in an enum, wtf?
- || node.Ancestors().OfType().Any() // again... nesting is ignored.
- )
+ if (typeAffixData.MemberAffixes?.TryGetValue(originalName, out var affixes) ?? false)
{
- return;
+ return affixes;
}
+ }
- if (
- node.AttributeLists.Any(x =>
- x.Attributes.Any(y => y.IsAttribute("Silk.NET.Core.Transformed"))
- )
- )
+ return [];
+ }
+
+ ///
+ /// Removes affixes from the specified primary name and adds the original specified primary to the secondary list if provided.
+ ///
+ ///
+ /// Designed to be used by either or .
+ ///
+ /// The current primary name.
+ /// The container name. Either null or the containing type.
+ /// The original name of the identifier. Either the type name or the member name.
+ /// The affix data retrieved by the .
+ /// The list of secondary names. This should be null if used by .
+ /// The new primary name.
+ private static string RemoveAffixes(string primary, string? container, string originalName, Dictionary affixTypes, List? secondary)
+ {
+ var affixes = GetAffixes(container, originalName, affixTypes);
+ if (affixes.Length == 0)
+ {
+ return primary;
+ }
+
+ var originalPrimary = primary;
+
+ // Sort affixes so that the outer affixes are first
+ affixes.Sort(static (a, b) =>
+ {
+ // Sort by ascending order
+ // Higher order means the affix is closer to the inside of the name
+ if (a.Order != b.Order)
{
- NonDeterminant.Add(node.Identifier.ToString());
+ return a.Order.CompareTo(b.Order);
}
- _classInProgress = (
- node,
- new List(),
- new List<(string, MethodDeclarationSyntax)>()
- );
+ // Then by descending declaration order
+ // Lower declaration order means the affix is closer to the inside of the name
+ return -a.DeclarationOrder.CompareTo(b.DeclarationOrder);
+ });
- // Recurse into the members.
- base.VisitClassDeclaration(node);
- var id = node.Identifier.ToString();
+ var prefixes = affixes.Where(x => x.IsPrefix).ToList();
+ var suffixes = affixes.Where(x => !x.IsPrefix).ToList();
+
+ RemoveSide(true, prefixes);
+ RemoveSide(false, suffixes);
- // Tolerate partial classes.
- if (!Types.TryGetValue(id, out var inner))
+ if (originalPrimary != primary)
+ {
+ secondary?.Add(originalPrimary);
+ }
+
+ return primary;
+
+ void RemoveSide(bool isPrefix, List nameAffixes)
+ {
+ while (nameAffixes.Count > 0)
{
- inner = (new List(), new List<(string, MethodDeclarationSyntax)>(), false);
- Types.Add(id, inner);
+ var removedAffix = false;
+ for (var i = 0; i < nameAffixes.Count; i++)
+ {
+ var affix = nameAffixes[i];
+ if (isPrefix ? primary.StartsWith(affix.Affix) : primary.EndsWith(affix.Affix))
+ {
+ primary = isPrefix ? primary[affix.Affix.Length..] : primary[..^affix.Affix.Length];
+
+ nameAffixes.RemoveAt(i);
+ removedAffix = true;
+ break;
+ }
+ }
+
+ if (!removedAffix)
+ {
+ break;
+ }
}
+ }
+ }
- // Merge with the other partials.
- (inner.NonFunctions ??= new List()).AddRange(
- _classInProgress.Value.NonFunctions
- );
- (inner.Functions ??= new List<(string, MethodDeclarationSyntax)>()).AddRange(
- _classInProgress.Value.Functions
- );
- _classInProgress = null;
+ ///
+ /// Applies affixes to the specified primary name and adds fallbacks to the secondary list if provided.
+ ///
+ ///
+ /// Designed to be used by either or .
+ ///
+ /// The current primary name.
+ /// The container name. Either null or the containing type.
+ /// The original name of the identifier. Either the type name or the member name.
+ /// The affix data retrieved by the .
+ /// The list of secondary names. This should be null if used by .
+ /// The new primary name.
+ private static string ApplyAffixes(string primary, string? container, string originalName, Dictionary affixTypes, List? secondary)
+ {
+ var affixes = GetAffixes(container, originalName, affixTypes);
+ if (affixes.Length == 0)
+ {
+ return primary;
}
- public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
+ // Sort affixes by priority
+ // Negative priority is first, followed by highest non-negative priority
+ // This groups the required affixes at the start and each group of fallback affixes together
+ affixes.Sort(static (a, b) =>
{
- // Can't have a field within a field or an enum. This is basically a "wtf" check.
- if (_visitingField is not null || _enumInProgress is not null)
+ // Negative priority first
+ // These are our required affixes
+ if (int.Sign(a.DiscriminatorPriority) != 1 || int.Sign(b.DiscriminatorPriority) != 1)
{
- return;
+ return a.DiscriminatorPriority.CompareTo(b.DiscriminatorPriority);
}
- // If it's not a constant then we only prettify.
- if (
- !node.Modifiers.Any(SyntaxKind.ConstKeyword)
- && !node.Modifiers.Any(SyntaxKind.StaticKeyword)
- )
+ // Then sort the remaining by descending priority
+ return -a.DiscriminatorPriority.CompareTo(b.DiscriminatorPriority);
+ });
+
+ // This is guaranteed to be non-null when this method returns if there is at least one affix
+ string? newPrimary = null;
+
+ var currentPriority = -1;
+ for (var affixI = 0; affixI < affixes.Length; affixI++)
+ {
+ var affix = affixes[affixI];
+ if (currentPriority == -1 && affix.DiscriminatorPriority < 0)
{
- _prettifyOnly = true;
+ continue;
}
- _visitingField = node;
- base.VisitFieldDeclaration(node);
- _prettifyOnly = false;
- _visitingField = null;
+ if (currentPriority == -1 || affix.DiscriminatorPriority < currentPriority)
+ {
+ currentPriority = affix.DiscriminatorPriority;
+ CreateName(primary, affixes.AsSpan()[..affixI], ref newPrimary, secondary);
+ if (secondary == null)
+ {
+ return newPrimary!;
+ }
+ }
}
- public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
+ CreateName(primary, affixes, ref newPrimary, secondary);
+
+ return newPrimary!;
+
+ static void CreateName(string name, Span currentAffixes, ref string? newPrimary, List? secondary)
{
- if (node.Parent?.Parent != _visitingField)
+ // Sort affixes so that the inner affixes are first
+ currentAffixes.Sort(static (a, b) =>
{
- return;
- }
+ // Sort by descending order
+ // Higher order means the affix is closer to the inside of the name
+ if (a.Order != b.Order)
+ {
+ return -a.Order.CompareTo(b.Order);
+ }
- if (
- _prettifyOnly
- && node.Parent?.Parent?.Parent is BaseTypeDeclarationSyntax type
- && type.Parent?.FirstAncestorOrSelf() is null
- )
+ // Then by ascending declaration order
+ // Lower declaration order means the affix is closer to the inside of the name
+ return a.DeclarationOrder.CompareTo(b.DeclarationOrder);
+ });
+
+ foreach (var affix in currentAffixes)
{
- var tiden = type.Identifier.ToString();
- if (!PrettifyOnlyTypes.TryGetValue(tiden, out var inner))
+ if (affix.Order >= 0)
{
- inner = new List();
- PrettifyOnlyTypes.Add(tiden, inner);
+ if (affix.IsPrefix)
+ {
+ name = affix.Affix + name;
+ }
+ else
+ {
+ name += affix.Affix;
+ }
}
+ }
- var iden = node.Identifier.ToString();
- inner.Add(iden);
+ if (newPrimary == null)
+ {
+ newPrimary = name;
}
- else if (_classInProgress is not null && !_prettifyOnly)
+ else
{
- _classInProgress.Value.NonFunctions.Add(node.Identifier.ToString());
+ secondary?.Add(name);
}
}
+ }
- public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
+ ///
+ public Task> BeforeScrapeAsync(string key, List rsps)
+ {
+ foreach (var responseFile in rsps)
{
- if (node.Parent == _classInProgress?.Class)
+ if (!responseFile.GeneratorConfiguration.DontUseUsingStaticsForEnums)
{
- _classInProgress!.Value.Functions.Add((node.Identifier.ToString(), node));
+ logger.LogWarning(
+ "{} (for {}) should use exclude-using-statics-for-enums as PrettifyNames does not resolve "
+ + "conflicts with members of other types.",
+ responseFile.FilePath,
+ key
+ );
+ }
+ if (!responseFile.GeneratorConfiguration.DontUseUsingStaticsForGuidMember)
+ {
+ logger.LogWarning(
+ "{} (for {}) should use exclude-using-statics-for-guid-members as PrettifyNames does not resolve "
+ + "conflicts with members of other types.",
+ responseFile.FilePath,
+ key
+ );
}
}
- public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
+ return Task.FromResult(rsps);
+ }
+
+ ///
+ /// Contains the new name of a type and mappings between original names and new names of its members.
+ ///
+ /// The new name of the type.
+ /// The mappings from original names to new names of the type's non-function members.
+ /// The mappings from original names to new names of the type's function members.
+ private record struct RenamedType(string NewName, Dictionary NonFunctions, Dictionary Functions);
+
+ private record struct NameAffix(bool IsPrefix, string Affix, int Order, int DiscriminatorPriority, int DeclarationOrder);
+
+ private record struct TypeData(List NonFunctions, List Functions);
+ private record struct FunctionData(string Name, MethodDeclarationSyntax Syntax);
+ private record struct TypeAffixData(NameAffix[] TypeAffixes, Dictionary? MemberAffixes);
+
+ private class Visitor : CSharpSyntaxWalker
+ {
+ ///
+ /// A mapping from type names to their member names (along with some additional info).
+ /// These names are first trimmed, then prettified.
+ ///
+ public Dictionary TrimmableTypes { get; } = new();
+
+ ///
+ /// A mapping from type names to their member names.
+ /// These names do not participate in trimming and are only prettified.
+ ///
+ public Dictionary> PrettifyOnlyTypes { get; } = new();
+
+ ///
+ /// A mapping from type names to the type's affix data, which contains mappings from member names to each member's affix data.
+ /// This is used at the start of trimming to remove declared affixes and at the end to restore declared affixes.
+ /// Declared affixes are defined by the [NamePrefix] and [NameSuffix] attributes and don't contribute towards the usual trimming processes.
+ ///
+ public Dictionary AffixTypes { get; } = new();
+
+ ///
+ /// A set of type names marked with the [Transformed] attribute.
+ ///
+ ///
+ /// These are not used for prefix determination since they can contain identifiers that
+ /// are not part of the original source code.
+ ///
+ public HashSet NonDeterminant { get; } = new();
+
+ ///
+ /// Tracks the type that we currently are visiting.
+ ///
+ private TypeInProgress? _typeInProgress;
+
+ ///
+ /// Tracks the enum that we currently are visiting.
+ ///
+ private EnumInProgress? _enumInProgress;
+
+ ///
+ /// While this is called a "type" in progress, this represents either a class or a struct.
+ ///
+ /// The class or struct's declaration syntax node.
+ /// The names of the non-function members directly contained by the type.
+ /// The names of the function members directly contained by the type.
+ private record struct TypeInProgress(TypeDeclarationSyntax Type, List NonFunctions, List Functions);
+
+ ///
+ /// Represents an enum.
+ ///
+ /// The enum's declaration syntax node.
+ /// The names of the members directly contained by the enum.
+ private record struct EnumInProgress(EnumDeclarationSyntax Enum, List EnumMembers);
+
+ ///
+ /// Returns whether we are currently inside of a type.
+ ///
+ ///
+ /// Note that we currently do not handle nested types.
+ /// If we encounter a type while we are already in a type, we ignore that type.
+ /// If we encounter a non-type (i.e., a type member), we add the member to the type we are already in.
+ ///
+ private bool IsCurrentlyInType(SyntaxNode node) =>
+ _typeInProgress is not null
+ || _enumInProgress is not null
+ || node.Ancestors().OfType().Any();
+
+ private bool TryGetAffixData(SyntaxList attributeLists, out NameAffix[] affixes)
{
- if (node.Parent == _classInProgress?.Class)
+ affixes = [];
+ var declarationOrder = 0;
+ foreach (var list in attributeLists)
{
- _classInProgress!.Value.NonFunctions.Add(node.Identifier.ToString());
+ foreach (var attribute in list.Attributes)
+ {
+ if (!attribute.IsAttribute("Silk.NET.Core.NameAffix"))
+ {
+ continue;
+ }
+
+ if (attribute.ArgumentList != null)
+ {
+ var type = (attribute.ArgumentList.Arguments[0].Expression as LiteralExpressionSyntax)?.Token.Value as string;
+ var affix = (attribute.ArgumentList.Arguments[1].Expression as LiteralExpressionSyntax)?.Token.Value as string;
+ var order = (attribute.ArgumentList.Arguments[2].Expression as LiteralExpressionSyntax)?.Token.Value as int? ?? -1;
+ var discriminatorPriority = (attribute.ArgumentList.Arguments[3].Expression as LiteralExpressionSyntax)?.Token.Value as int? ?? -1;
+
+ if (affix != null)
+ {
+ affixes = [..affixes, new NameAffix(type == "Prefix", affix, order, discriminatorPriority, declarationOrder)];
+ declarationOrder++;
+ }
+ }
+ }
}
+
+ return affixes.Length != 0;
}
- public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node)
+ private void ReportTypeAffixData(string typeIdentifier, SyntaxList attributeLists)
{
- if (
- _classInProgress is not null
- || _enumInProgress is not null
- || node.Ancestors().OfType().Any()
- )
+ if (!TryGetAffixData(attributeLists, out var affixes))
{
- if (node.Parent == _classInProgress?.Class)
- {
- _classInProgress!.Value.NonFunctions.Add(node.Identifier.ToString());
- }
+ return;
+ }
+ if (!AffixTypes.TryGetValue(typeIdentifier, out var typeAffixData))
+ {
+ typeAffixData = new TypeAffixData([], null);
+ }
+
+ AffixTypes[typeIdentifier] = typeAffixData with
+ {
+ TypeAffixes = [..typeAffixData.TypeAffixes, ..affixes],
+ };
+ }
+
+ private void ReportMemberAffixData(string typeIdentifier, string memberIdentifier, SyntaxList attributeLists)
+ {
+ if (!TryGetAffixData(attributeLists, out var affixData))
+ {
return;
}
- if (
- node.AttributeLists.Any(x =>
- x.Attributes.Any(y => y.IsAttribute("Silk.NET.Core.Transformed"))
- )
- )
+ if (!AffixTypes.TryGetValue(typeIdentifier, out var typeAffixData))
{
- NonDeterminant.Add(node.Identifier.ToString());
+ typeAffixData = new TypeAffixData([], null);
}
- Types.Add(node.Identifier.ToString(), (null, null, false));
+ // Note that TryAdd will lead to affixes for later members being silently dropped.
+ // This is to handle methods which have the same name and affixes. It is fine to drop the affixes in this case.
+ (typeAffixData.MemberAffixes ??= []).TryAdd(memberIdentifier, affixData);
+ AffixTypes[typeIdentifier] = typeAffixData;
}
- public override void VisitStructDeclaration(StructDeclarationSyntax node)
+ // ----- Types -----
+
+ public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
- if (
- _classInProgress is not null
- || _enumInProgress is not null
- || node.Ancestors().OfType().Any()
- )
+ if (IsCurrentlyInType(node))
{
return;
}
- if (
- node.AttributeLists.Any(x =>
- x.Attributes.Any(y => y.IsAttribute("Silk.NET.Core.Transformed"))
- )
- )
+ var identifier = node.Identifier.ToString();
+ if (node.AttributeLists.ContainsAttribute("Silk.NET.Core.Transformed"))
{
- NonDeterminant.Add(node.Identifier.ToString());
+ NonDeterminant.Add(identifier);
}
- Types[node.Identifier.ToString()] = (null, null, false);
- base.VisitStructDeclaration(node);
+ ReportTypeAffixData(identifier, node.AttributeLists);
+
+ // Recurse into members.
+ _typeInProgress = new TypeInProgress(node, [], []);
+ base.VisitClassDeclaration(node);
+
+ // Merge with existing data in case of partials
+ if (!TrimmableTypes.TryGetValue(identifier, out var typeData))
+ {
+ typeData = new TypeData([], []);
+ TrimmableTypes.Add(identifier, typeData);
+ }
+
+ typeData.NonFunctions.AddRange(_typeInProgress.Value.NonFunctions.Where(nonFunction => !typeData.NonFunctions.Contains(nonFunction)));
+ typeData.Functions.AddRange(_typeInProgress.Value.Functions);
+
+ _typeInProgress = null;
}
- public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node)
+ public override void VisitStructDeclaration(StructDeclarationSyntax node)
{
- if (node.Parent == _enumInProgress?.Enum)
+ if (IsCurrentlyInType(node))
+ {
+ return;
+ }
+
+ var identifier = node.Identifier.ToString();
+ if (node.AttributeLists.ContainsAttribute("Silk.NET.Core.Transformed"))
+ {
+ NonDeterminant.Add(identifier);
+ }
+
+ ReportTypeAffixData(identifier, node.AttributeLists);
+
+ // Recurse into members
+ _typeInProgress = new TypeInProgress(node, [], []);
+ base.VisitStructDeclaration(node);
+
+ // Merge with existing data in case of partials
+ if (!TrimmableTypes.TryGetValue(identifier, out var typeData))
{
- _enumInProgress!.Value.EnumMembers.Add(node.Identifier.ToString());
+ typeData = new TypeData([], []);
+ TrimmableTypes.Add(identifier, typeData);
}
+
+ typeData.NonFunctions.AddRange(_typeInProgress.Value.NonFunctions.Where(nonFunction => !typeData.NonFunctions.Contains(nonFunction)));
+ typeData.Functions.AddRange(_typeInProgress.Value.Functions);
+
+ _typeInProgress = null;
}
public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
{
- if (
- _classInProgress is not null
- || _enumInProgress is not null
- || node.Ancestors().OfType().Any()
- )
+ if (IsCurrentlyInType(node))
{
return;
}
- if (
- node.AttributeLists.Any(x =>
- x.Attributes.Any(y => y.IsAttribute("Silk.NET.Core.Transformed"))
- )
- )
+ var identifier = node.Identifier.ToString();
+ if (node.AttributeLists.ContainsAttribute("Silk.NET.Core.Transformed"))
{
- NonDeterminant.Add(node.Identifier.ToString());
+ NonDeterminant.Add(identifier);
}
- _enumInProgress = (node, new List());
+ ReportTypeAffixData(identifier, node.AttributeLists);
+
+ // Recurse into members
+ _enumInProgress = new EnumInProgress(node, []);
base.VisitEnumDeclaration(node);
- var id = _enumInProgress.Value.Enum.Identifier.ToString();
- if (!Types.TryGetValue(id, out var inner))
+
+ // Merge with existing data in case of partials
+ if (!TrimmableTypes.TryGetValue(identifier, out var typeData))
{
- inner = (new List(), new List<(string, MethodDeclarationSyntax)>(), true);
- Types.Add(id, inner);
+ typeData = new TypeData([], []);
+ TrimmableTypes.Add(identifier, typeData);
}
- (inner.NonFunctions ??= new List()).AddRange(_enumInProgress.Value.EnumMembers);
+ typeData.NonFunctions.AddRange(_enumInProgress.Value.EnumMembers);
_enumInProgress = null;
}
+
+ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node)
+ {
+ var identifier = node.Identifier.ToString();
+ if (IsCurrentlyInType(node))
+ {
+ if (node.Parent == _typeInProgress?.Type)
+ {
+ _typeInProgress!.Value.NonFunctions.Add(identifier);
+ }
+
+ return;
+ }
+
+ if (node.AttributeLists.ContainsAttribute("Silk.NET.Core.Transformed"))
+ {
+ NonDeterminant.Add(identifier);
+ }
+
+ ReportTypeAffixData(identifier, node.AttributeLists);
+ TrimmableTypes.Add(identifier, new TypeData([], []));
+ }
+
+ // ----- Members -----
+
+ public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node)
+ {
+ if (node.Parent == _enumInProgress?.Enum)
+ {
+ var typeIdentifier = _enumInProgress!.Value.Enum.Identifier.ToString();
+ var memberIdentifier = node.Identifier.ToString();
+ ReportMemberAffixData(typeIdentifier, memberIdentifier, node.AttributeLists);
+
+ _enumInProgress!.Value.EnumMembers.Add(memberIdentifier);
+ }
+ }
+
+ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
+ {
+ // If it's not a constant then we only prettify
+ // C constants are globally scoped and typically prefixed, so we trim in addition to prettifying
+ var prettifyOnly = !node.Modifiers.Any(SyntaxKind.ConstKeyword) && !node.Modifiers.Any(SyntaxKind.StaticKeyword);
+
+ if (node.Parent == _typeInProgress?.Type)
+ {
+ var typeIdentifier = _typeInProgress!.Value.Type.Identifier.ToString();
+ foreach (var variable in node.Declaration.Variables)
+ {
+ var memberIdentifier = variable.Identifier.ToString();
+ ReportMemberAffixData(typeIdentifier, memberIdentifier, node.AttributeLists);
+
+ if (prettifyOnly)
+ {
+ if (!PrettifyOnlyTypes.TryGetValue(typeIdentifier, out var typeData))
+ {
+ typeData = [];
+ PrettifyOnlyTypes.Add(typeIdentifier, typeData);
+ }
+
+ typeData.Add(memberIdentifier);
+ }
+ else
+ {
+ _typeInProgress.Value.NonFunctions.Add(memberIdentifier);
+ }
+ }
+ }
+ }
+
+ public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
+ {
+ if (node.Parent == _typeInProgress?.Type)
+ {
+ var typeIdentifier = _typeInProgress!.Value.Type.Identifier.ToString();
+ var memberIdentifier = node.Identifier.ToString();
+
+ if (_typeInProgress!.Value.Type.IsKind(SyntaxKind.StructDeclaration))
+ {
+ // Prettify only
+ // Struct methods are introduced by the generator so they are not prefixed
+ if (!PrettifyOnlyTypes.TryGetValue(typeIdentifier, out var typeData))
+ {
+ typeData = [];
+ PrettifyOnlyTypes.Add(typeIdentifier, typeData);
+ }
+
+ typeData.Add(memberIdentifier);
+ }
+ else
+ {
+ // Trim + Prettify
+ ReportMemberAffixData(typeIdentifier, memberIdentifier, node.AttributeLists);
+
+ _typeInProgress!.Value.Functions.Add(new FunctionData(memberIdentifier, node));
+ }
+ }
+ }
+
+ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
+ {
+ if (node.Parent == _typeInProgress?.Type)
+ {
+ var typeIdentifier = _typeInProgress!.Value.Type.Identifier.ToString();
+ var memberIdentifier = node.Identifier.ToString();
+ ReportMemberAffixData(typeIdentifier, memberIdentifier, node.AttributeLists);
+
+ // If it's not a constant then we only prettify.
+ var hasSetter = node.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration) || a.IsKind(SyntaxKind.InitAccessorDeclaration)) ?? false;
+ if (hasSetter)
+ {
+ if (!PrettifyOnlyTypes.TryGetValue(typeIdentifier, out var typeData))
+ {
+ typeData = [];
+ PrettifyOnlyTypes.Add(typeIdentifier, typeData);
+ }
+
+ typeData.Add(memberIdentifier);
+ }
+ else
+ {
+ _typeInProgress!.Value.NonFunctions.Add(memberIdentifier);
+ }
+ }
+ }
}
private class RenameSafeAttributeListsRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) =>
- (
- (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!
- ).WithRenameSafeAttributeLists();
+ ((MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!)
+ .WithRenameSafeAttributeLists();
}
- private Location? IdentifierLocation(SyntaxNode? node) =>
- node switch
- {
- BaseTypeDeclarationSyntax bt => bt.Identifier.GetLocation(),
- DelegateDeclarationSyntax d => d.Identifier.GetLocation(),
- EnumMemberDeclarationSyntax em => em.Identifier.GetLocation(),
- EventDeclarationSyntax e => e.Identifier.GetLocation(),
- MethodDeclarationSyntax m => m.Identifier.GetLocation(),
- PropertyDeclarationSyntax p => p.Identifier.GetLocation(),
- VariableDeclaratorSyntax v => v.Identifier.GetLocation(),
- ConstructorDeclarationSyntax c => c.Identifier.GetLocation(),
- DestructorDeclarationSyntax d => d.Identifier.GetLocation(),
- _ => null,
- };
+ private class NameAffixerEarlyTrimmer(Dictionary affixTypes) : INameTrimmer
+ {
+ ///
+ public Version Version => new(0, 0, 0);
- ///
- public Task> BeforeScrapeAsync(string key, List rsps)
+ ///
+ public int Order => (int)TrimmerOrder.NameAffixerEarlyTrimmer;
+
+ public void Trim(NameTrimmerContext context)
+ {
+ if (context.Container == null)
+ {
+ foreach (var (original, (primary, secondary)) in context.Names)
+ {
+ var secondaries = secondary;
+ var newPrimary = RemoveAffixes(primary, null, original, affixTypes, secondaries);
+ context.Names[original] = new CandidateNames(newPrimary, secondaries);
+ }
+
+ return;
+ }
+
+ foreach (var (original, (primary, secondary)) in context.Names)
+ {
+ var secondaries = secondary;
+ var newPrimary = RemoveAffixes(primary, context.Container, original, affixTypes, secondaries);
+ context.Names[original] = new CandidateNames(newPrimary, secondaries);
+ }
+ }
+ }
+
+ private class NameAffixerLateTrimmer(Dictionary affixTypes) : INameTrimmer
{
- foreach (var responseFile in rsps)
+ ///
+ public Version Version => new(0, 0, 0);
+
+ ///
+ public int Order => (int)TrimmerOrder.NameAffixerLateTrimmer;
+
+ public void Trim(NameTrimmerContext context)
{
- if (!responseFile.GeneratorConfiguration.DontUseUsingStaticsForEnums)
+ if (context.Container == null)
{
- logger.LogWarning(
- "{} (for {}) should use exclude-using-statics-for-enums as PrettifyNames does not resolve "
- + "conflicts with members of other types.",
- responseFile.FilePath,
- key
- );
+ foreach (var (original, (primary, secondary)) in context.Names)
+ {
+ var secondaries = secondary;
+ var newPrimary = ApplyAffixes(primary, null, original, affixTypes, secondaries);
+ context.Names[original] = new CandidateNames(newPrimary, secondaries);
+ }
+
+ return;
+ }
+
+ foreach (var (original, (primary, secondary)) in context.Names)
+ {
+ var secondaries = secondary;
+ var newPrimary = ApplyAffixes(primary, context.Container, original, affixTypes, secondaries);
+ context.Names[original] = new CandidateNames(newPrimary, secondaries);
}
}
+ }
- return Task.FromResult(rsps);
+ private class PrettifyNamesTrimmer(ICulturedStringTransformer nameTransformer) : INameTrimmer
+ {
+ ///
+ public Version Version => new(0, 0, 0);
+
+ ///
+ public int Order => (int)TrimmerOrder.PrettifyNamesTrimmer;
+
+ public void Trim(NameTrimmerContext context)
+ {
+ foreach (var (original, (primary, secondary)) in context.Names)
+ {
+ // Be lenient about caps for type names (e.g. GL)
+ var allowAllCaps = context.Container == null;
+
+ for (var i = 0; i < secondary.Count; i++)
+ {
+ secondary[i] = secondary[i].Prettify(nameTransformer, allowAllCaps);
+ }
+
+ context.Names[original] = new CandidateNames(primary.Prettify(nameTransformer, allowAllCaps), secondary);
+ }
+ }
}
}
diff --git a/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs b/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs
new file mode 100644
index 0000000000..09e2e10091
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs
@@ -0,0 +1,189 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Extensions.Options;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Silk.NET.SilkTouch.Mods;
+
+///
+/// Strips matching attributes from the generated bindings.
+///
+[ModConfiguration]
+public class StripAttributes(IOptionsSnapshot cfg) : IMod
+{
+ ///
+ /// mod configuration.
+ ///
+ public record Configuration
+ {
+ ///
+ /// The list of attribute types to remove. These must be exact matches.
+ ///
+ public HashSet Remove { get; init; } = new();
+ }
+
+ ///
+ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
+ {
+ var config = cfg.Get(ctx.JobKey);
+
+ var proj = ctx.SourceProject;
+ if (proj == null)
+ {
+ return;
+ }
+
+ var compilation = await proj.GetCompilationAsync(ct);
+ if (compilation == null)
+ {
+ return;
+ }
+
+ var rewriter = new Rewriter(config);
+ foreach (var docId in proj.DocumentIds)
+ {
+ var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing");
+ proj = doc.WithSyntaxRoot(
+ rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace()
+ ?? throw new InvalidOperationException("Visit returned null.")
+ ).Project;
+ }
+
+ ctx.SourceProject = proj;
+ }
+
+ private class Rewriter(Configuration config) : ModCSharpSyntaxRewriter
+ {
+ private SyntaxList StripAttributes(SyntaxList attributeLists)
+ {
+ var results = attributeLists
+ .Select(list =>
+ {
+ var attributes = list.Attributes;
+ attributes = [..attributes.Where(attribute => !config.Remove.Contains(attribute.Name.ToString()))];
+
+ return attributes.Count == 0 ? null : list.WithAttributes(attributes);
+ })
+ .Where(list => list != null)
+ .Cast();
+
+ return List(results);
+ }
+
+ // ----- Types -----
+
+ ///
+ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (StructDeclarationSyntax)base.VisitStructDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (EnumDeclarationSyntax)base.VisitEnumDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (InterfaceDeclarationSyntax)base.VisitInterfaceDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (RecordDeclarationSyntax)base.VisitRecordDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (DelegateDeclarationSyntax)base.VisitDelegateDeclaration(node)!;
+ return node;
+ }
+
+ // ----- Members -----
+
+ ///
+ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (EnumMemberDeclarationSyntax)base.VisitEnumMemberDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (PropertyDeclarationSyntax)base.VisitPropertyDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!;
+ return node;
+ }
+
+ ///
+ public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
+ {
+ // This just uses the first declared field's identifier in cases where a field declares multiple variables
+ // Eg: int a, b;
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (FieldDeclarationSyntax)base.VisitFieldDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (EventDeclarationSyntax)base.VisitEventDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (ConstructorDeclarationSyntax)base.VisitConstructorDeclaration(node)!;
+ return node;
+ }
+
+ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (DestructorDeclarationSyntax)base.VisitDestructorDeclaration(node)!;
+ return node;
+ }
+
+ // ----- Other -----
+
+ public override SyntaxNode VisitParameter(ParameterSyntax node)
+ {
+ node = node.WithAttributeLists(StripAttributes(node.AttributeLists));
+ node = (ParameterSyntax)base.VisitParameter(node)!;
+ return node;
+ }
+ }
+}
diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs b/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs
index 107ae9e93d..81c6c91f37 100644
--- a/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs
@@ -44,7 +44,7 @@ public enum EnumBackingTypePreference
}
///
- /// TransformEnums mod configuration.
+ /// mod configuration.
///
public record Configuration
{
@@ -177,8 +177,8 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
return;
}
- var referenceDetector = new MemberReferenceDetector();
- var rewriter = new Rewriter(config, removeMemberFilters, compilation, referenceDetector);
+ var memberRewriteDecider = new MemberRewriteDecider();
+ var rewriter = new Rewriter(config, removeMemberFilters, compilation, memberRewriteDecider);
foreach (var docId in proj.DocumentIds)
{
var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing");
@@ -191,7 +191,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default)
ctx.SourceProject = proj;
}
- private class Rewriter(Configuration config, List removeMemberFilters, Compilation compilation, MemberReferenceDetector referenceDetector) : CSharpSyntaxRewriter
+ private class Rewriter(Configuration config, List removeMemberFilters, Compilation compilation, MemberRewriteDecider memberRewriteDecider) : CSharpSyntaxRewriter
{
public override SyntaxNode? VisitEnumDeclaration(EnumDeclarationSyntax node)
{
@@ -219,9 +219,7 @@ private class Rewriter(Configuration config, List removeMember
.ToList();
}
- var isFlagsEnum = node.AttributeLists.SelectMany(list => list.Attributes)
- .Any(attribute => attribute.IsAttribute("System.Flags"));
-
+ var isFlagsEnum = node.AttributeLists.ContainsAttribute("System.Flags");
if (isFlagsEnum && config.AddNoneMemberToFlags)
{
// Add None member if it doesn't exist yet
@@ -254,36 +252,7 @@ private class Rewriter(Configuration config, List removeMember
}
}
- if (config.RewriteMemberValues)
- {
- members = members
- .Select(m =>
- {
- if (m.Parent == null)
- {
- return m;
- }
-
- // Enum member contains a reference
- // We want to preserve these
- referenceDetector.Visit(m.EqualsValue);
- if (referenceDetector.ContainsReference)
- {
- return m;
- }
-
- var fieldSymbol = semanticModel.GetDeclaredSymbol(m);
- if (fieldSymbol == null)
- {
- return m;
- }
-
- var value = Convert.ToInt64(fieldSymbol.ConstantValue);
- return m.WithEqualsValue(CreateEqualsValueClause(value, isFlagsEnum));
- })
- .ToList();
- }
-
+ // This code uses the semantic model for members so it has to run before the members are rewritten below
switch (config.CoerceBackingTypes)
{
case EnumBackingTypePreference.PreferSigned:
@@ -324,6 +293,28 @@ private class Rewriter(Configuration config, List removeMember
case EnumBackingTypePreference.PreferUnsigned:
{
+ var hasNegativeValues = members.Any(m => {
+ if (m.Parent == null)
+ {
+ return false;
+ }
+
+ var fieldSymbol = semanticModel.GetDeclaredSymbol(m);
+ if (fieldSymbol == null)
+ {
+ return false;
+ }
+
+ var value = Convert.ToInt64(fieldSymbol.ConstantValue);
+ return value < 0;
+ });
+
+ if (hasNegativeValues)
+ {
+ // Enum has negative values, we can't use an unsigned backing type
+ break;
+ }
+
var baseList = originalNode.BaseList;
var baseTypes = (baseList?.Types ?? [])
.Select(t =>
@@ -359,6 +350,42 @@ private class Rewriter(Configuration config, List removeMember
}
}
+ if (config.RewriteMemberValues)
+ {
+ members = members
+ .Select(m =>
+ {
+ if (m.Parent == null)
+ {
+ return m;
+ }
+
+ if (m.EqualsValue != null)
+ {
+ memberRewriteDecider.Visit(m.EqualsValue);
+ if (!memberRewriteDecider.ShouldRewrite)
+ {
+ return m;
+ }
+ }
+
+ var fieldSymbol = semanticModel.GetDeclaredSymbol(m);
+ if (fieldSymbol == null)
+ {
+ return m;
+ }
+
+ if (fieldSymbol.ConstantValue == null)
+ {
+ return m;
+ }
+
+ var value = Convert.ToInt64(fieldSymbol.ConstantValue);
+ return m.WithEqualsValue(CreateEqualsValueClause(value, isFlagsEnum));
+ })
+ .ToList();
+ }
+
node = node.WithMembers([..members]);
return base.VisitEnumDeclaration(node);
@@ -373,20 +400,38 @@ private EqualsValueClauseSyntax CreateEqualsValueClause(long value, bool useHex)
}
}
- private class MemberReferenceDetector : CSharpSyntaxWalker
+ private class MemberRewriteDecider : CSharpSyntaxWalker
{
- public bool ContainsReference { get; private set; }
+ ///
+ /// Whether the enum member's value should be rewritten to be simpler.
+ /// We default to rewriting the member value, but do not if the enum member contains important information.
+ ///
+ /// Currently, there is only 1 case.
+ ///
+ /// Case 1: References to enum members or constants. Eg: NoneKHR = None in Vulkan.
+ /// We approximate this by checking for identifiers in the .
+ ///
+ public bool ShouldRewrite { get; private set; }
public override void VisitEqualsValueClause(EqualsValueClauseSyntax node)
{
- ContainsReference = false;
+ ShouldRewrite = true;
base.VisitEqualsValueClause(node);
}
public override void VisitIdentifierName(IdentifierNameSyntax node)
{
base.VisitIdentifierName(node);
- ContainsReference = true;
+ ShouldRewrite = false;
+ }
+
+ public override void VisitCastExpression(CastExpressionSyntax node)
+ {
+ // Ignore cast expressions
+ // These contain identifiers, but these identifiers are references to types.
+ //
+ // Eg: DepthBufferBit = unchecked((uint)0x00000100)
+ // uint is an identifier, but we want to rewrite this anyway
}
}
}
diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs
index 0387459f74..afe9d46e77 100644
--- a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs
@@ -48,6 +48,11 @@ public class Config
/// Whether the DSL (i.e. nullptr) should be usable with handle types.
///
public bool UseDSL { get; init; }
+
+ ///
+ /// The order with which the -Handle suffix is applied.
+ ///
+ public int HandleSuffixOrder { get; init; } = 0;
}
///
@@ -121,17 +126,15 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct =
}
// Do the two following transformation to all references of the handle types:
- // 1. Add -Handle suffix
// 2. Reduce pointer dimensions
ctx.SourceProject = project;
await LocationTransformationUtils.ModifyAllReferencesAsync(ctx, handleTypes, [
- new IdentifierRenamingTransformer(handleTypes.Select(t => ((ISymbol)t, GetNewHandleTypeName(t.Name)))),
new PointerDimensionReductionTransformer(),
], logger, ct);
project = ctx.SourceProject;
// Use document IDs from earlier
- var handleTypeRewriter = new HandleTypeRewriter(cfg.UseDSL);
+ var handleTypeRewriter = new HandleTypeRewriter(cfg.UseDSL, cfg.HandleSuffixOrder);
foreach (var (originalName, documentId) in handleTypeDocumentIds)
{
var document = project.GetDocument(documentId) ?? throw new InvalidOperationException("Failed to find document");
@@ -147,28 +150,12 @@ await LocationTransformationUtils.ModifyAllReferencesAsync(ctx, handleTypes, [
// Rewrite handle struct to include handle members
document = document.WithSyntaxRoot(handleTypeRewriter.Visit(syntaxRoot).NormalizeWhitespace());
- // Rename document to match type name
- document = document.ReplaceNameAndPath(originalName, GetNewHandleTypeName(originalName));
-
project = document.Project;
}
ctx.SourceProject = project;
return;
-
- string GetNewHandleTypeName(string name)
- {
- // TODO: Hack: This is a temporary fix for trimming _T off of Vulkan handle structs. Remove after implementing the new prettification strategy and things should still work.
- if (name.EndsWith("_T"))
- {
- name = name[..^2];
- }
-
- name += "Handle";
-
- return name;
- }
}
private class MissingHandleTypeDiscoverer(ILogger logger, Compilation compilation, CancellationToken ct) : SymbolVisitor
@@ -495,12 +482,17 @@ public override void VisitNamedType(INamedTypeSymbol symbol)
}
}
- private class HandleTypeRewriter(bool useDSL) : CSharpSyntaxRewriter
+ private class HandleTypeRewriter(bool useDSL, int handleSuffixPriority) : CSharpSyntaxRewriter
{
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
{
var structName = node.Identifier.Text;
- return node.WithIdentifier(Identifier(structName))
+ return node
+ .WithIdentifier(Identifier(structName))
+ .WithAttributeLists(
+ new SyntaxList()
+ .WithNativeName(structName)
+ .AddNameSuffix("Handle", handleSuffixPriority))
.WithMembers(
List(
GetDefaultHandleMembers(structName).Concat(useDSL ? GetDSLHandleMembers(structName) : [])
@@ -518,9 +510,11 @@ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
private static IEnumerable GetDefaultHandleMembers(string structName)
{
+ var backingType = PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)));
+
yield return FieldDeclaration(
VariableDeclaration(
- PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword))),
+ backingType,
SingletonSeparatedList(VariableDeclarator("Handle"))
)
)
@@ -528,6 +522,25 @@ private static IEnumerable GetDefaultHandleMembers(stri
TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ReadOnlyKeyword))
);
+ yield return ConstructorDeclaration(Identifier(structName))
+ .WithModifiers(
+ TokenList(Token(SyntaxKind.PublicKeyword))
+ )
+ .WithParameterList(ParameterList(
+ SingletonSeparatedList(Parameter(Identifier("handle")).WithType(backingType)))
+ )
+ .WithBody(
+ Block(
+ ExpressionStatement(
+ AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ IdentifierName("Handle"),
+ IdentifierName("handle")
+ )
+ )
+ )
+ );
+
yield return MethodDeclaration(
PredefinedType(Token(SyntaxKind.BoolKeyword)),
Identifier("Equals")
diff --git a/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs
index f18fe7ccdf..64baa1ce54 100644
--- a/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs
+++ b/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs
@@ -251,9 +251,7 @@ Action transform
x.Attributes.Where(y =>
!y.IsAttribute("System.Runtime.InteropServices.DllImport")
&& !y.IsAttribute("Silk.NET.Core.NativeFunction")
- && !y.IsAttribute(
- "System.Runtime.CompilerServices.MethodImpl"
- )
+ && !y.IsAttribute("System.Runtime.CompilerServices.MethodImpl")
)
)
)
diff --git a/sources/SilkTouch/SilkTouch/Naming/CandidateNames.cs b/sources/SilkTouch/SilkTouch/Naming/CandidateNames.cs
new file mode 100644
index 0000000000..101122c6c8
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Naming/CandidateNames.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.SilkTouch.Naming;
+
+///
+/// Represents the set of primary and secondary options for the trimmed version of a name.
+///
+/// The preferred version of the trimmed name.
+/// The fallback versions of the trimmed name in case the primary does not work.
+public record struct CandidateNames(string Primary, List Secondary)
+{
+ ///
+ /// Formats this instance as a string.
+ ///
+ public override string ToString() =>
+ $"(Primary={Primary}, Secondary=[{string.Join(", ", Secondary)}])";
+}
diff --git a/sources/SilkTouch/SilkTouch/Naming/CandidateNamesWithOriginal.cs b/sources/SilkTouch/SilkTouch/Naming/CandidateNamesWithOriginal.cs
new file mode 100644
index 0000000000..98dcbfa1e4
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Naming/CandidateNamesWithOriginal.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.SilkTouch.Naming;
+
+///
+/// Represents the set of primary and secondary options for the trimmed version of a name.
+/// Also includes the original, unmodified name.
+///
+/// The preferred version of the trimmed name.
+/// The fallback versions of the trimmed name in case the primary does not work.
+/// The original, unmodified name.
+public record struct CandidateNamesWithOriginal(string Primary, List Secondary, string Original)
+{
+ ///
+ /// Formats this instance as a string.
+ ///
+ public override string ToString() =>
+ $"(Original={Original}, Primary={Primary}, Secondary=[{string.Join(", ", Secondary)}])";
+}
diff --git a/sources/SilkTouch/SilkTouch/Naming/INameTrimmer.cs b/sources/SilkTouch/SilkTouch/Naming/INameTrimmer.cs
index 813b74647f..ec77fef58e 100644
--- a/sources/SilkTouch/SilkTouch/Naming/INameTrimmer.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/INameTrimmer.cs
@@ -15,6 +15,12 @@ public interface INameTrimmer
///
Version Version { get; }
+ ///
+ /// Used to define the order that the trimmers will run in.
+ /// Higher values indicate that the trimmer should run later.
+ ///
+ int Order { get; }
+
///
/// Trims prefixes from the given constituent names within the given container.
///
diff --git a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer.cs b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer.cs
index e8f3980035..c8a8ff7718 100644
--- a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer.cs
@@ -14,6 +14,9 @@ public class NameTrimmer : INameTrimmer
///
public virtual Version Version => new(3, 0);
+ ///
+ public virtual int Order => (int)TrimmerOrder.NameTrimmer;
+
///
/// Determines whether a second pass without using is warranted.
///
@@ -32,8 +35,7 @@ public class NameTrimmer : INameTrimmer
public void Trim(NameTrimmerContext context)
{
string? identifiedPrefix = null;
- Dictionary? Secondary, string Original)> localNames =
- null!;
+ Dictionary localNames = null!;
var nPasses = HasRawPass
? HasNaivePass
? 3
@@ -135,7 +137,7 @@ hint is null
foreach (
var candidatePrefix in !string.IsNullOrWhiteSpace(identifiedPrefix)
? [identifiedPrefix] // otherwise we fall back to the hints...
- : context.Configuration.GlobalPrefixHints ?? Enumerable.Empty()
+ : context.Configuration.GlobalPrefixHints ?? []
)
{
if (
@@ -160,13 +162,13 @@ hint is null
continue;
}
- var sec = secondary ?? [];
+ var sec = secondary;
sec.Add(oldPrimary);
// this was trimmingName originally. given that we're using trimming name to determine a prefix but then
// using that prefix on the old primary, this could cause intended behaviour in some cases. there's probably
// a better way to do this. (this is working around glDisablei -> glDisable -> Disablei).
- context.Names![originalName] = (oldPrimary[prefixLen..].Trim('_'), sec);
+ context.Names![originalName] = new CandidateNames(oldPrimary[prefixLen..].Trim('_'), sec);
break;
}
}
@@ -191,13 +193,10 @@ hint is null
/// Returns the local names dictionary alongside it as well. That is, the mapping of the results of
/// to the new name.
///
- protected (
- string Prefix,
- Dictionary? Secondary, string Original)>
- )? GetPrefix(
+ protected (string Prefix, Dictionary)? GetPrefix(
string? container,
string? hint,
- Dictionary? Secondary)>? names,
+ Dictionary? names,
Dictionary? prefixOverrides,
HashSet? nonDeterminant,
bool getTrimmingName,
@@ -217,7 +216,7 @@ bool naive
: container ?? hint ?? string.Empty;
var localNames = names.ToDictionary(
x => getTrimmingName ? GetTrimmingName(prefixOverrides, x.Key, false, hint) : x.Key,
- x => (x.Value.Primary, x.Value.Secondary, x.Key)
+ x => new CandidateNamesWithOriginal(x.Value.Primary, x.Value.Secondary, x.Key)
);
// Set the prefix to the prefix override for this container, if it exists.
@@ -248,7 +247,7 @@ container is not null
)
: NameUtils.FindCommonPrefix(
localNames
- .Where(x => !(nonDeterminant?.Contains(x.Value.Key) ?? false))
+ .Where(x => !(nonDeterminant?.Contains(x.Value.Original) ?? false))
.Select(x => x.Key)
.ToList(),
// If naive mode is on and we're trimming type names, allow full matches (method class is
diff --git a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer217.cs b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer217.cs
index 87287fea40..e188daad90 100644
--- a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer217.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer217.cs
@@ -13,6 +13,9 @@ public class NameTrimmer217 : NameTrimmer
///
public override Version Version => new(2, 17);
+ ///
+ public override int Order => (int)TrimmerOrder.NameTrimmer217;
+
///
protected override bool HasRawPass => false;
diff --git a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer218.cs b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer218.cs
index de89020c0a..fd9a0b8fbf 100644
--- a/sources/SilkTouch/SilkTouch/Naming/NameTrimmer218.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/NameTrimmer218.cs
@@ -11,6 +11,9 @@ public class NameTrimmer218 : NameTrimmer
///
public override Version Version => new(2, 18);
+ ///
+ public override int Order => (int)TrimmerOrder.NameTrimmer218;
+
///
protected override bool HasRawPass => false;
diff --git a/sources/SilkTouch/SilkTouch/Naming/NameTrimmerContext.cs b/sources/SilkTouch/SilkTouch/Naming/NameTrimmerContext.cs
index 3c5a7c132b..caede1740e 100644
--- a/sources/SilkTouch/SilkTouch/Naming/NameTrimmerContext.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/NameTrimmerContext.cs
@@ -26,10 +26,7 @@ public readonly struct NameTrimmerContext
/// names or other names that are otherwise less preferred to the primary name are listed in the optional secondary
/// list (in order of preference).
///
- public required Dictionary<
- string,
- (string Primary, List? Secondary)
- >? Names { get; init; }
+ public required Dictionary Names { get; init; }
///
/// Gets the current configuration for the mod.
@@ -37,8 +34,8 @@ public required Dictionary<
public PrettifyNames.Configuration Configuration { get; init; }
///
- /// Gets a set of original API names (i.e. key sto ) that have been marked with
- /// the Transformed attribute.
+ /// Gets a set of original API names (i.e. the key stored in ) that have been marked with
+ /// the [Transformed] attribute.
///
public HashSet NonDeterminant { get; init; }
}
diff --git a/sources/SilkTouch/SilkTouch/Naming/NameUtils.cs b/sources/SilkTouch/SilkTouch/Naming/NameUtils.cs
index 873a5e360a..a23297ce68 100644
--- a/sources/SilkTouch/SilkTouch/Naming/NameUtils.cs
+++ b/sources/SilkTouch/SilkTouch/Naming/NameUtils.cs
@@ -41,31 +41,33 @@ public static partial class NameUtils
/// Prettifies the given string.
///
/// The string to prettify.
- ///
+ ///
/// The transformer that mutates a humanised string before being converted back to pascal case.
///
/// Whether the output is allowed to be fully capitalised ("all caps").
/// The pretty string.
- public static string Prettify(
- this string str,
- ICulturedStringTransformer transformer,
- bool allowAllCaps = false
- )
+ public static string Prettify(this string str, ICulturedStringTransformer nameTransformer, bool allowAllCaps = false)
{
+ if (str.Length == 0)
+ {
+ throw new InvalidOperationException("Cannot prettify an empty string");
+ }
+
var ret = string.Join(
null,
str.LenientUnderscore()
.Humanize()
- .Transform(transformer)
+ .Transform(nameTransformer)
.Pascalize()
.Where(x => char.IsLetter(x) || char.IsNumber(x))
);
if (ret.Length == 0)
{
- throw new InvalidOperationException($"Failed to prettify string: {str}");
+ throw new InvalidOperationException($"Prettification for '{str}' led to an empty string");
}
+ // Disallow all capitals
var retSpan = ret.AsSpan();
if (!allowAllCaps && retSpan.IndexOfAny(NotUppercase) == -1)
{
@@ -73,6 +75,7 @@ public static string Prettify(
retSpan[1..].ToLower(caps, CultureInfo.InvariantCulture);
ret = $"{ret[0]}{caps}";
}
+
return !char.IsLetter(ret[0]) ? $"X{ret}" : ret;
}
@@ -126,7 +129,7 @@ public static string FindCommonPrefix(
/// Finds a common prefix in a set of names with respect to the word boundaries
///
/// Set of names, snake_case
- /// Allows result to be a a full match with one of the names
+ /// Allows result to be a full match with one of the names
/// Match length limit
///
/// Just match the start of the strings, don't bother checking for obvious name separation gaps.
@@ -256,6 +259,7 @@ public string Transform(string input, CultureInfo? culture)
{
continue;
}
+
if (
word.Length > longAcronymThreshold
|| !AllCapitals(word)
diff --git a/sources/SilkTouch/SilkTouch/Naming/TrimmerOrder.cs b/sources/SilkTouch/SilkTouch/Naming/TrimmerOrder.cs
new file mode 100644
index 0000000000..d7cdf310ff
--- /dev/null
+++ b/sources/SilkTouch/SilkTouch/Naming/TrimmerOrder.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.SilkTouch.Naming;
+
+///
+/// Defines the order for SilkTouch's implementations.
+///
+internal enum TrimmerOrder
+{
+ NameTrimmer = 100,
+ NameTrimmer217 = 110,
+ NameTrimmer218 = 120,
+
+ NameAffixerEarlyTrimmer = 200,
+
+ MixKhronosData = 300,
+
+ PrettifyNamesTrimmer = 400,
+
+ NameAffixerLateTrimmer = 500,
+}
diff --git a/sources/SilkTouch/SilkTouch/Sources/IInputResolver.cs b/sources/SilkTouch/SilkTouch/Sources/IInputResolver.cs
index ee26b26b55..d78b13ec67 100644
--- a/sources/SilkTouch/SilkTouch/Sources/IInputResolver.cs
+++ b/sources/SilkTouch/SilkTouch/Sources/IInputResolver.cs
@@ -118,9 +118,7 @@ async Task ResolveInPlace(IList files)
async Task Resolve(ClangScraper.Configuration config)
{
await ResolveInPlace(config.ClangSharpResponseFiles);
- foreach (
- var (k, v) in config.ManualOverrides ?? Enumerable.Empty>()
- )
+ foreach (var (k, v) in config.ManualOverrides ?? [])
{
config.ManualOverrides![k] = await ResolvePath(v);
}
diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs
index ff0cca0f03..da687e8f20 100644
--- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs
+++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs
@@ -309,18 +309,14 @@ public void OverzealousNameTrimming()
{
["OpenGL"] = new MixKhronosData.JobData
{
- Configuration = new MixKhronosData.Configuration
- {
- UseExtensionVendorTrimmings = MixKhronosData
- .ExtensionVendorTrimmingMode
- .None,
- },
+ Configuration = new MixKhronosData.Configuration(),
Vendors = ["NV"],
Groups =
{
{
"OcclusionQueryParameterNameNV",
new MixKhronosData.EnumGroup(
+ "OcclusionQueryParameterNameNV",
"OcclusionQueryParameterNameNV",
"uint",
[],
@@ -333,10 +329,10 @@ public void OverzealousNameTrimming()
},
},
};
- var names = new Dictionary?)>
+ var names = new Dictionary
{
- { "GL_PIXEL_COUNT_NV", ("GL_PIXEL_COUNT_NV", []) },
- { "GL_PIXEL_COUNT_AVAILABLE_NV", ("GL_PIXEL_COUNT_AVAILABLE_NV", []) },
+ { "GL_PIXEL_COUNT_NV", new CandidateNames("GL_PIXEL_COUNT_NV", []) },
+ { "GL_PIXEL_COUNT_AVAILABLE_NV", new CandidateNames("GL_PIXEL_COUNT_AVAILABLE_NV", []) },
};
var ctx = new NameTrimmerContext
{
@@ -347,8 +343,8 @@ public void OverzealousNameTrimming()
};
baseTrimmer.Trim(ctx);
uut.Trim(ctx);
- Assert.That(names["GL_PIXEL_COUNT_NV"].Item1, Is.EqualTo("PixelCount"));
- Assert.That(names["GL_PIXEL_COUNT_AVAILABLE_NV"].Item1, Is.EqualTo("PixelCountAvailable"));
+ Assert.That(names["GL_PIXEL_COUNT_NV"].Primary, Is.EqualTo("PixelCount"));
+ Assert.That(names["GL_PIXEL_COUNT_AVAILABLE_NV"].Primary, Is.EqualTo("PixelCountAvailable"));
}
[Test]
@@ -361,18 +357,14 @@ public void OverzealousNameTrimmingFixupIsNotOverzealousForOpenAL()
{
["OpenAL"] = new MixKhronosData.JobData
{
- Configuration = new MixKhronosData.Configuration
- {
- UseExtensionVendorTrimmings = MixKhronosData
- .ExtensionVendorTrimmingMode
- .None,
- },
+ Configuration = new MixKhronosData.Configuration(),
Vendors = ["SOFT"],
Groups =
{
{
"VocalMorpherPhoneme",
new MixKhronosData.EnumGroup(
+ "VocalMorpherPhoneme",
"VocalMorpherPhoneme",
"uint",
[],
@@ -385,11 +377,11 @@ public void OverzealousNameTrimmingFixupIsNotOverzealousForOpenAL()
},
},
};
- var names = new Dictionary?)>
+ var names = new Dictionary
{
- { "AL_VOCAL_MORPHER_PHONEME_A", ("AL_VOCAL_MORPHER_PHONEME_A", null) },
- { "AL_VOCAL_MORPHER_PHONEME_E", ("AL_VOCAL_MORPHER_PHONEME_E", null) },
- { "AL_VOCAL_MORPHER_PHONEME_I", ("AL_VOCAL_MORPHER_PHONEME_I", null) },
+ { "AL_VOCAL_MORPHER_PHONEME_A", new CandidateNames("AL_VOCAL_MORPHER_PHONEME_A", []) },
+ { "AL_VOCAL_MORPHER_PHONEME_E", new CandidateNames("AL_VOCAL_MORPHER_PHONEME_E", []) },
+ { "AL_VOCAL_MORPHER_PHONEME_I", new CandidateNames("AL_VOCAL_MORPHER_PHONEME_I", []) },
};
var ctx = new NameTrimmerContext
{
@@ -400,8 +392,8 @@ public void OverzealousNameTrimmingFixupIsNotOverzealousForOpenAL()
};
baseTrimmer.Trim(ctx);
uut.Trim(ctx);
- Assert.That(names["AL_VOCAL_MORPHER_PHONEME_A"].Item1, Is.EqualTo("A"));
- Assert.That(names["AL_VOCAL_MORPHER_PHONEME_E"].Item1, Is.EqualTo("E"));
- Assert.That(names["AL_VOCAL_MORPHER_PHONEME_I"].Item1, Is.EqualTo("I"));
+ Assert.That(names["AL_VOCAL_MORPHER_PHONEME_A"].Primary, Is.EqualTo("A"));
+ Assert.That(names["AL_VOCAL_MORPHER_PHONEME_E"].Primary, Is.EqualTo("E"));
+ Assert.That(names["AL_VOCAL_MORPHER_PHONEME_I"].Primary, Is.EqualTo("I"));
}
}
diff --git a/tests/SilkTouch/SilkTouch/Naming/NameTests.cs b/tests/SilkTouch/SilkTouch/Naming/NameTests.cs
index e8644c1875..544b0406c7 100644
--- a/tests/SilkTouch/SilkTouch/Naming/NameTests.cs
+++ b/tests/SilkTouch/SilkTouch/Naming/NameTests.cs
@@ -12,17 +12,17 @@ public class NameTests : NameTrimmer
[Test, TestCase(null), TestCase("glfw")]
public void SimpleGlfwTestDetermination(string? hint)
{
- var test = new Dictionary?)>
+ var test = new Dictionary
{
- { "GLFWallocator", ("GLFWallocator", null) },
- { "GLFWgammaramp", ("GLFWgammaramp", null) },
- { "GLFWgamepadstate", ("GLFWgamepadstate", null) },
- { "GLFWvidmode", ("GLFWvidmode", null) },
- { "GLFWimage", ("GLFWimage", null) },
- { "Glfw", ("Glfw", null) },
- { "GLFWcursor", ("GLFWcursor", null) },
- { "GLFWmonitor", ("GLFWmonitor", null) },
- { "GLFWwindow", ("GLFWwindow", null) },
+ { "GLFWallocator", new CandidateNames("GLFWallocator", []) },
+ { "GLFWgammaramp", new CandidateNames("GLFWgammaramp", []) },
+ { "GLFWgamepadstate", new CandidateNames("GLFWgamepadstate", []) },
+ { "GLFWvidmode", new CandidateNames("GLFWvidmode", []) },
+ { "GLFWimage", new CandidateNames("GLFWimage", []) },
+ { "Glfw", new CandidateNames("Glfw", []) },
+ { "GLFWcursor", new CandidateNames("GLFWcursor", []) },
+ { "GLFWmonitor", new CandidateNames("GLFWmonitor", []) },
+ { "GLFWwindow", new CandidateNames("GLFWwindow", []) },
};
Assert.That(
GetPrefix(null, hint, test, null, null, false, true)?.Prefix,
@@ -53,22 +53,19 @@ public void SimpleGlfwTestDetermination(string? hint)
};
foreach (var (key, (trimmed, _)) in test)
{
- Assert.That(
- trimmed.Prettify(new NameUtils.NameTransformer(4)),
- Is.EqualTo(expected[key])
- );
+ Assert.That(trimmed.Prettify(new NameUtils.NameTransformer(4)), Is.EqualTo(expected[key]));
}
}
[Test]
public void RegressionFragmentShaderColorModMaskATI()
{
- var test = new Dictionary?)>
+ var test = new Dictionary
{
- { "GL_2X_BIT_ATI", ("GL_2X_BIT_ATI", null) },
- { "GL_COMP_BIT_ATI", ("GL_COMP_BIT_ATI", null) },
- { "GL_NEGATE_BIT_ATI", ("GL_NEGATE_BIT_ATI", null) },
- { "GL_BIAS_BIT_ATI", ("GL_BIAS_BIT_ATI", null) },
+ { "GL_2X_BIT_ATI", new CandidateNames("GL_2X_BIT_ATI", []) },
+ { "GL_COMP_BIT_ATI", new CandidateNames("GL_COMP_BIT_ATI", []) },
+ { "GL_NEGATE_BIT_ATI", new CandidateNames("GL_NEGATE_BIT_ATI", []) },
+ { "GL_BIAS_BIT_ATI", new CandidateNames("GL_BIAS_BIT_ATI", []) },
};
Trim(
new NameTrimmerContext
@@ -88,20 +85,17 @@ public void RegressionFragmentShaderColorModMaskATI()
};
foreach (var (key, (trimmed, _)) in test)
{
- Assert.That(
- trimmed.Prettify(new NameUtils.NameTransformer(4)),
- Is.EqualTo(expected[key])
- );
+ Assert.That(trimmed.Prettify(new NameUtils.NameTransformer(4)), Is.EqualTo(expected[key]));
}
}
[Test]
public void RegressionEvalTargetNV()
{
- var test = new Dictionary?)>
+ var test = new Dictionary
{
- { "GL_EVAL_2D_NV", ("GL_EVAL_2D_NV", null) },
- { "GL_EVAL_TRIANGULAR_2D_NV", ("GL_EVAL_TRIANGULAR_2D_NV", null) },
+ { "GL_EVAL_2D_NV", new CandidateNames("GL_EVAL_2D_NV", []) },
+ { "GL_EVAL_TRIANGULAR_2D_NV", new CandidateNames("GL_EVAL_TRIANGULAR_2D_NV", []) },
};
Trim(
new NameTrimmerContext
@@ -119,10 +113,7 @@ public void RegressionEvalTargetNV()
};
foreach (var (key, (trimmed, _)) in test)
{
- Assert.That(
- trimmed.Prettify(new NameUtils.NameTransformer(4)),
- Is.EqualTo(expected[key])
- );
+ Assert.That(trimmed.Prettify(new NameUtils.NameTransformer(4)), Is.EqualTo(expected[key]));
}
}
@@ -136,9 +127,9 @@ public void RegressionNamespacePrefixDetermination() =>
[Test]
public void RegressionSingleMemberEnumUsesGlobalPrefixHint()
{
- var names = new Dictionary?)>
+ var names = new Dictionary
{
- { "GL_FILL_NV", ("GL_FILL_NV", null) },
+ { "GL_FILL_NV", new CandidateNames("GL_FILL_NV", []) },
};
var ctx = new NameTrimmerContext
{
@@ -149,15 +140,15 @@ public void RegressionSingleMemberEnumUsesGlobalPrefixHint()
};
var uut = new NameTrimmer();
uut.Trim(ctx);
- Assert.That(names["GL_FILL_NV"].Item1, Is.EqualTo("FILL_NV"));
+ Assert.That(names["GL_FILL_NV"].Primary, Is.EqualTo("FILL_NV"));
}
[Test]
public void MultipleGlobalPrefixHints()
{
- var names = new Dictionary?)>
+ var names = new Dictionary
{
- { "ALC_CONTEXT_DEBUG_BIT_EXT", ("ALC_CONTEXT_DEBUG_BIT_EXT", null) },
+ { "ALC_CONTEXT_DEBUG_BIT_EXT", new CandidateNames("ALC_CONTEXT_DEBUG_BIT_EXT", []) },
};
var ctx = new NameTrimmerContext
{
@@ -168,6 +159,6 @@ public void MultipleGlobalPrefixHints()
};
var uut = new NameTrimmer();
uut.Trim(ctx);
- Assert.That(names["ALC_CONTEXT_DEBUG_BIT_EXT"].Item1, Is.EqualTo("CONTEXT_DEBUG_BIT_EXT"));
+ Assert.That(names["ALC_CONTEXT_DEBUG_BIT_EXT"].Primary, Is.EqualTo("CONTEXT_DEBUG_BIT_EXT"));
}
}