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")); } }