diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 63425757a9..5ff74b19d5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,8 +10,10 @@ Please read [the Contributions Guide](CONTRIBUTING.md) before writing release no Silk.NET 3.0 Preview 1 -- Added SDL3 bindings using the 3.0 bindings style. - Added combined OpenGL bindings using the 3.0 bindings style. +- Added OpenAL bindings using the 3.0 bindings style. +- Added SDL3 bindings using the 3.0 bindings style. +- Added Vulkan bindings using the 3.0 bindings style. - Improved the bindings style to be more accessible, IDE-friendly, and AOT-friendly. Learn more at **TODO ADD A LINK TO DOCUMENTATION HERE**. - Removed SDL2 bindings. diff --git a/docs/for-contributors/Generator/api-specific-notes.md b/docs/for-contributors/Generator/api-specific-notes.md new file mode 100644 index 0000000000..3d3c4175a4 --- /dev/null +++ b/docs/for-contributors/Generator/api-specific-notes.md @@ -0,0 +1,38 @@ +# API-Specific Notes + +This document's purpose is to note down any decisions or quirks that are specific to a library that Silk is generating +bindings for. + +This is meant to be a living document. Please update this as new work is being done on the generator. + +## OpenAL + +Currently empty. + +## OpenGL + +Currently empty. + +## SDL + +Currently empty. + +## Vulkan + +There will be the following errors in the generation log. This is expected. These types are part of the XML +specification, but not part of the main `vulkan.h` header. + +For more information: https://github.com/dotnet/Silk.NET/pull/2457#issuecomment-2910293716 + +``` +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkFullScreenExclusiveEXT" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkFaultLevel" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkFaultType" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkFaultQueryBehavior" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkPipelineMatchControl" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkSciSyncClientTypeNV" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkSciSyncPrimitiveTypeNV" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkPipelineCacheValidationVersion" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkDisplacementMicromapFormatNV" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +fail: Silk.NET.SilkTouch.Mods.MixKhronosData[0] Enum "VkCompressedTriangleFormatAMDX" has no base type. Please add TypeMap entry to the configuration. This enum group will be skipped. +``` diff --git a/docs/for-contributors/Generator/generator-mods.md b/docs/for-contributors/Generator/generator-mods.md new file mode 100644 index 0000000000..216bd080a2 --- /dev/null +++ b/docs/for-contributors/Generator/generator-mods.md @@ -0,0 +1,710 @@ +# Generator Mods + +Silk's SilkTouch bindings generator is designed to be a linear pipeline where a set of mods sequentially transform C# +source code represented by Roslyn syntax nodes. This approach used by Silk 3 is in contrast to the approach in Silk 2, +where a monolithic generator output code represented by bespoke data structures. Silk 3 focuses on breaking down each +transformation step into its own mod to aid in maintainability and understanding of the codebase. + +This document is intended to act as both documentation on how to use the mods as well notes on how the mods were +implemented. For users of the generator, first read the [Using the Generator](using-the-generator.md) documentation, +then use this document to understand how each individual mod works. + +## Implementation + +### IMod Interface + +SilkTouch mods implement the `IMod` interface, which contains the `InitializeAsync` and `ExecuteAsync` methods. + +The primary work of a mod is done within the `ExecuteAsync` method. This method takes in a +`Microsoft.CodeAnalysis.Project` containing the C# source code representing the current state of the bindings. This is +the primary input *and* output of each mod. The output of each mod is passed directly into the next mod for further +transformation. + +`InitializeAsync` is rarely used and is used to initialize data before any transformations have begun so that other mods +can access that data before their own `ExecuteAsync` method runs. The usage of this method is made even rarer since most +communication between mods should be done through the C# source code representing the generated bindings instead, or +ideally, no communication is done at all so that mods are kept completely standalone. + +### Mod Configuration + +Mods are configured through the generator config JSON file. Silk's config file is named `generator.json` and is located +at the root of the Silk.NET repository. This config file can be used as reference for your own config files. In +addition, most mods have a configuration class located in their source code with additional documentation. + +## Available Mods + +This section provides a high level explanation of what each mod does. The list is sorted in alphabetical order. + +In particular, config options and specific implementation details are omitted here as the source code documentation +provides the information in a more clear format with less chance of being outdated. However, high level design decisions +will be documented here. + +Standardized sections: + +- **Mod categories** - Assigns a category to the mod and is purely for documentation purposes. Alphabetically sorted. + This allows for easy searching for related mods. The category is also used to provide recommendations and information + relating to those categories. + +- **Name affix categories** - Lists the name affix categories that the mod adds. Alphabetically sorted. Please refer + to the [Name Processing](name-processing.md) documentation to understand what these are and how to make use of them. + That said, advice for how to handle each category is also provided alongside information about what the name affix + represents. + +- **Usage recommendations** - Provides information such as situations the mod is useful for, how to configure it, and + where to place it in the mod order. This information can include examples of how Silk's own bindings use it or whether + the mod is mainly designed for Silk internal use. + +General recommendations: + +For the most part, mods should be configured to run in an order similar to the mod orders used by Silk's existing +bindings. The same goes for the configuration, but more care needs to be done regarding whether the configuration is +specific to that set of bindings. + +### AddApiProfiles + +Mod categories: Metadata + +This mod adds `[SupportedApiProfile]` attributes throughout the generated bindings for the purpose of providing API +analyzers the ability to understand when a specific API can be used. + +This mod is WIP: + +- Ideally, the mod internally uses `[NativeName]` attributes to associate data with the API exposed by the bindings. + Currently, the managed C# names are used, meaning that name prettification and other name modifications can lead to + inaccurate `[SupportedApiProfile]` attributes. + +Usage recommendations: + +This mod should be positioned late in the mod order, after all APIs have been added to the generated bindings. +If the attribute is missing on a certain API and a later mod adds that API, investigate whether this mod can be moved +to be after that mod. + +### AddIncludes + +Mod categories: Creation + +This mod interacts with `ClangScraper` by providing standard include directories and other user-specified include paths +to `ClangScraper`. + +Usage recommendations: + +This should be positioned at the start of the mod order. + +### AddOpaqueStructs + +Mod categories: Creation + +This mod adds an empty struct for each name specified in its mod configuration. + +Usage recommendations: + +(TODO: To be added) + +### AddVTables + +Mod categories: Transformation + +This mod transforms `[DllImport]` and `[Transformed]` methods to use Silk-style virtual tables. These vtables allow for +different styles of accessing native APIs, such as through an instance of an API object or through static methods. + +Stateless APIs are typically accessed through static methods, while stateful APIs are typically accessed through API +objects. More information on this can be found in the +[Static vs Instance Bindings](../../silk.net/static-vs-instance-bindings.md) document. + +Usage recommendations: + +(TODO: To be added) + +### BakeSourceSets + +Mod categories: Transformation + +This mod merges multiple sets of source code into one set of source code. + +Usage recommendations: + +(TODO: To be added) + +### ChangeNamespace + +Mod categories: Transformation + +This mod moves types from one namespace to another. + +Usage recommendations: + +(TODO: To be added) + +### ChangeNativeClass + +Mod categories: Transformation + +This mod moves members from one type to another. + +Usage recommendations: + +(TODO: To be added) + +### ClangScraper + +Mod categories: Creation + +This is a critical mod used to generate the initial set of raw C# bindings for C APIs. The use of this mod is equivalent +to using ClangSharpPInvokeGenerator. The C# source code generated by this mod is typically the starting point for +transformations done by the rest of the SilkTouch mods. + +Note that this mod is platform specific and will have different outputs depending on the platform. This is because +ClangSharpPInvokeGenerator, and by extension, `ClangScraper` makes use of system headers. Any platform specific +differences in the API for which bindings are being generated will also affect the output. + +Platform-specific differences: + +For the most part, these behaviors are expected, but potentially unwanted behaviors considering the goal of Silk is to +provide cross-platform bindings. + +- `uint64_t` in C becomes `ulong` on Windows, `nuint` on Linux. This is filed as an issue in the ClangSharp repo: + https://github.com/dotnet/ClangSharp/issues/574. Silk handles this by remapping the `stdint.h` types in + [remap-stdint.rsp](https://github.com/dotnet/Silk.NET/blob/develop/3.0/eng/silktouch/remap-stdint.rsp). APIs, such as + OpenGL, that define their own integer types may require additional configuration. + +- Enums use `uint` as their backing type on Linux instead of `int` like on Windows. Silk handles this by using + `TransformEnums` to "coerce" the backing types to their Windows equivalents when possible. + +Note: There may be other differences not yet documented here. In the case new differences are discovered, please +update this section. API-specific differences should not be documented here and should be documented in the +[API-Specific Notes](api-specific-notes.md) document. + +To further avoid platform-specific differences, Silk prefers to generate its bindings on Windows. However, for sake of +development and iteration, the generator is typically set up so that platform-specific differences are minimalized. + +Usage recommendations: + +This mod is configured differently from the rest of the mods. The JSON mod configuration follows the same pattern as the +rest of the mods, but the bulk of the configuration comes in the form of `.rsp` files. These `.rsp` files represent the +command line arguments passed to ClangSharpPInvokeGenerator. More information about how to use this mod and how to +configure the generator as a whole is found in the [Using the Generator](using-the-generator.md) documentation. + +This mod should be positioned at the start of the mod order, after `AddIncludes`. + +### ExtractHandles + +Mod categories: Creation + +This mod adds empty structs for missing types that are identified to be used as handle types. To be a handle type, the +type must only be ever referenced through a pointer. After the empty struct representing the handle type is extracted, +`TransformHandles` can then be used to transform the pointer to be wrapped within the handle struct. + +Note: This mod is similar to `AddOpaqueStructs` in that it adds empty structs, but `ExtractHandles` has a much more +automated approach since it deals specifically with handle types that are referenced using pointers. + +This code has been manually trimmed for the sake of example and comes from the state of the Vulkan bindings before +`ExtractHandles` executes. In this case, `VkInstance_T` will be identified as a missing handle type and an empty struct +will be added for it. On the other hand, `VkInstanceCreateInfo` and `VkAllocationCallbacks` will not be affected since +they already exist. Similarly, `VkResult` is not affected because it is not referenced through a pointer. This example +has a matching test case in the SilkTouch unit tests. +```cs +public struct VkAllocationCallbacks; +public struct VkInstanceCreateInfo; + +public class Vk +{ + public static extern VkResult vkCreateInstance( + VkInstanceCreateInfo* pCreateInfo, + VkAllocationCallbacks* pAllocator, + VkInstance_T** pInstance + ); +} +``` + +The result of running `ExtractHandles` on the above code will lead to the creation of a new type: +```cs +public unsafe partial struct VkInstance_T +{ +} +``` + +Usage recommendations: + +This mod should be used if a set of bindings contains types referenced only through pointers and those pointers are +missing from the final set of generated bindings. + +Furthermore, this mod should be used alongside `TransformHandles` so that the handles are transformed into a more +user-friendly version. `ExtractHandles` should be positioned before `TransformHandles` and any other mods that might use +its results in the mod order. + +### ExtractNestedTyping + +Mod categories: Creation + +This mod handles a few responsibilities, the primary of which is to extract nested types out of their parent types and +into the containing namespace as non-nested types. The second is replacing function pointers with structs and delegate +types representing those function pointers. The third is moving C-style enum constants into their respective enums. + +Arguably, the non-primary responsibilities of this mod should be split out into separate mods, but has not yet been done +so due to lack of time invested into doing so. There is also the slight performance hit of splitting out the operations +out since it will lead to three separate passes over the codebase, but this cost is negligible for most reasonably sized +native APIs. + +Examples for how `ExtractNestedTyping` works can be found in the `ExtractNestedTypingTests` test cases. + +Name affix categories: + +- `FunctionPointerDelegateType` - This is a suffix that always has the value of `Delegate`. This is used for the + delegate representation of a function pointer type to distinguish the delegate type from the struct type for extracted + function pointers. + +- `FunctionPointerParent` - This is a prefix used by the delegate representation of a function pointer type. This is + used to ensure that the delegate type always uses the current name of its struct counterpart as part of its own name. + +- `NestedStructParent` - This is a prefix that references the name of the type that the extracted type was previously + nested in. This is used to ensure that the extracted type always uses the current name of its original "parent" type + as part of its own name. + +These affixes are usually left unconfigured in `PrettifyNames`. + +Usage recommendations: + +This mod must be used before `PrettifyNames` when using `PrettifyNames` and there are nested types in the generated +bindings. This is because `PrettifyNames` does not handle nesting when renaming identifiers. Other mods may have similar +restrictions. This restriction is generally because nesting increases complexity, and as such, mods are written with the +assumption that nested types are extracted beforehand. + +### IdentifySharedPrefixes + +Mod categories: Metadata, Naming + +This mod is designed to handle C-style namespace prefixes where all types, functions, and constants in a native API +share a common prefix. This includes casing convention differences. For example, constants often use screaming case +while type and function names use camel case or pascal case. This can be seen in Vulkan, where functions are prefixed +with `vk`, such as in `vkCreateInstance` and `vkCmdBindPipeline`, and constants are prefixed with `VK_`, such as in +`VK_MAX_MEMORY_HEAPS` and `VK_TRUE`. + +Furthermore, identification of shared prefixes is done per scope and only in cases where C-style namespace prefixes +might be used. For example, a struct type typically does not use namespace prefixes because the struct itself acts as +a way to disambiguate names contained inside of it. However, C-style enum values are often defined as global constants +using macros such as `#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u`. After these constants are moved to their +corresponding enum types by `ExtractNestedTyping`, `IdentifySharedPrefixes` then handles the identification of the +prefix shared by the enum type's members. In the case of `SDL_BlendMode`, all of the members of `SDL_BlendMode` share +`SDL_BLENDMODE_` as their common prefix. + +Note: Despite `VK_` and `SDL_BLENDMODE_` being the "true" shared prefix, `IdentifySharedPrefixes` annotates the +identifier with `VK` and `SDL_BLENDMODE` as the shared prefix, without the trailing underscore. While the inclusion of +the underscore can be debated and can subtly affect the bindings output by the generator in edge cases, this is the +current behavior of `IdentifySharedPrefixes`. + +Implementation-wise, this mod's functionality was notably originally part of `PrettifyNames`. In the original form, +`PrettifyNames` handled both the identification of and removal of shared prefixes. This has now been split out to +simplify `PrettifyNames` and to provide better control over how shared prefixes are processed. + +Examples for how `IdentifySharedPrefixes` works can be found in the `IdentifySharedPrefixesTests` test cases. + +The [Name Processing](name-processing.md) documentation also covers `IdentifySharedPrefixes` to a limited extent, +notably relating to how existing name affixes are treated in the +[IdentifySharedPrefixes and Name Affixes](name-processing.md#identifysharedprefixes-and-name-affixes) section. + +Name affix categories: + +- `SharedPrefix` - This is a prefix that represents the shared prefix that is identified for a group of names. Silk's + bindings typically remove this prefix because these prefixes are typically used to prevent naming collisions in C + libraries. C# has its own namespacing functionality, thus making this prefix irrelevant. This prefix can be kept or + prettified if preferred over removing the prefix. The prefix can also be configured as a discriminator, which removes + the prefix by default, but allows the prefix to be used in case of name conflicts. + +Example name affix configurations for `PrettifyNames`: + +``` +"SharedPrefix": { + "Remove": true +} +``` + +``` +"SharedPrefix": { + "IsDiscriminator": true +} +``` + +Usage recommendations: + +This mod should be used when the transformation of C-style namespace prefixes or similar naming patterns is desired. +The most common case of this is the removal of such prefixes by using `IdentifySharedPrefixes` before `PrettifyNames`. +`PrettifyNames` can then be configured like above to remove or use shared prefixes as discriminators. + +This mod also interacts with `ExtractNestedTyping`, which moves constants that are identified as likely being part of +an enum to the corresponding enum. As such, this `IdentifySharedPrefixes` should be positioned after +`ExtractNestedTyping` in the mod order. + +### InterceptNativeFunctions + +Mod categories: Transformation + +This mod intercepts native functions and allows the generator user to provide a manual implementation in a non-generated +partial file. + +This is done by identifying native functions by name and by their `[DllImport]` attribute. If the native function's name +is one of the functions to intercept, the original method is replaced with two new method: + +1. A `private` version of the original that is suffixed with `-Internal`. +2. A `public` version with no method body, but using the `partial` keyword. + +This second `partial` method is what allows generator users to provide their own implementation. Similar to overriding +`virtual` methods, the generator user is free to do anything within this implementation, but common use cases involve +wrapping the method before calling the original `-Internal` suffixed version of the method. + +Examples for how `InterceptNativeFunctions` works can be found in the `InterceptNativeFunctionsTests` test cases. + +Name affix categories: + +- `InterceptedFunction` - This is a suffix that always has the value of `Internal`. This is used for the original + version of the private, intercepted native function to distinguish it from the new, public version. + +Usage recommendations: + +Use this when there is a strong reason to directly replace the original native function rather than create a custom +overload or utility method. + +For example, this is used in the Vulkan bindings to capture the created `Instance` and `Device` objects to be used in +native function loading. Specifically, `vkCreateInstance` and `vkCreateDevice` are intercepted so that the created +objects can be passed to `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` respectively. In this case, the reason for +intercepting these native functions is so that function pointers can be automatically loaded without user intervention. + +### MarkNativeNames + +Mod categories: Metadata, Naming + +This mod naively adds `[NativeName]` attributes to most identifiers in the generated bindings and is designed to be +placed immediately after `ClangScraper`. Syntax nodes that are not output by `ClangScraper` are intentionally not +processed. + +The name stored by the `[NativeName]` attribute matches the C# identifier at the time the mod runs. This assumes that +the name used by the C# source code matches the name used by the native source code, which is *usually* the case when +this mod is placed immediately after `ClangScraper`. However, there are cases where the names output by `ClangScraper` +do not correspond to native names. These cases are usually because there is no native name available, such as for +inline array types (named in the format `_name_e__FixedBuffer`) or backing fields used by bitfield structs (named +`_bitfield`). + +While this mod is intended to mark identifiers generated by `ClangScraper`, this mod is not intended to mark all +identifiers present in the final set of bindings. When other mods introduce new identifiers that represent a native API, +those mods will need to add `[NativeName]` attributes themselves or have another mod do it for them. + +Usage recommendations: + +As mentioned, this mod is best used immediately after `ClangScraper` runs. This should mark all identifiers output +by `ClangScraper` itself. Other mods that add new APIs should add the `[NativeName]` attributes themselves. + +Because the name stored as the value for `[NativeName]` comes from the native API, it can be used as a stable identifier +for an API. This is in contrast to the current C# identifier being used, as that identifier is often transformed by mods +such as `[PrettifyNames]`. + +### MixKhronosData + +Mod categories: Creation, Metadata, Naming, Transformation + +This is a monolithic mod that handles behavior specific to Khronos-style APIs such as OpenGL, OpenAL, Vulkan, and more. + +Because this mod is intended more for internal use rather than public use, the documentation here will focus on +decisions made during the development of the mod and other internal details rather than the exact usage of the mod. +For information relating to how the mod should be used, please use Silk's `generator.json` configuration, source code, +and `MixKhronosDataTests` as a reference. + +(TODO: Document major decisions relating to `MixKhronosData`. This is difficult because of the mod's long development +history, and while this section is already massive, it still does not cover everything the mod does. This should be done +over time as further changes are made to the mod.) + +To combat the monolithic nature of the mod, the mod is split into multiple phases. This refers to both the +`InitializeAsync` and `ExecuteAsync` phases, as well as the use of multiple rewriters. The mod also implements +multiple interfaces that integrate `MixKhronosData` into the behavior of other mods. + +`InitializeAsync` is where `MixKhronosData` initializes its data by reading the Khronos-style XML specification file +containing data relating to the API that the generator is generating bindings for. A list of such specifications is +provided below. These XML specs roughly follow the same format, but have subtle or major differences depending on the +history of that API. For example, OpenGL is similar to OpenAL, but differs greatly from Vulkan and OpenXR who are +themselves similar. As such, it is best to have all of the specification files open for reference when working on +parsing. It may also be helpful to have the corresponding header files open. + +`ExecuteAsync` is where `MixKhronosData` does multiple sequential transformation steps on the source code representing +the generated bindings. These steps are split into different rewriter phases in a way that focuses on balancing +performance with maintainability. Performance-wise, these rewriters should be combined as much as possible. This is +because repeated loops over the project source code has an associated time cost. However, for sake of maintainability, +it is much easier to understand how transformations are done when they are separated out into different phases. +Fortunately, as long as the transformations do not use the symbol representation (eg: `ISymbol`, `SemanticModel`) of +the source code, the transformation is fairly lightweight and only adds a few seconds to the total execution time of +the mod. + +Khronos-style XML specifications: + +- OpenAL: https://raw.githubusercontent.com/kcat/openal-soft/refs/heads/master/registry/xml/al.xml +- OpenCL: https://raw.githubusercontent.com/KhronosGroup/OpenCL-Docs/refs/heads/main/xml/cl.xml +- OpenGL: https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/refs/heads/main/xml/gl.xml +- OpenGL Windows: https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/main/xml/wgl.xml +- OpenGL X11: https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/glx.xml +- OpenXR: https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/main/specification/registry/xr.xml +- Vulkan: https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/refs/heads/main/xml/vk.xml + +Be aware that these link to the latest version. Silk's repo may be using an older version of these XML files. + +Name affix categories: + +- `KhronosFunctionDataType` - `IdentifyFunctionDataTypes` must be set to true in the `MixKhronosData` configuration for + this affix category to be identified. This is a suffix relevant to OpenGL-like APIs where functions like `glColor3` + have variants such as `glColor3i`, `glColor3f`, and `glColor3b`. These suffixes indicate the data type that the + function expects. In this case, integer, float, and byte, respectively. Silk's bindings configure these as + discriminators so that they can be removed when removing them does not lead to method overload conflicts. + +- `KhronosHandleType` - This is a suffix used on handle structs resulting from the typedefs used by Khronos in their + headers. For example, Vulkan uses the following macro to define handle types: + `#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;`, used as `VK_DEFINE_HANDLE(VkInstance)`. + Although Vulkan uses the handle type as `VkInstance`, `ClangScraper` outputs the type as `VkInstance_T` due to the + typedef. As such, `MixKhronosData` identifies this suffix so that `PrettifyNames` can be configured to remove this + suffix later. + +- `KhronosImpliedVendor` - `IdentifyEnumMemberImpliedVendors` must be set to true in the `MixKhronosData` configuration + for this affix category to be identified. This is a suffix used on enum members instead of `KhronosVendor` when an + enum member has the same vendor suffix as **the containing enum type. This suffix exists in native code because the + enum member is usually defined as a standalone, global constant without any other context whether the enum member is + part of an extension. In C#, this is not a problem because the enum type itself conveys that information. For example, + in Vulkan, `VkPresentModeKHR` in Vulkan is a `KHR` suffixed enum type that contains `VK_PRESENT_MODE_IMMEDIATE_KHR` as + a member. In C#, this `KHR` suffix on the member is redundant. As such, Silk's bindings are configured to remove this + suffix. + +- `KhronosNamespaceEnum` - This is a prefix added to the "namespace" enum of OpenGL-like APIs such as `GLEnum`, + `ALEnum`, and `ALCEnum`. In this case, the value of the prefix would be `GL`, `AL`, and `ALC`, respectively. This is + so that the casing of the prefix is preserved by `PrettifyNames`. As such, Silk's bindings leaves the affix category + unconfigured in the `PrettifyNames` configuration so that `PrettifyNames` uses the default behavior of preserving the + affix. + +- `KhronosNonExclusiveVendor` - `IdentifyEnumTypeNonExclusiveVendors` must be set to true in the `MixKhronosData` + configuration for this affix category to be identified. This is a suffix used on enum types instead of `KhronosVendor` + when the enum type's vendor suffix does not match the vendor suffixes used by the enum members contained within that + type. For example, `BufferUsageARB` has the `ARB` vendor suffix, but contains non-suffixed members such as + `GL_STREAM_DRAW`. Similarly, `GetMultisamplePNameNV` contains `ProgrammableSampleLocationARB`, which is also a + mismatch. + + This affix category is only intended to be used for OpenGL-like APIs where enum member promotion was not + fully defined, leading to inconsistent vendor suffixing where a non-promoted enum type contains a promoted enum + member. Modern APIs like Vulkan do not have this issue. In modern APIs, there can be "mismatches", but those are cases + where promoted enum types contain non-promoted enum members, which is allowed. As such, Silk's bindings enables + `IdentifyEnumTypeNonExclusiveVendors` and configures `KhronosNonExclusiveVendor` affixes to be removed only for + OpenGL-like APIs. + + Furthermore, `IdentifyEnumTypeNonExclusiveVendors` also interacts with + `IdentifyEnumMemberImpliedVendors`. Specifically, if an enum type is identified to have a non-exclusive vendor, that + vendor will not be used to identify implied vendors, as it is assumed that the non-exclusive vendor will be removed. + Also note that the behavior of `IdentifyEnumTypeNonExclusiveVendors` can be considered "too aggressive" since it + triggers off of *any* mismatch. For example, if a vendor suffixed enum type contains something generic such as + `GL_NONE`, the enum type vendor suffix will still be identified as a `KhronosNonExclusiveVendor`. This behavior was + ported from the now removed `NameTrimmer`-based implementation and is kept for simplicity and consistency with the old + implementation. + +- `KhronosNonVendor` - This is a suffix added to any identifier that is identified to contain a suffix listed in the + `NonVendorSuffixes` list of the `MixKhronosData` configuration. This is used in cases when a suffix might block the + identification of other suffixes. For example, OpenAL has names such as `alAuxiliaryEffectSlotfDirect` where the + `Direct` suffix is after the `KhronosFunctionDataType` suffix, thus blocking the `KhronosFunctionDataType` suffix + from being identified. In this case, adding `Direct` as a non-vendor suffix fixes the issue. Because this is a + "helper" affix, Silk's bindings leave this affix category unconfigured in the `PrettifyNames` configuration. + +- `KhronosVendor` - This is a suffix added to any identifier that is identified to contain a Khronos vendor suffix such + as `KHR`, `EXT`, or `NV`. The list of vendor suffixes used during identification is retrieved from the provided XML + specification. Silk's bindings are configured to move `KhronosVendor` suffixes to the end of the name. This is to + match Khronos's own naming convention. This primarily affects cases where Silk's generator added additional suffixes + to the end of the name, such as with `HandleType` suffixes like in `DebugUtilsMessengerHandleEXT` in Vulkan. + +Usage recommendations: + +This mod should only be used when generating bindings for Khronos-style APIs. While the mod does not strictly require +the XML specification file, `MixKhronosData` has not been tested for use without the XML specification. + +One key consideration when using `MixKhronosData` is identifying the conventions used by the API for which bindings are +being generated for. Older Khronos APIs are more similar to OpenGL while newer Khronos APIs are more similar to Vulkan. +These conventions determine which settings should be used in the `MixKhronosData` configuration. For exact configuration +details, please refer to the configurations used by Silk for the existing Khronos-style bindings as well as the source +code. + +Note: Although `MixKhronosData` has not been used by Silk for generating bindings for APIs that do not have XML +specifications, we will likely experiment with using this mod for bindings that do not have XML specifications such as +SPIRV-Reflect in the near future. Whether we end up using this mod for these types of bindings depends on how much +benefit `MixKhronosData` provides for those bindings. + +### PrettifyNames + +Mod categories: Naming, Transformation + +This is the mod central to name processing. `PrettifyNames` focuses on the bulk prettification of names and the +processing of name affixes declared by other mods. + +Name processing as a whole and information about the high level implementation details of `PrettifyNames` is available +in the [Name Processing](name-processing.md) documentation. + +Usage recommendations: + +This mod should be positioned late in the mod order to ensure that all mods that introduce new identifiers and +`[NameAffix]` attributes have run. + +The placement of this mod can also affect mods that rely on the C# identifiers of types and their members rather than +the names specified by that identifier's `[NativeName]` attribute. One notable mod of this type is `TransformEnums`. + +When configuring how name affixes are processed in the `PrettifyNames` configuration, note that the name affix category +configuration is designed to be verbose on purpose. If a category is left unconfigured, it will simply use the default +configuration. Mods cannot provide default affix category configurations. This is to ensure that what you see in the +`PrettifyNames` configuration directly corresponds to the output. + +Furthermore, when defining values for the `Order` and `DiscriminatorPriority` properties, prefer to use consecutive +values since there is no need to reserve space for intermediate values when new name affix categories are introduced. +This is because it is easy enough to update the other entries to use a higher/lower value for the set of bindings being +configured. New name affixes are also very unlikely to be introduced after the set of bindings have been created. + +### StripAttributes + +Mod categories: Metadata + +This mod removes attributes that are listed in the `Remove` list of its config. + +Usage recommendations: + +This mod is intended to be used as a way to clean up intermediate metadata attributes and other attributes usually +useful during bindings generation or debugging, but not particularly useful to the end user of the generated bindings. + +These are attributes removed in Silk's own bindings: + +- `NameAffix` - Metadata attribute used to store name affix information. Introduced by various mods. + +- `NativeTypeName` - Metadata attribute used to store native type information. Introduced by `ClangScraper`. + +- `Transformed` - Metadata attribute used to denote that an API is a transformed variant of another API. Introduced by + various mods. + +Tip: When debugging the name processing pipeline, disabling the `StripAttributes` mod (or just removing the +`[NameAffix]` attribute from the list of attributes to be removed) can be helpful. Disabling the stripping of other +attributes can also be helpful for this or other purposes. + +### TransformEnums + +Mod categories: Transformation + +This mod focuses on the transformation of enum types. + +Usage recommendations: + +This mod can be used to reduce the platform specific differences in the generated bindings. For example, enums use +signed backing types by default on Windows while enums default to unsigned backing types on Unix. This is controlled by +the `CoerceBackingTypes` configuration option. + +This mod can also be used to transform `[Flags]` enums in various ways. A `None = 0` member can be added for `[Flags]` +enums that do not have an equivalent. Member values can also be rewritten to use hexadecimal for `[Flags]` enums and +decimal values for normal enums. + +Finally, the last feature is that member can be according to a filter. This filter can filter the type name and the +member name by regex. The filter can also filter by member value. One common use case is for removing the "max value" +enum members used by native libraries to ensure that enum backing types are a specific width. For example, in Vulkan, +the value used is `0x7FFFFFFF`, equivalent to the maximum value of a 32-bit signed integer. + +Note: Currently, `TransformEnums` requires the `[Flags]` attribute on enum types to be added by another mod. +`TransformEnums` does not yet have the functionality to identify enums on its own. This is a relatively high priority +task. However, also note that identification will be done using a heuristic and may not be perfectly accurate. If other +metadata is available for identifying `[Flags]` enums, consider using that instead for better results. + +### TransformFunctions + +Mod categories: Transformation + +This mod focuses on the transformation of methods, such as by changing parameters types and adding new overloads. + +(TODO: To be expanded) + +Notably, the transformations include transforming methods to use the Silk DSL types (`Ptr`, `Ref`, `MaybeBool`, etc). + +Name affix categories: + +- `RawFunction` - This is a suffix added when method overloads conflict with each other. Specifically, if a transformed + version of a method differs from the original method only by return type, the *original* has the `-Raw` suffix added + along with the corresponding `[NameAffix]` attribute. + +Usage recommendations: + +(TODO: To be expanded) + +While not a requirement, the `BoolTypes` property in the configuration should match the configuration used in +`TransformProperties` for consistency. + +### TransformHandles + +Mod categories: Transformation + +This mod focuses on the transformation of opaque structs into more developer-friendly handle types. + +This is done by finding references to empty structs. If all references to the empty struct are done through pointers, +that struct will be treated as a handle type and transformed. + +Empty structs identified as handle types will be transformed in two ways: + +1. The struct itself will be transformed to wrap the underlying pointer and have methods/operators added for ease of + use. + +2. All references to that struct will have their pointer dimension reduced. For example, `VkBuffer**` becomes + `VkBuffer*` This is because the mentioned struct transformation means that the innermost pointer dimension is now + stored inside the struct. + +This mod currently only processes handle types that wrap pointer types. Integer types are not yet supported. + +Name affix categories: + +- `HandleType` - This is a suffix added to handle types transformed by `TransformedHandles`. Note that + `TransformHandles` only adds the attribute and does not rename the actual handle type. This is so that the rename is + deferred until `PrettifyNames` where all renames are done in bulk for performance reasons. This pattern is explained + in the [Name Processing - Deferring Renames](name-processing.md#deferring-renames) documentation. + +Usage recommendations: + +This mod should be placed after `ExtractHandles` and `AddOpaqueStructs`. This is because `TransformHandles` relies on +the previously mentioned mods to add the empty struct types. `TransformHandles` itself does not add new structs, it only +transforms existing ones that it identifies as a handle type. + +### TransformProperties + +Mod categories: Transformation + +This mod focuses on the transformation of fields and properties. Despite the name, fields are also handled because they +often need to be transformed alongside properties or have very similar transformations that it makes sense to colocate +these transformations in the same mod. + +This mod currently handles the following transformations: + +1. Transform string constant properties to use the `Utf8String` type. For example, + `static ReadOnlySpan Thing => "thing"u8;` becomes + `static Utf8String Thing => "thing"u8;`. + +2. Transform fields and properties identified to be boolean-like to use the `MaybeBool` type. This is similar to the + transformation done by `TransformFunctions`. + +Usage recommendations: + +This mod should be used when the above transformations are desired. Currently, there are no configuration options to +disable the string constant to `Utf8String` transformation, but we may add an option in the future. + +While not a requirement, the `BoolTypes` property in the configuration should match the configuration used in +`TransformFunctions` for consistency. + +## Mod Categories + +### Creation + +These mods focus on the creation of new APIs that strictly do not exist in any form in the current state of the +bindings. + +Generally, these mods should be early in the mod order so that other mods have the chance to modify their outputs. + +### Metadata + +These mods deal with metadata, either by annotating the generated bindings or by providing metadata to other mods. + +### Naming + +These mods deal with the naming of type and member identifiers within the generated bindings. + +### Transformation + +These mods focus on the transformation of existing APIs. While these mods can create new APIs, these new APIs are based +on APIs that already exist in the generated bindings. + +Generally, these mods should be placed after any mods (such as ones in the Creation category) that introduce any APIs +that might get transformed by these Transformation mods. diff --git a/docs/for-contributors/Generator/name-processing.md b/docs/for-contributors/Generator/name-processing.md new file mode 100644 index 0000000000..c5150d7bcb --- /dev/null +++ b/docs/for-contributors/Generator/name-processing.md @@ -0,0 +1,419 @@ +# Name Processing and Prettification + +A primary goal of Silk.NET is to provide a first-class .NET experience for the bindings that it provides. + +One such way that Silk.NET achieves this is by transforming native identifiers into identifiers that follow the +Microsoft Framework Design guidelines. This is the process referred to as "prettification". +Of these guidelines, most notable are the guidelines relating to capitalization. + +Naming Guidelines: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines + +Capitalization Conventions: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions + +## High-Level Overview + +This section explains how names flow through the SilkTouch generator pipeline. +- For more information about the pipeline itself, please see the [Generator Mods](generator-mods.md) documentation. + +`vkCreateSwapchainKHR` from Vulkan is used here as an example. + +1. Names enter the pipeline from native sources (eg: C header files). + - Eg: `vkCreateSwapchainKHR` as input from Vulkan during the `ClangScraper` mod. + +2. Mods add metadata to each name as C# attributes. + - `[NativeName("vkCreateSwapchainKHR")]` from `MarkNativeNames` + - `[NameAffix("Suffix", "KhronosVendor", "KHR")]` from `MixKhronosData` + - `[NameAffix("Prefix", "SharedPrefix", "vk")]` from `IdentifySharedPrefixes` + +3. `PrettifyNames` uses the metadata to transform the names according to user-provided configuration. + - The affixes are first stripped → `CreateSwapchain` + - The base name is "prettified" (pascal-casing, removal of underscores) → `CreateSwapchain` (No change in this case) + - Affixes are reapplied according to user configuration → `CreateSwapchainKHR` + - Silk's bindings remove shared prefixes since these represent C namespace prefixes and preserve Khronos vendor + suffixes verbatim for emphasis (notably in contradiction with the Framework Design Guidelines). + +4. Mods strip most metadata from the generated bindings to keep the output clean. + - Silk's bindings keep metadata useful for users, while removing internal generator metadata. + - For example, `[NativeName]` is kept and `[NameAffix]` is removed during the `StripAttributes` mod. + - Tip: Disabling the `StripAttributes` mod can be helpful for debugging unwanted outputs. + +## Test cases + +The behavior for the name processing pipeline is heavily unit tested. Please refer to the unit tests for the +corresponding section of the codebase to see detailed examples of expected inputs and outputs. + +## PrettifyNames + +As seen above, `PrettifyNames` is the mod central to name processing. + +The goal of this mod is to take all of the names from the generated bindings and transform them in bulk. This keeps +other mods performant and simple, as renaming identifiers is a costly operation that involves searching the entire +project for references to that identifier. + +Despite this, `PrettifyNames` also has the goal of remaining dumb and straightforward. It relies on the generator config +for API-specific decisions (eg: removing/reordering affixes, overrides) and other mods for API-specific annotations +(eg: API-specific prefix/suffix conventions). The rest of the processing (eg: prettification), while complex, is done +uniformly. + +This allows `PrettifyNames` to focus strictly on the common case, while edge cases are handled elsewhere. This works +fairly well in practice. Even though the configuration options are limited mostly to how affixes are handled, affixes +are usually where native APIs differ in their naming conventions. Other differences fall outside the common case and are +therefore handled by the generator user or by other mods. + +Furthermore, to keep `PrettifyNames` simple and linear, each step takes the output of the previous step, with no +interweaving of logic. + +`PrettifyNames` works as follows: + +1. All current source code is scraped to gather name information. +2. The names are transformed by a series of name processors. +3. Symbols corresponding to all transformed names are gathered. +4. A symbol-based renamer is used to replace all references to those names with their new versions. +5. Document file names are renamed using the transformed names. + +At time of writing, these are the name processors in use: +```cs +var nameProcessors = new INameProcessor[] +{ + new HandleOverridesProcessor(...), // Overrides are user configurable + new StripAffixesProcessor(...), + new PrettifyProcessor(...), // Acronym threshold is user configurable + new ReapplyAffixesProcessor(...), // Affix reapplication is user configurable + new PrefixIfStartsWithNumberProcessor(), + new ResolveConflictsProcessor(...), + new OutputFinalNamesProcessor(), + new RemoveUnmodifiedFinalNamesProcessor(), +}; +``` + +For specifics on how these processors and other steps work, it is best to refer to the `PrettifyNames` source code. + +## PrettifyNames - Notable Decisions + +Note: It may be helpful to come back to this section after reading about the rest of the name processing pipeline. + +### Strip/Reapply Affixes Scope + +Affixes are stripped and reapplied to create a "scope" where only the base name is visible. + +For example, in the execution order above, `PrettifyProcessor` only affects the base name, but +`PrefixIfStartsWithNumberProcessor` works on the full name with affixes applied. + +Currently, this distinction is not as prevalent as it was when shared prefix trimming was done during `PrettifyNames`. + +Originally, this was implemented so that prefix identification ignores any affixes that have been declared. This +notably affects cases like the `I-` prefix in the Microsoft bindings (the C-style namespace prefix is after the `I-`) +and the vendor suffixes in the Khronos bindings (removing the suffixes before identifying shared prefixes prevents +problematic cases where prefix trimming trims everything except for the vendor suffix, since the vendor suffix was the +only non-shared part of the name; see `OcclusionQueryParameterNameNV` in OpenGL for example). + +Shared prefix identification is now handled by `IdentifySharedPrefixes` by handling affix stripping/reapplication using +the utility methods provided by `NameAffixer`. `PrettifyNames` can be then configured to remove these shared prefixes, +thus matching the original behavior. + +### Strip/Reapply Affixes Configuration + +To keep things simple, only affix reapplication is configurable. This is because the user is expected to configure the +generator output, while mods are expected to handle the process of affix identification. + +Affix reapplication is when common transformations to affixes are applied, such as removing them, reordering them, and +prettifying them. + +## Name Splitting + +Name splitting involves splitting an identifier into separate "tokens" and is handled by the `NameSplitter` class. These +tokens can refer to literal words (as identified by underscore/pascal case separations), but can also refer to groups of +numbers or capitalized letters. + +Note: The codebase is inconsistent when referring to tokens, usually calling them "words" or "fragments" instead. + +The goal of name splitting is to have a consistent representation of a name where each part of the name can be examined +individually. This is helpful when names differ by casing or by different types of separation. + +For example, `VkAccessFlags`, `vkCreateBuffer`, and `VK_MAX_MEMORY_HEAPS` effectively have the same shared prefix. + +For specifics on how this process works and the exact behaviors, it is best to refer to the `NameSplitter` source code +and the `NameSplitterTests` test cases. + +### Name Splitting - Notable Decisions + +#### Handling of Numbers + +Numbers are always split out as their own individual token. This is because this is easier to work with and consistent +than special casing when numbers should "stick" to preceding or proceeding tokens. + +For example: +- `2D` is split as `2_D` +- `R32` is split as `R_32` + +In these two cases, both inputs can be considered one English word, so it can be argued that the output should be the +same as the input. However, this means the name splitting code should have preferences for when numbers should "stick" +one way or the other. + +This gets even messier with names like `Image_2D_RGB16` or `Image2D_RGB16`. Although these exact names have not shown +up in native code, names like `SpvImageFormatR32ui` do in fact exist. + +Because the goal of name splitting is to have a consistent tokenized representation of the name, it can be argued +that it is safer to go for a more naive approach that does not attempt to group numbers with letters together at all. +In this case, a more naive approach means simpler code. It also means less potential surprises since the output is more +resistant to subtle changes in the input. + +## Name Prettification + +As hinted to previous, name prettification is the process of transforming an identifier to follow the Framework +Design Guidelines and is handled by the `NamePrettifier` class. + +This primarily involves pascal casing and the removal of underscore separators. Acronyms are also handled. By default, +acronyms of length 2 are preserved (matching the guidelines), while acronyms of greater lengths are pascal-cased. + +For example, "UI" is prettified as "UI" while "GUI" is prettified as "Gui". +Similarly, "GL" is prettified as "GL" while "EGL" is prettified as "Egl". + +Name prettification takes in a name "fragment" and outputs another fragment representing the prettified version of the +input. The input is first split using `NameSplitter` to get a tokenized representation of the name before being +processed. + +For specifics on how this process works and the exact behaviors, it is best to refer to the `NamePrettifier` source code +and the `NamePrettifierTests` test cases. + +### Name Prettification - Notable Decisions + +#### Output of Fully Capitalized Names + +By default, the `NamePrettifier` disallows outputs that are all caps. +For example, if `GL` is the output and `allowAllCaps` is the default of false, then `Gl` will be the actual output. + +This is to prevent fully capitalized member names, so the codebase typically overrides this behavior when dealing with +type names. This means the `GL` class remains as `GL`. + +#### Handling of Acronyms that contain Numbers + +An acronym includes the capital letters and the numbers immediately following those letters. + +For example: +- `2D` is split as `2_D`. There are 2 acronyms of length 1 here. +- `R32` is split as `R_32`. There is 1 acronym of length 3 here. + +Where this behavior matters is in the following case: +- `RG` is split as `RG` and is prettified as `RG`, however the `NamePrettifier` also disallows outputs that are fully + capitalized by default. This means `RG` is actually output as `Rg`. +- `RG32` is split as `RG_32`. Because this is an acronym of length 4, it is output as `Rg32`. + +Notably, this means that `RG` and `RG32` are consistently output as `Rg-`. + +In the code, this is implemented by merging number tokens with preceding letter tokens. + +For example: +- `2_D` is merged as `2_D`. +- `RG_32` is merged as `RG32`. + +This can be argued to be a hack, but simplifies acronym length calculations and continues to work with the code that +handles pascal casing, which simply uppercases the first character and lowercases the rest for each token. + +#### Acronym Indeterminate Inputs + +These refer to inputs that are fully uppercased, making it hard to tell whether the input is a standalone acronym or +simply written in screaming case. + +The current code handling this behavior was implemented back when the generator used a default long acronym threshold of +3 (and occasionally using 4*), which in turn was ported from the original Humanizer-based prettify implementation. +Therefore, the examples given in the code state a threshold of 4. + +\*4 was used for Khronos APIs as a best effort to preserve vendor suffixes (eg: `KHR`, `EXT`, `NV`, `QCOM`). This is no +longer necessary because the name affix system is now used to preserve these suffixes. + +This behavior notably is less noticeable with the long acronym threshold of 2, but still affects a few names, such as +the `GL` class. Without this, `GL` gets turned into `Gl` since the input is treated as screaming case. + +To learn more about this behavior, please refer to the comments in `NamePrettifier`. + +#### Handling of Consecutive Acronyms + +Consecutive acronyms are pascal-cased, if they both are candidates for being uppercased. + +For example, assuming a long acronym threshold of 4, `RGBA_ASTC` will be prettified as `RgbaAstc`, not `RGBAASTC`. +This is because the latter is much harder to read. + +However, if only one of the two consecutive acronyms is a candidate, for example, with a threshold of 2 and `RG_ASTC`, +the result will be `RGAstc`. + +#### Lowercase "x" between Numbers + +Consecutive numbers are separated by a lowercase "x". Furthermore, if a name already is in the format `2_X_2`, the "X" +will be lowercased. + +The use of the "x" is to ensure that numbers remain separated, especially because prettified names never contain +underscores, which is usually how consecutive numbers are separated in native code. + +The use of a *lowercase* x in particular is a stylistic choice and matches names like `System.Numerics.Matrix4x4`. + +## Name Affixes + +Name prefixes and suffixes are used commonly in both native code and in identifiers created by the SilkTouch generator. + +For example, in `VkPresentInfoKHR` from Vulkan, `Vk-` is a namespace prefix commonly used in C code, while `-KHR` is a +Khronos-style suffix denoting that the type belongs to the `KHR` family of extensions. + +In the generator, suffixes are usually used to denote names that are derived from other names or to prevent name +collisions. For example, `-Handle` is appended to handle types transformed by `TransformHandles`. This means that handle +types like `Buffer` are named as `BufferHandle` instead, thus reducing name collision risks with user-defined types. + +Because of the prevalence of affixes in both native and generated code, the name affix system was added so that names +can be annotated with information about what affixes have been identified or added to the name. This allows mods to +target transformations to a specific, known part of a name. + +Furthermore, because each category of affix can be identified by different mods, it keeps the complex affix +identification process localized to the mod that specializes in that area. For example, C-style namespace prefixes +are handled by `IdentifySharedPrefixes`. + +### Name Affixes - Metadata Format + +The name affixes for a corresponding identifier are stored as C# attributes declared on that identifier. This takes +advantage of the fact that the SilkTouch generator is designed such that mods primarily take Roslyn syntax trees as +input and return new syntax trees as output. + +For example, from the OpenGL bindings: +```cs +public enum InternalFormat +{ + [NameAffix("Prefix", "SharedPrefix", "GL")] + [NameAffix("Suffix", "KhronosVendor", "ARB")] + GL_RGBA32F_ARB = 34836, +} +``` + +In order, the parameters are: + +1. Affix type - Either "Prefix" or "Suffix". +2. Affix category - Used to identify the purpose or source of that affix. + - `PrettifyNames` can be configured to process different affix categories in different ways. For example, + shared prefixes can be removed by targeting the `SharedPrefix` category. +3. Affix value - The affix as it appears in the identifier. + - Note: Currently, affixes need to verbatim match the part of the identifier they represent. For example, stripping + `GL_RGBA32F_ARB` of the `GL-` prefix leads to `_RGBA32F_ARB`, while `GL_-` will lead to `RGBA32F_ARB`. Despite + this, the codebase is written to not include the underscore since it currently does not affect the output and is + arguably cleaner to avoid leading or trailing underscores in affix values. If this does prove to be a problem, + prefer updating the affix stripping code to be tolerant of extra underscores. + +These parameters are all strings for simplicity when parsing. Additionally, the order of the attributes is significant: +name affixes declared earlier in the attribute list represent name affixes closer to the inside of the name. + +However, as a user of the name affix system, the utilities provided by `NameAffixer` should provide everything necessary +for interacting with name affixes without interacting with the exact syntax node representation. + +### Name Affixes - Notable Interactions + +#### IdentifySharedPrefixes and Name Affixes + +`IdentifySharedPrefixes` strips affixes before identifying shared prefixes. Therefore, names like `ID3D12Device` will +appear as `D3D12Device` if the `I-` prefix is identified beforehand. + +#### Deferring Renames + +Renames that involve the addition of affixes can be done simply by adding the affix to the name, assuming that +`PrettifyNames` runs afterward. This is preferable because it avoids a project-wide symbol search to locate and update +where that identifier is used. + +This is because stripping affixes is tolerant of missing affixes and affixes reapplication will then add the newly +declared affix to the final name. + +For example, the `-Handle` suffix is added by `TransformHandles`. This leads to syntax that looks like: +```cs +[NameAffix("Suffix", "HandleType", "Handle")] +public struct Buffer; +``` + +Even though `Buffer` does not have a `-Handle` suffix, it will have it after `PrettifyNames` executes. This is assuming +that `PrettifyNames` is not configured to remove it. + +The removal of affixes can be done similarly, but will involve updating the generator config so that `PrettifyNames` +removes the affix. + +### Referenced Affixes + +Referenced affixes were added to handle compound names where part of the name is actually the name of another +identifier. This ensures that the "referenced" part of the name always matches the name being referenced, in other +words, changes to the referenced name is "synchronized" to the name referencing it (more on this later). + +This occurs primarily in types added to the bindings by Silk. For example: + +- **Nested types** - Nested types are extracted by `ExtractNestedTyping` to be non-nested types. These types have the name + of their parent type plus their original name (for nested types that have proper names in the native code) or the name + of their parent type plus the name of the field that uses them (eg: for `InlineArray` types). + - Example: `GamepadBinding`, `GamepadBindingInput`, and `GamepadBindingInputAxis` in the SDL bindings. The latter are + nested structs. + - Example: `PerformanceCounterDescriptionARM` and `PerformanceCounterDescriptionARMName` in the Vulkan bindings. The + latter is an inline array used by the field, `PerformanceCounterDescriptionARM.Name`. + +- **Derived types** - Derived types refer to types that are generated based on another type (not to be confused with + inheritance). At time of writing, this only refers to function pointer types for which Silk generates a function + pointer struct and a corresponding delegate type. The delegate type has the `-Delegate` suffix appended to it. + - Example: `DebugReportCallbackEXT` and `DebugReportCallbackEXTDelegate` in the Vulkan bindings. + +In other words, referenced affixes are most helpful when dealing with types that are the logical extensions of other +types. + +Most notably, handle type suffixes do not fall into the above categorization. + +For example: `PipelineBinaryHandleKHR` in the Vulkan bindings is not an extension of `PipelineBinaryKHR`, it *is* the +type, just renamed to avoid naming collisions. + +Going back to the idea of synchronizing changes, if `GamepadBinding` was to be renamed using an override, the referenced +affix system ensures that `GamepadBindingInput` and `GamepadBindingInputAxis` are renamed correspondingly. + +Similarly, it ensures that if an affix is configured to be moved to the end of the referenced name, the affix only moves +to the end of the name it was originally declared on. This can be seen in `PerformanceCounterDescriptionARMName` and +`PipelineBinaryHandleKHR`. `ARM` is a Khronos vendor suffix for `PerformanceCounterDescriptionARM`, so it only moves to +the end of that name; however, `KHR` is a vendor suffix for `PipelineBinaryHandleKHR` as a whole. + +Side note: Another benefit of referenced affixes is that it ensures that derived types show up when typing the base +type's name in the IDE. For example, if vendor suffixes always moved to the end of the name, +`PerformanceCounterDescriptionARMName` would become `PerformanceCounterDescriptionNameARM` and would not show up when +autocompleting `PerformanceCounterDescriptionARM`. + +### Referenced Affixes - Metadata Format + +Referenced affixes work the exact same as normal name affixes, but take advantage of C#'s nameof syntax. + +For example, from the SDL bindings: +```cs +public struct GamepadBinding; + +[NameAffix("Prefix", "NestedStructParent", nameof(GamepadBinding))] +public struct GamepadBindingInput; + +[NameAffix("Prefix", "NestedStructParent", nameof(GamepadBindingInput))] +public struct GamepadBindingInputAxis; +``` + +Limitation: Only simple references are allowed because references are resolved manually by `PrettifyNames` and not by +Roslyn. For example, `nameof(GamepadBinding.Member)` will not work because member access expressions are not handled. +Currently, only identifiers that exist in the current scope or parent scope can be referenced. That said, this should be +enough for most use cases. + +## Symbol-based Renamer + +The renamer exists as `NameUtils.RenameAllAsync()` and uses Roslyn symbols to determine whether an identifier needs to +be replaced. + +The renamer has gone through several iterations, mainly due to performance reasons. + +Previously, it used `SymbolFinder.FindReferencesAsync()`, which was replaced since it was far too slow for bigger APIs +like the Microsoft bindings. `FindReferencesAsync` was not designed for mass replacement of all identifiers in a project +and thus suffered an `O(n^2)` scaling (where `n` is the size of the project) since it scanned the entire project for +each symbol replaced. + +The current implementation, which is the `LocationTransformationUtils` class in the codebase, uses a +`CSharpSyntaxRewriter` that visits every syntax node in the project and looks up symbols related to the node before +deciding to replace it. This changes the scaling to `O(n)` with symbol lookup being the primary bottleneck. Symbol +lookup is optimized by checking if the name of the identifier matches a name of the symbols to rename, which doubles the +speed of renaming when it comes to the Vulkan bindings. + +The reason the new renamer is part of the "location transformation" code is because the renamer has also been +generalized to work with any transformation that needs to modify all references of a symbol. This notably was designed +back when `TransformHandles` needed to simultaneously rename all references to a handle type and decrease the pointer +dimension of the references to the type by one (eg: `Buffer**` becomes `BufferHandle*`). This bulk modification ensures +that symbol lookup only needs to occur once. + +Side note: Arguably, "reference transformation" better describes this area of the codebase, but the name originally came +from the `ReferenceLocation` type returned by `SymbolFinder.FindReferencesAsync()`. diff --git a/docs/for-contributors/Generator/using-the-generator.md b/docs/for-contributors/Generator/using-the-generator.md new file mode 100644 index 0000000000..70eb00493c --- /dev/null +++ b/docs/for-contributors/Generator/using-the-generator.md @@ -0,0 +1,231 @@ +# Using the Generator + +**Warning: The generator is still in heavy development and will likely be subject to frequent breaking changes.** +This will likely be the case until we are a few previews in. + +SilkTouch is Silk 3's bindings generator, which works by taking the output of ClangSharpPInvokeGenerator, which itself +is its own bindings generator, and modifying the output with a set of mods. These mods apply transformations such as +renaming identifiers, creating types such as handle structs or enums, and adding method overloads. + +In other words, ClangSharpPInvokeGenerator acts as the input to the SilkTouch generator and SilkTouch works to improve +on that input. Other inputs to the SilkTouch generator will likely be available in the future to cover APIs such as +Metal. + +**Also note that only C bindings are supported right now. COM will be available later.** + +## Generator Overview + +There are two main things to configure: + +1. Silk 3 - This is the [`generator.json`](https://github.com/dotnet/Silk.NET/blob/develop/3.0/generator.json) file. + +2. ClangSharpPInvokeGenerator - This is the [`eng/silktouch`](https://github.com/dotnet/Silk.NET/tree/develop/3.0/eng/silktouch) folder. + +Both are organized by native API. + +Note: For the average C API, SDL's generator configuration would be the best configuration to reference. Most options +used for the SDL bindings should be applicable after replacing the SDL-specific paths and values to suit the C API that +you are binding. + +## SilkTouch Configuration + +Silk defines the configuration for SilkTouch in the +[`generator.json`](https://github.com/dotnet/Silk.NET/blob/develop/3.0/generator.json) file. This file defines the +different bindings jobs and the list of mods to run for each job. + +For example, when binding to a C API: + +- `AddIncludes` tells `ClangScraper` where to find the system header files. You likely want to include this. + +- `ClangScraper` runs ClangSharpPInvokeGenerator. Including this mod on its own is equivalent to running + ClangSharpPInvokeGenerator directly. + +- The rest of the mods apply different transformations to the output of `ClangScraper`. Documentation for the other mods + can be found in the [Generator Mods](generator-mods.md) documentation. + +Aside from reading documentation, some other ways to learn about the mods are to: + +- Read through the tests. The tests act as examples for specific behaviors expected by each mod, with configurations, + inputs, and expected outputs provided for each case. + +- Add them one by one. Mods run in the order you define them and work off the output of the previous mod. + +(TODO: Not sure how to set up bindings test projects. This refers to the `TestProject` property in `generator.json`.) + +## ClangSharpPInvokeGenerator Configuration + +Due to SilkTouch using ClangSharpPInvokeGenerator as an input, ClangSharpPInvokeGenerator must also be configured. +Silk stores its configuration for ClangSharpPInvokeGenerator in the +[`eng/silktouch`](https://github.com/dotnet/Silk.NET/tree/develop/3.0/eng/silktouch) folder as `.rsp` files (response +files). These response files store command line arguments to be passed into ClangSharpPInvokeGenerator. + +Note that these files can be stored anywhere since the SilkTouch configuration lets you configure where the SilkTouch +generator looks for these response files. + +> To read more about ClangSharpPInvokeGenerator's command line arguments, a good option is to install the tool directly +> and use `--help` to display its command line documentation. +> +> ```sh +> dotnet tool install --global ClangSharpPInvokeGenerator +> ClangSharpPInvokeGenerator --help +> ClangSharpPInvokeGenerator --config help +> ``` + +Aside from simply storing the command line arguments to be passed into ClangSharpPInvokeGenerator, response files can +also import other response files using the `@path` syntax. For example: `@../settings.rsp`. + +Silk commonly uses these import paths to share settings between different sets of bindings, such as the +[common.rsp](https://github.com/dotnet/Silk.NET/blob/develop/3.0/eng/silktouch/common.rsp) file for general shared +settings and the [remap-stdint.rsp](https://github.com/dotnet/Silk.NET/blob/develop/3.0/eng/silktouch/remap-stdint.rsp) +file used to ensure that the `stdint.h` types behave consistently between Windows and Linux. + +Please note that these paths are relative to the response file specified in the generator and **not** relative to the +response file the `@path` directive is actually defined in. + +For example, Silk's SDL bindings sets `ClangSharpResponseFiles` to be `eng/silktouch/sdl/**/generate.rsp`. Therefore, +any import paths used in the response files, including transitively imported response files, must be relative to +the matched `generate.rsp` file. + +### Example Response File Folder Structure + +This is the general structure of the `eng/silktouch` folder: + +``` +eng +- silktouch + - opengl <-- This level contains folders per native API. + - glcompat <-- This level contains folders for each "profile", which represent variants of the API. + - glcore + - gles1 + - gles2 + - sdl + - SDL3 +``` + +Profiles likely will not be relevent for most C APIs, so the examples here will keep focusing on the SDL case. + +The following is the folder structure used for Silk's SDL bindings. You do not have to structure it the way Silk does. +Silk's structure focuses on keeping consistency in its response file organization, regardless of whether the API makes +use of profiles or not. + +``` +eng +- silktouch + - sdl + - SDL3 + - generate.rsp <-- The main settings file. Used as the entrypoint. All import paths must be relative to this file. + - header.txt + - sdl-SDL.h <-- Handwritten header file that #includes the relevant headers of the library you want to bind. + - remap.rsp + - settings.rsp <-- Shared settings for all profiles. +``` + +### Example ClangSharpPInvokeGenerator Configuration + +This section will now focus on how to actually create the response files, starting with the `sdl-SDL.h`, `generate.rsp`, +and `settings.rsp` files. The snippets below will only contain the most important sections of those files for brevity. + +`sdl-SDL.h`: +```h +#include +#include +#include +``` + +`generate.rsp`: +```rsp +@../settings.rsp +@../remap.rsp +--exclude +SDL_SetX11EventHook +SDL_SetWindowsMessageHook +SDL_FILE +SDL_LINE +--file +sdl-SDL.h +--methodClassName +Sdl +--namespace +Silk.NET.SDL +--output +../../../../sources/SDL/SDL3 +--traverse +../../../submodules/sdl/include/SDL3/SDL_assert.h +../../../submodules/sdl/include/SDL3/SDL_atomic.h +../../../submodules/sdl/include/SDL3/SDL_audio.h +``` + +`settings.rsp`: +```rsp +@../../common.rsp +--define-macro +TODO_DEFINE_MACROS=HERE +--headerFile +header.txt +--include-directory +../../../submodules/sdl/include +--with-callconv +*=Winapi +--with-librarypath +*=SDL3 +``` + +#### Relevant Options from `generate.rsp`: + +- `--file` specifies the header file to use as the entrypoint. This should be the custom header you defined. + +- `--traverse` specifies which header files contribute towards the output. + +This separation is because while header files, such as system headers, may be required to compile the library, we do not +want to include those headers as part of the final set of generated bindings. + +- `--output` should point to the same `Jobs.JOB_NAME.SourceProject` path you defined in `generator.json`. + +- `--methodClassName` specifies which C# class contains the generated methods/constants. + +- `--namespace` specifies the C# namespace of the generated files. + +- `--exclude` allows you exclude types/functions/constants from the output. These usually are APIs that are not useful, + do not generate correctly, or are platform-specific. + +#### Relevant options from `settings.rsp`: + +- `--headerFile` specifies the header file appended to the top of every generated file. Silk uses this to inject its + copyright headers. + +- `--include-directory` specifies the include directories to be used. This affects all headers included, such as + `sdl-SDL.h`. + +- `--with-librarypath` is the name of the native library without prefixes/suffixes. If the library name differs outside + of the usual `lib` prefix or `.dll`/`.so`/`.dylib` suffixes, the way to handle this is to add `UseAlternativeName` in + the generated bindings. An example with Vulkan can be found in + [`sources/Vulkan/Vulkan/Vk.cs`](https://github.com/dotnet/Silk.NET/blob/develop/3.0/sources/Vulkan/Vulkan/Vk.cs), + which is a manually written file. + +```cs +static Vk() +{ + LoaderInterface.RegisterHook(Assembly.GetExecutingAssembly()); + LoaderInterface.RegisterAlternativeName("vulkan", "vulkan-1"); + LoaderInterface.RegisterAlternativeName("vulkan", "MoltenVK"); +} +``` + +### Generated Bindings Output + +All generated binding will be output to the `Jobs.JOB_NAME.SourceProject` path defined in `generator.json`. + +These generated files all have the `.gen.cs` suffix and most of them are partial type declarations. +This means by creating a similarly named `.cs` file and using the `partial` C# keyword, you can add to the type. + +Do not modify the `.gen.cs` files since running the generator again will overwrite those changes. + +### Packing the Generated Bindings + +(TODO: This section needs verification.) + +`dotnet pack` should simply work here. + +If you are contributing to Silk's repository, Silk automatically packs and pushes changes made during a pull request +to its experimental NuGet feed. More information on how to access this feed is available in the +[Experimental Feed](../../silk.net/experimental-feed.md) documentation. diff --git a/docs/silk.net/static-vs-instance-bindings.md b/docs/silk.net/static-vs-instance-bindings.md index 38c43b0e48..7c2314ac6d 100644 --- a/docs/silk.net/static-vs-instance-bindings.md +++ b/docs/silk.net/static-vs-instance-bindings.md @@ -1,4 +1,4 @@ -# Static vs Input Bindings +# Static vs Instance Bindings ## Overview diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs b/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs index 15ea50eb5d..97aafc6251 100644 --- a/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs +++ b/sources/SilkTouch/SilkTouch/Mods/Common/ModUtils.cs @@ -160,17 +160,24 @@ public static string GetMethodDiscriminator(BaseParameterSyntax param) => GetMethodDiscriminator(param.Modifiers, param.Type); /// - /// Gets the relative path for this document. + /// Gets the path relative to the document's project path for the specified document. /// /// The document. /// The relative path. public static string? RelativePath(this Document doc) { - if ( - doc.FilePath is null - || doc.Project.FilePath is null - || Path.GetDirectoryName(doc.Project.FilePath) is not { Length: > 0 } dir - ) + if (doc.FilePath is null) + { + return default; + } + + // Handle projects with no path by simply returning the document path + if (doc.Project.FilePath is null) + { + return doc.FilePath; + } + + if (Path.GetDirectoryName(doc.Project.FilePath) is not { Length: > 0 } dir) { return default; } diff --git a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs index 4d5ff5fb28..273eee7491 100644 --- a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs +++ b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs @@ -17,7 +17,7 @@ namespace Silk.NET.SilkTouch.Mods; /// /// /// Replacing function pointers identified by their s with delegates and -/// Pfn-prefixed structures. +/// function pointer structs. /// /// /// Moving constants into their respective enums. These constants are identified by checking for an enum with diff --git a/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs index 9f8aa554d1..48549860e9 100644 --- a/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs +++ b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Silk.NET.SilkTouch.Clang; namespace Silk.NET.SilkTouch.Mods; @@ -11,8 +12,8 @@ namespace Silk.NET.SilkTouch.Mods; /// /// /// 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. +/// Syntax nodes not output by are intentionally not processed. +/// This mod is best placed directly after . /// public class MarkNativeNames : IMod { diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs index c2011b0ba4..b74ffba117 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs @@ -15,16 +15,19 @@ namespace Silk.NET.SilkTouch.Mods; /// -/// Identifies handle types by finding pointers to empty structs or missing types. +/// Identifies handle types by finding pointers to empty structs. /// In general, a handle type is a struct that wraps an underlying opaque pointer (or some other underlying value). /// These handle types are then transformed by making the struct wrap the underlying pointer and /// reducing the dimension of pointers referencing that handle type by one. /// /// /// Given an empty struct, struct VkBuffer, and all usages of that struct are through a pointer, -/// VkBuffer*, usages of that pointer will be replaced by VkBufferHandle. For a 2-dimensional pointer, -/// VkBuffer**, the resulting replacement is VkBufferHandle*. +/// VkBuffer*, usages of that pointer will be replaced by VkBuffer. For a 2-dimensional pointer, +/// VkBuffer**, the resulting replacement is VkBuffer*. /// +/// +/// The `Handle` suffix is not applied until executes. +/// [ModConfiguration] public class TransformHandles( IOptionsSnapshot config, diff --git a/tests/SilkTouch/SilkTouch/ArrayParameterTransformerTests.cs b/tests/SilkTouch/SilkTouch/ArrayParameterTransformerTests.cs index 772f171f97..da9e66b66b 100644 --- a/tests/SilkTouch/SilkTouch/ArrayParameterTransformerTests.cs +++ b/tests/SilkTouch/SilkTouch/ArrayParameterTransformerTests.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using NUnit.Framework; using Silk.NET.SilkTouch.Mods.Metadata; using Silk.NET.SilkTouch.Mods.Transformation; diff --git a/tests/SilkTouch/SilkTouch/Caching/CacheTests.cs b/tests/SilkTouch/SilkTouch/Caching/CacheTests.cs index d31d1dcb71..aae419386d 100644 --- a/tests/SilkTouch/SilkTouch/Caching/CacheTests.cs +++ b/tests/SilkTouch/SilkTouch/Caching/CacheTests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; -using System.Diagnostics; using System.IO.Compression; using System.Runtime.CompilerServices; using System.Text; diff --git a/tests/SilkTouch/SilkTouch/DummyModContext.cs b/tests/SilkTouch/SilkTouch/DummyModContext.cs index 197f71f229..1ce9b55a70 100644 --- a/tests/SilkTouch/SilkTouch/DummyModContext.cs +++ b/tests/SilkTouch/SilkTouch/DummyModContext.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Silk.NET.SilkTouch.Mods; diff --git a/tests/SilkTouch/SilkTouch/ExtractHandlesTests.SuccessfullyExtractsHandleType.verified.txt b/tests/SilkTouch/SilkTouch/ExtractHandlesTests.SuccessfullyExtractsHandleType.verified.txt new file mode 100644 index 0000000000..61f0e29eff --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractHandlesTests.SuccessfullyExtractsHandleType.verified.txt @@ -0,0 +1,12 @@ +// Vk.gen.cs +public struct VkAllocationCallbacks; +public struct VkInstanceCreateInfo; +public class Vk +{ + public static extern VkResult vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, VkInstance_T** pInstance); +} + +// VkInstance_T.gen.cs +public unsafe partial struct VkInstance_T +{ +} diff --git a/tests/SilkTouch/SilkTouch/ExtractHandlesTests.cs b/tests/SilkTouch/SilkTouch/ExtractHandlesTests.cs new file mode 100644 index 0000000000..3d3679afd5 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractHandlesTests.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging.Abstractions; +using Silk.NET.SilkTouch.Mods; + +namespace Silk.NET.SilkTouch.UnitTests; + +public class ExtractHandlesTests +{ + static ExtractHandlesTests() + { + if (!VerifyDiffPlex.Initialized) + { + VerifyDiffPlex.Initialize(); + } + } + + [Test] + public async Task SuccessfullyExtractsHandleType() + { + var project = TestUtils + .CreateTestProject() + .AddDocument( + "Vk.gen.cs", + """ + public struct VkAllocationCallbacks; + public struct VkInstanceCreateInfo; + + public class Vk + { + public static extern VkResult vkCreateInstance( + VkInstanceCreateInfo* pCreateInfo, + VkAllocationCallbacks* pAllocator, + VkInstance_T** pInstance + ); + } + """ + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractHandles = new ExtractHandles(NullLogger.Instance); + + await extractHandles.ExecuteAsync(context); + + // There should be an empty struct named VkInstance_T in a new file + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } +} diff --git a/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_MethodParameter.verified.txt b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_MethodParameter.verified.txt new file mode 100644 index 0000000000..6381c82a87 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_MethodParameter.verified.txt @@ -0,0 +1,20 @@ +// SDL_BlendMode.gen.cs +public enum SDL_BlendMode : uint +{ + SDL_BLENDMODE_NONE = 0x00000000U, + SDL_BLENDMODE_BLEND = 0x00000001U, + SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U, + SDL_BLENDMODE_ADD = 0x00000002U, + SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U, + SDL_BLENDMODE_MOD = 0x00000004U, + SDL_BLENDMODE_MUL = 0x00000008U, + SDL_BLENDMODE_INVALID = 0x7FFFFFFFU +} + +// Sdl.gen.cs +public unsafe partial struct Sdl +{ + [DllImport("SDL3", ExactSpelling = true)] + [return: NativeTypeName("bool")] + public static extern byte SDL_SetSurfaceBlendMode(SDL_Surface* surface, [NativeTypeName("SDL_BlendMode")] SDL_BlendMode blendMode); +} diff --git a/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_Pointer.verified.txt b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_Pointer.verified.txt new file mode 100644 index 0000000000..3ff556a066 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsCStyleEnumConstants_Pointer.verified.txt @@ -0,0 +1,20 @@ +// SDL_BlendMode.gen.cs +public enum SDL_BlendMode : uint +{ + SDL_BLENDMODE_NONE = 0x00000000U, + SDL_BLENDMODE_BLEND = 0x00000001U, + SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U, + SDL_BLENDMODE_ADD = 0x00000002U, + SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U, + SDL_BLENDMODE_MOD = 0x00000004U, + SDL_BLENDMODE_MUL = 0x00000008U, + SDL_BLENDMODE_INVALID = 0x7FFFFFFFU +} + +// Sdl.gen.cs +public unsafe partial struct Sdl +{ + [DllImport("SDL3", ExactSpelling = true)] + [return: NativeTypeName("bool")] + public static extern byte SDL_GetSurfaceBlendMode(SDL_Surface* surface, [NativeTypeName("SDL_BlendMode *")] SDL_BlendMode* blendMode); +} diff --git a/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsFunctionPointer.verified.txt b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsFunctionPointer.verified.txt new file mode 100644 index 0000000000..bbbbe8facc --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsFunctionPointer.verified.txt @@ -0,0 +1,26 @@ +// PFN_vkDebugReportCallbackEXT.gen.cs +[NativeName("PFN_vkDebugReportCallbackEXT")] +public unsafe readonly struct PFN_vkDebugReportCallbackEXT : IDisposable +{ + private readonly void* _pointer; + public delegate* unmanaged Handle => (delegate* unmanaged )_pointer; + + public PFN_vkDebugReportCallbackEXT(delegate* unmanaged ptr) => _pointer = ptr; + public PFN_vkDebugReportCallbackEXT(PFN_vkDebugReportCallbackEXTDelegate proc) => _pointer = SilkMarshal.DelegateToPtr(proc); + public void Dispose() => SilkMarshal.Free(_pointer); + public static implicit operator PFN_vkDebugReportCallbackEXT(delegate* unmanaged pfn) => new(pfn); + public static implicit operator delegate* unmanaged (PFN_vkDebugReportCallbackEXT pfn) => (delegate* unmanaged )pfn._pointer; +} + +// PFN_vkDebugReportCallbackEXTDelegate.gen.cs +[NativeName("PFN_vkDebugReportCallbackEXT")] +[NameAffix("Prefix", "FunctionPointerParent", nameof(PFN_vkDebugReportCallbackEXT))] +[NameAffix("Suffix", "FunctionPointerDelegateType", "Delegate")] +public unsafe delegate uint PFN_vkDebugReportCallbackEXTDelegate(uint arg0, VkDebugReportObjectTypeEXT arg1, ulong arg2, nuint arg3, int arg4, sbyte* arg5, sbyte* arg6, void* arg7); + +// VkDebugReportCallbackCreateInfoEXT.gen.cs +public unsafe partial struct VkDebugReportCallbackCreateInfoEXT +{ + [NativeTypeName("PFN_vkDebugReportCallbackEXT")] + public PFN_vkDebugReportCallbackEXT pfnCallback; +} diff --git a/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsNestedInlineArray.verified.txt b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsNestedInlineArray.verified.txt new file mode 100644 index 0000000000..42e315ede4 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.SuccessfullyExtractsNestedInlineArray.verified.txt @@ -0,0 +1,16 @@ +// VkPerformanceCounterDescriptionARM.gen.cs +namespace Silk.NET.Vulkan; +public struct VkPerformanceCounterDescriptionARM +{ + [NativeTypeName("char[256]")] + public VkPerformanceCounterDescriptionARMname name; +} + +// VkPerformanceCounterDescriptionARMname.gen.cs +namespace Silk.NET.Vulkan; +[InlineArray(256)] +[NameAffix("Prefix", "NestedStructParent", nameof(VkPerformanceCounterDescriptionARM))] +public struct VkPerformanceCounterDescriptionARMname +{ + public sbyte e0; +} diff --git a/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.cs b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.cs new file mode 100644 index 0000000000..d6e4d373d6 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/ExtractNestedTypingTests.cs @@ -0,0 +1,330 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging.Abstractions; +using Silk.NET.SilkTouch.Mods; + +namespace Silk.NET.SilkTouch.UnitTests; + +public class ExtractNestedTypingTests +{ + static ExtractNestedTypingTests() + { + if (!VerifyDiffPlex.Initialized) + { + VerifyDiffPlex.Initialize(); + } + } + + [Test] + public async Task SuccessfullyExtractsNestedInlineArray() + { + var inputDocName = "VkPerformanceCounterDescriptionARM.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + namespace Silk.NET.Vulkan; + + public struct VkPerformanceCounterDescriptionARM + { + [NativeTypeName("char[256]")] + public _name_e__FixedBuffer name; + + [InlineArray(256)] + public struct _name_e__FixedBuffer + { + public sbyte e0; + } + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"Vulkan/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The nested struct should be extracted and named as VkPerformanceCounterDescriptionARMname + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task SuccessfullyExtractsFunctionPointer() + { + var inputDocName = "VkDebugReportCallbackCreateInfoEXT.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + public unsafe partial struct VkDebugReportCallbackCreateInfoEXT + { + [NativeTypeName("PFN_vkDebugReportCallbackEXT")] + public delegate* unmanaged< + uint, + VkDebugReportObjectTypeEXT, + ulong, + nuint, + int, + sbyte*, + sbyte*, + void*, + uint> pfnCallback; + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"Vulkan/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The function pointer should be extracted as both a struct and a delegate + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task SuccessfullyExtractsCStyleEnumConstants_Field() + { + var inputDocName = "Sdl.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + public unsafe partial struct Sdl + { + [NativeTypeName("#define SDL_BLENDMODE_NONE 0x00000000u")] + public const uint SDL_BLENDMODE_NONE = 0x00000000U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND 0x00000001u")] + public const uint SDL_BLENDMODE_BLEND = 0x00000001U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u")] + public const uint SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD 0x00000002u")] + public const uint SDL_BLENDMODE_ADD = 0x00000002U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD_PREMULTIPLIED 0x00000020u")] + public const uint SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U; + + [NativeTypeName("#define SDL_BLENDMODE_MOD 0x00000004u")] + public const uint SDL_BLENDMODE_MOD = 0x00000004U; + + [NativeTypeName("#define SDL_BLENDMODE_MUL 0x00000008u")] + public const uint SDL_BLENDMODE_MUL = 0x00000008U; + + [NativeTypeName("#define SDL_BLENDMODE_INVALID 0x7FFFFFFFu")] + public const uint SDL_BLENDMODE_INVALID = 0x7FFFFFFFU; + } + + public class Test + { + [NativeTypeName("SDL_BlendMode")] + public uint Blend; + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"SDL3/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The constants should have been moved from the Sdl clas to the SDL_BlendMode enum + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task SuccessfullyExtractsCStyleEnumConstants_MethodParameter() + { + var inputDocName = "Sdl.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + public unsafe partial struct Sdl + { + [NativeTypeName("#define SDL_BLENDMODE_NONE 0x00000000u")] + public const uint SDL_BLENDMODE_NONE = 0x00000000U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND 0x00000001u")] + public const uint SDL_BLENDMODE_BLEND = 0x00000001U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u")] + public const uint SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD 0x00000002u")] + public const uint SDL_BLENDMODE_ADD = 0x00000002U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD_PREMULTIPLIED 0x00000020u")] + public const uint SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U; + + [NativeTypeName("#define SDL_BLENDMODE_MOD 0x00000004u")] + public const uint SDL_BLENDMODE_MOD = 0x00000004U; + + [NativeTypeName("#define SDL_BLENDMODE_MUL 0x00000008u")] + public const uint SDL_BLENDMODE_MUL = 0x00000008U; + + [NativeTypeName("#define SDL_BLENDMODE_INVALID 0x7FFFFFFFu")] + public const uint SDL_BLENDMODE_INVALID = 0x7FFFFFFFU; + + [DllImport("SDL3", ExactSpelling = true)] + [return: NativeTypeName("bool")] + public static extern byte SDL_SetSurfaceBlendMode( + SDL_Surface* surface, + [NativeTypeName("SDL_BlendMode")] uint blendMode + ); + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"SDL3/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The constants should have been moved from the Sdl clas to the SDL_BlendMode enum + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task SuccessfullyExtractsCStyleEnumConstants_Pointer() + { + var inputDocName = "Sdl.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + public unsafe partial struct Sdl + { + [NativeTypeName("#define SDL_BLENDMODE_NONE 0x00000000u")] + public const uint SDL_BLENDMODE_NONE = 0x00000000U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND 0x00000001u")] + public const uint SDL_BLENDMODE_BLEND = 0x00000001U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u")] + public const uint SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD 0x00000002u")] + public const uint SDL_BLENDMODE_ADD = 0x00000002U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD_PREMULTIPLIED 0x00000020u")] + public const uint SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U; + + [NativeTypeName("#define SDL_BLENDMODE_MOD 0x00000004u")] + public const uint SDL_BLENDMODE_MOD = 0x00000004U; + + [NativeTypeName("#define SDL_BLENDMODE_MUL 0x00000008u")] + public const uint SDL_BLENDMODE_MUL = 0x00000008U; + + [NativeTypeName("#define SDL_BLENDMODE_INVALID 0x7FFFFFFFu")] + public const uint SDL_BLENDMODE_INVALID = 0x7FFFFFFFU; + + [DllImport("SDL3", ExactSpelling = true)] + [return: NativeTypeName("bool")] + public static extern byte SDL_GetSurfaceBlendMode( + SDL_Surface* surface, + [NativeTypeName("SDL_BlendMode *")] uint* blendMode + ); + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"SDL3/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The constants should have been moved from the Sdl clas to the SDL_BlendMode enum + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task SuccessfullyExtractsCStyleEnumConstants_ReturnType() + { + var inputDocName = "Sdl.gen.cs"; + var project = TestUtils + .CreateTestProject() + .AddDocument( + inputDocName, + """ + public unsafe partial struct Sdl + { + [NativeTypeName("#define SDL_BLENDMODE_NONE 0x00000000u")] + public const uint SDL_BLENDMODE_NONE = 0x00000000U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND 0x00000001u")] + public const uint SDL_BLENDMODE_BLEND = 0x00000001U; + + [NativeTypeName("#define SDL_BLENDMODE_BLEND_PREMULTIPLIED 0x00000010u")] + public const uint SDL_BLENDMODE_BLEND_PREMULTIPLIED = 0x00000010U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD 0x00000002u")] + public const uint SDL_BLENDMODE_ADD = 0x00000002U; + + [NativeTypeName("#define SDL_BLENDMODE_ADD_PREMULTIPLIED 0x00000020u")] + public const uint SDL_BLENDMODE_ADD_PREMULTIPLIED = 0x00000020U; + + [NativeTypeName("#define SDL_BLENDMODE_MOD 0x00000004u")] + public const uint SDL_BLENDMODE_MOD = 0x00000004U; + + [NativeTypeName("#define SDL_BLENDMODE_MUL 0x00000008u")] + public const uint SDL_BLENDMODE_MUL = 0x00000008U; + + [NativeTypeName("#define SDL_BLENDMODE_INVALID 0x7FFFFFFFu")] + public const uint SDL_BLENDMODE_INVALID = 0x7FFFFFFFU; + + [DllImport("SDL3", ExactSpelling = true)] + [return: NativeTypeName("SDL_BlendMode")] + public static extern uint SDL_ComposeCustomBlendMode( + SDL_BlendFactor srcColorFactor, + SDL_BlendFactor dstColorFactor, + SDL_BlendOperation colorOperation, + SDL_BlendFactor srcAlphaFactor, + SDL_BlendFactor dstAlphaFactor, + SDL_BlendOperation alphaOperation + ); + } + """, + // ExtractNestedTyping requires the file path to be set and that the document is under a subfolder + filePath: $"SDL3/{inputDocName}" + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var extractNestedTyping = new ExtractNestedTyping(NullLogger.Instance); + + await extractNestedTyping.ExecuteAsync(context); + + // The constants should have been moved from the Sdl clas to the SDL_BlendMode enum + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } +} diff --git a/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.SuccessfullyInterceptsRequestedFunction.verified.txt b/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.SuccessfullyInterceptsRequestedFunction.verified.txt new file mode 100644 index 0000000000..f956110e13 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.SuccessfullyInterceptsRequestedFunction.verified.txt @@ -0,0 +1,11 @@ +// Vk.gen.cs +public class Vk +{ + [DllImport("vulkan", ExactSpelling = true, EntryPoint = "vkCreateInstance")] + [NameAffix("Suffix", "InterceptedFunction", "Internal")] + private static extern VkResult vkCreateInstanceInternal(VkInstanceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, VkInstance_T** pInstance); + [DllImport("vulkan", ExactSpelling = true)] + public static extern VkResult vkCreateDevice(VkPhysicalDevice_T physicalDevice, VkDeviceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, VkDevice_T* pDevice); + [NativeFunction("vulkan", EntryPoint = "vkCreateInstance")] + public static partial VkResult vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, VkInstance_T** pInstance); +} diff --git a/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.cs b/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.cs new file mode 100644 index 0000000000..af0c31a491 --- /dev/null +++ b/tests/SilkTouch/SilkTouch/InterceptNativeFunctionsTests.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Silk.NET.SilkTouch.Mods; + +namespace Silk.NET.SilkTouch.UnitTests; + +public class InterceptNativeFunctionsTests +{ + static InterceptNativeFunctionsTests() + { + if (!VerifyDiffPlex.Initialized) + { + VerifyDiffPlex.Initialize(); + } + } + + [Test] + public async Task SuccessfullyInterceptsRequestedFunction() + { + var project = TestUtils + .CreateTestProject() + .AddDocument( + "Vk.gen.cs", + """ + public class Vk + { + [DllImport("vulkan", ExactSpelling = true)] + public static extern VkResult vkCreateInstance( + VkInstanceCreateInfo* pCreateInfo, + VkAllocationCallbacks* pAllocator, + VkInstance_T** pInstance + ); + + [DllImport("vulkan", ExactSpelling = true)] + public static extern VkResult vkCreateDevice( + VkPhysicalDevice_T physicalDevice, + VkDeviceCreateInfo* pCreateInfo, + VkAllocationCallbacks* pAllocator, + VkDevice_T* pDevice + ); + } + """ + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var interceptNativeFunctions = new InterceptNativeFunctions( + new DummyOptions( + new InterceptNativeFunctions.Configuration() + { + NativeFunctionNames = ["vkCreateInstance"], + } + ) + ); + + await interceptNativeFunctions.ExecuteAsync(context); + + // vkCreateInstance should be intercepted by suffixing the original with -Internal + // and by adding a replacement that makes use of the partial keyword + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } +} diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesImpliedVendorSuffixes.verified.txt b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesImpliedVendorSuffixes.verified.txt index ace8f8d1dc..e82c6a1a12 100644 --- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesImpliedVendorSuffixes.verified.txt +++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesImpliedVendorSuffixes.verified.txt @@ -1,8 +1,9 @@ -[NameAffix("Suffix", "KhronosVendor", "NV")] +// OcclusionQueryParameterNameNV.gen.cs +[NameAffix("Suffix", "KhronosVendor", "NV")] public enum OcclusionQueryParameterNameNV { [NameAffix("Suffix", "KhronosImpliedVendor", "NV")] GL_PIXEL_COUNT_NV = 34918, [NameAffix("Suffix", "KhronosImpliedVendor", "NV")] GL_PIXEL_COUNT_AVAILABLE_NV = 34919 -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNamespaceEnumPrefix.verified.txt b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNamespaceEnumPrefix.verified.txt index 4a30909ca0..b86e2f5d1c 100644 --- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNamespaceEnumPrefix.verified.txt +++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNamespaceEnumPrefix.verified.txt @@ -1,5 +1,6 @@ -[NativeName("GLenum")] +// GLEnum.gen.cs +[NativeName("GLenum")] [NameAffix("Prefix", "KhronosNamespaceEnum", "GL")] public enum GLEnum { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNonExclusiveVendorSuffixes.verified.txt b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNonExclusiveVendorSuffixes.verified.txt index e927fc9c32..ccdd71932d 100644 --- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNonExclusiveVendorSuffixes.verified.txt +++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesNonExclusiveVendorSuffixes.verified.txt @@ -1,6 +1,7 @@ -[NameAffix("Suffix", "KhronosNonExclusiveVendor", "ARB")] +// BufferUsageARB.gen.cs +[NameAffix("Suffix", "KhronosNonExclusiveVendor", "ARB")] public enum BufferUsageARB : uint { GL_STREAM_DRAW = 35040, GL_STREAM_READ = 35041, -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesVendorSuffixes.verified.txt b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesVendorSuffixes.verified.txt index 4abf6926d5..13bd9282c0 100644 --- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesVendorSuffixes.verified.txt +++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.IdentifiesVendorSuffixes.verified.txt @@ -1,8 +1,9 @@ -[NameAffix("Suffix", "KhronosVendor", "NV")] +// OcclusionQueryParameterNameNV.gen.cs +[NameAffix("Suffix", "KhronosVendor", "NV")] public enum OcclusionQueryParameterNameNV { [NameAffix("Suffix", "KhronosVendor", "NV")] GL_PIXEL_COUNT_NV = 34918, [NameAffix("Suffix", "KhronosVendor", "NV")] GL_PIXEL_COUNT_AVAILABLE_NV = 34919, -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs index e3be2fda66..d5075413d4 100644 --- a/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs +++ b/tests/SilkTouch/SilkTouch/Khronos/MixKhronosDataTests.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Silk.NET.BuildTools.Common; @@ -328,8 +327,7 @@ public enum OcclusionQueryParameterNameNV await mixKhronosData.ExecuteAsync(context); // There should be 3 NV suffixes identified - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -370,8 +368,7 @@ public enum OcclusionQueryParameterNameNV // The NV suffix on the type name should be identified as KhronosVendor // The NV suffixes on the member names should be identified as KhronosImpliedVendor - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -412,8 +409,7 @@ public enum BufferUsageARB : uint // The ARB suffix on the type name should be identified as KhronosNonExclusiveVendor // This is because the enum group contains core enums - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -464,7 +460,6 @@ public enum GLEnum { } // The ARB suffix on the type name should be identified as KhronosNonExclusiveVendor // This is because the enum group contains core enums - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } } diff --git a/tests/SilkTouch/SilkTouch/MetadataUtilsTests.cs b/tests/SilkTouch/SilkTouch/MetadataUtilsTests.cs index 6e75bc5d49..1a5c8194d3 100644 --- a/tests/SilkTouch/SilkTouch/MetadataUtilsTests.cs +++ b/tests/SilkTouch/SilkTouch/MetadataUtilsTests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using NUnit.Framework; using Silk.NET.SilkTouch.Mods.Metadata; namespace Silk.NET.SilkTouch.UnitTests; diff --git a/tests/SilkTouch/SilkTouch/ModUtilsTests.cs b/tests/SilkTouch/SilkTouch/ModUtilsTests.cs index d03b7a130d..7d857207fd 100644 --- a/tests/SilkTouch/SilkTouch/ModUtilsTests.cs +++ b/tests/SilkTouch/SilkTouch/ModUtilsTests.cs @@ -1,4 +1,3 @@ -using NUnit.Framework; using Silk.NET.SilkTouch.Mods; namespace Silk.NET.SilkTouch.UnitTests; diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNoHint.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNoHint.verified.txt index 5b3fe30996..c1fbfee940 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNoHint.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNoHint.verified.txt @@ -1,3 +1,4 @@ -public enum VkPresentModeKHR +// VkPresentModeKHR.gen.cs +public enum VkPresentModeKHR { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNonMatchingHint.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNonMatchingHint.verified.txt index 7fa1bf9947..5d70d006ee 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNonMatchingHint.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.DoesNotIdentifyPrefix_WhenSingleName_WithNonMatchingHint.verified.txt @@ -1,3 +1,4 @@ -public enum OcclusionQueryParameterNameNV +// OcclusionQueryParameterNameNV.gen.cs +public enum OcclusionQueryParameterNameNV { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesPrefix_WhenMatchingHint.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesPrefix_WhenMatchingHint.verified.txt index 3e84535354..e93a2b28fc 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesPrefix_WhenMatchingHint.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesPrefix_WhenMatchingHint.verified.txt @@ -1,4 +1,5 @@ -[NameAffix("Prefix", "SharedPrefix", "Vk")] +// VkPresentModeKHR.gen.cs +[NameAffix("Prefix", "SharedPrefix", "Vk")] public enum VkPresentModeKHR { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix.verified.txt index fda9eee7f4..3022441b38 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix.verified.txt @@ -1,7 +1,8 @@ -public enum OcclusionQueryParameterNameNV +// OcclusionQueryParameterNameNV.gen.cs +public enum OcclusionQueryParameterNameNV { [NameAffix("Prefix", "SharedPrefix", "GL_PIXEL_COUNT")] GL_PIXEL_COUNT_NV = 34918, [NameAffix("Prefix", "SharedPrefix", "GL_PIXEL_COUNT")] GL_PIXEL_COUNT_AVAILABLE_NV = 34919 -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix2.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix2.verified.txt index e667f964d3..d764880fb3 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix2.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix2.verified.txt @@ -1,4 +1,5 @@ -public enum VocalMorpherPhoneme +// VocalMorpherPhoneme.gen.cs +public enum VocalMorpherPhoneme { [NameAffix("Prefix", "SharedPrefix", "AL_VOCAL_MORPHER_PHONEME")] AL_VOCAL_MORPHER_PHONEME_A = 0, @@ -6,4 +7,4 @@ AL_VOCAL_MORPHER_PHONEME_E = 1, [NameAffix("Prefix", "SharedPrefix", "AL_VOCAL_MORPHER_PHONEME")] AL_VOCAL_MORPHER_PHONEME_I = 2 -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=glfw.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=glfw.verified.txt index 80b5bf0e4f..52ed739f78 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=glfw.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=glfw.verified.txt @@ -1,4 +1,5 @@ -public struct Glfw; +// Glfw.gen.cs +public struct Glfw; [NameAffix("Prefix", "SharedPrefix", "GLFW")] public struct GLFWallocator; [NameAffix("Prefix", "SharedPrefix", "GLFW")] @@ -14,4 +15,4 @@ public struct GLFWmonitor; [NameAffix("Prefix", "SharedPrefix", "GLFW")] public struct GLFWvidmode; [NameAffix("Prefix", "SharedPrefix", "GLFW")] -public struct GLFWwindow; \ No newline at end of file +public struct GLFWwindow; diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=null.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=null.verified.txt index 80b5bf0e4f..52ed739f78 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=null.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefixGlfw_hint=null.verified.txt @@ -1,4 +1,5 @@ -public struct Glfw; +// Glfw.gen.cs +public struct Glfw; [NameAffix("Prefix", "SharedPrefix", "GLFW")] public struct GLFWallocator; [NameAffix("Prefix", "SharedPrefix", "GLFW")] @@ -14,4 +15,4 @@ public struct GLFWmonitor; [NameAffix("Prefix", "SharedPrefix", "GLFW")] public struct GLFWvidmode; [NameAffix("Prefix", "SharedPrefix", "GLFW")] -public struct GLFWwindow; \ No newline at end of file +public struct GLFWwindow; diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_ForTypes.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_ForTypes.verified.txt index ae42e933ba..03f006e9fe 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_ForTypes.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_ForTypes.verified.txt @@ -1,4 +1,5 @@ -[NameAffix("Prefix", "SharedPrefix", "VkPresent")] +// Vk.gen.cs +[NameAffix("Prefix", "SharedPrefix", "VkPresent")] public enum VkPresentModeKHR { } @@ -6,4 +7,4 @@ public enum VkPresentModeKHR [NameAffix("Prefix", "SharedPrefix", "VkPresent")] public enum VkPresentIdKHR { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenAffixesDeclared_AndNamesWithoutAffixesConflict.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenAffixesDeclared_AndNamesWithoutAffixesConflict.verified.txt index cf2cfb5ef8..83b43a5009 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenAffixesDeclared_AndNamesWithoutAffixesConflict.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenAffixesDeclared_AndNamesWithoutAffixesConflict.verified.txt @@ -1,4 +1,5 @@ -[NameAffix("Suffix", "KhronosVendor", "KHR")] +// VkPresentModeKHR.gen.cs +[NameAffix("Suffix", "KhronosVendor", "KHR")] public enum VkPresentModeKHR { [NameAffix("Prefix", "SharedPrefix", "VK_PRESENT_MODE_FIFO_LATEST")] @@ -7,4 +8,4 @@ public enum VkPresentModeKHR [NameAffix("Prefix", "SharedPrefix", "VK_PRESENT_MODE_FIFO_LATEST")] [NameAffix("Suffix", "KhronosVendor", "EXT")] VK_PRESENT_MODE_FIFO_LATEST_READY_EXT = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared.verified.txt new file mode 100644 index 0000000000..b6abaa5eab --- /dev/null +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared.verified.txt @@ -0,0 +1,6 @@ +// D3D12.gen.cs +[NameAffix("Prefix", "SharedPrefix", "D3D12")] +public struct D3D12_BUFFER_BARRIER; +[NameAffix("Prefix", "SharedPrefix", "D3D12")] +[NameAffix("Prefix", "Interface", "I")] +public struct ID3D12Device; diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared_WithHint.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared_WithHint.verified.txt new file mode 100644 index 0000000000..219a459f2f --- /dev/null +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenPrefixesDeclared_WithHint.verified.txt @@ -0,0 +1,4 @@ +// D3D12.gen.cs +[NameAffix("Prefix", "SharedPrefix", "D3D12")] +[NameAffix("Prefix", "Interface", "I")] +public struct ID3D12Device; diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenSuffixesDeclared.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenSuffixesDeclared.verified.txt index 15ef517d5e..20cbe58284 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenSuffixesDeclared.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.IdentifiesSharedPrefix_WhenSuffixesDeclared.verified.txt @@ -1,4 +1,5 @@ -public enum OcclusionQueryParameterNameNV +// OcclusionQueryParameterNameNV.gen.cs +public enum OcclusionQueryParameterNameNV { [NameAffix("Prefix", "SharedPrefix", "GL_PIXEL")] [NameAffix("Suffix", "KhronosVendor", "NV")] @@ -6,4 +7,4 @@ [NameAffix("Prefix", "SharedPrefix", "GL_PIXEL")] [NameAffix("Suffix", "KhronosVendor", "NV")] GL_PIXEL_COUNT_AVAILABLE_NV = 34919 -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.MultipleGlobalPrefixHints.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.MultipleGlobalPrefixHints.verified.txt index 72bc5da1a1..64ebe57c7d 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.MultipleGlobalPrefixHints.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.MultipleGlobalPrefixHints.verified.txt @@ -1,5 +1,6 @@ -public enum ContextFlagsEXT +// ContextFlagsEXT.gen.cs +public enum ContextFlagsEXT { [NameAffix("Prefix", "SharedPrefix", "ALC")] ALC_CONTEXT_DEBUG_BIT_EXT -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.PreserveKhronosNamespaceEnumPrefix.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.PreserveKhronosNamespaceEnumPrefix.verified.txt index a463c66a73..5c09c256b9 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.PreserveKhronosNamespaceEnumPrefix.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.PreserveKhronosNamespaceEnumPrefix.verified.txt @@ -1,4 +1,5 @@ -[NameAffix("Prefix", "KhronosNamespaceEnum", "GL")] +// GLEnum.gen.cs +[NameAffix("Prefix", "KhronosNamespaceEnum", "GL")] public enum GLEnum { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionEvalTargetNV.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionEvalTargetNV.verified.txt index ce47f6c2b4..9eeca54654 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionEvalTargetNV.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionEvalTargetNV.verified.txt @@ -1,7 +1,8 @@ -public enum EvalTargetNV +// EvalTargetNV.gen.cs +public enum EvalTargetNV { [NameAffix("Prefix", "SharedPrefix", "GL")] GL_EVAL_2D_NV, [NameAffix("Prefix", "SharedPrefix", "GL")] GL_EVAL_TRIANGULAR_2D_NV -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionFragmentShaderColorModMaskATI.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionFragmentShaderColorModMaskATI.verified.txt index c705fa7e85..ba371ad66b 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionFragmentShaderColorModMaskATI.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionFragmentShaderColorModMaskATI.verified.txt @@ -1,4 +1,5 @@ -public enum FragmentShaderDestModMask +// FragmentShaderDestModMask.gen.cs +public enum FragmentShaderDestModMask { [NameAffix("Prefix", "SharedPrefix", "GL")] GL_2X_BIT_ATI, @@ -8,4 +9,4 @@ GL_NEGATE_BIT_ATI, [NameAffix("Prefix", "SharedPrefix", "GL")] GL_BIAS_BIT_ATI -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionSingleMemberEnumUsesGlobalPrefixHint.verified.txt b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionSingleMemberEnumUsesGlobalPrefixHint.verified.txt index 129f94dbeb..43c9b15b78 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionSingleMemberEnumUsesGlobalPrefixHint.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.RegressionSingleMemberEnumUsesGlobalPrefixHint.verified.txt @@ -1,5 +1,6 @@ -public enum EvalMapsModeNV +// EvalMapsModeNV.gen.cs +public enum EvalMapsModeNV { [NameAffix("Prefix", "SharedPrefix", "GL")] GL_FILL_NV -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.cs b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.cs index c87e545903..6691c47d17 100644 --- a/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.cs +++ b/tests/SilkTouch/SilkTouch/Naming/IdentifySharedPrefixesTests.cs @@ -45,8 +45,7 @@ public enum OcclusionQueryParameterNameNV // The prefix shared by the member names should be identified (GL_PIXEL_COUNT) // The type itself should be left untouched - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -79,8 +78,7 @@ public enum VocalMorpherPhoneme // The prefix shared by the member names should be identified (AL_VOCAL_MORPHER_PHONEME) // The type itself should be left untouched - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -92,7 +90,7 @@ public async Task IdentifiesSharedPrefixGlfw(string? hint) var project = TestUtils .CreateTestProject() .AddDocument( - "VocalMorpherPhoneme.gen.cs", + "Glfw.gen.cs", """ public struct Glfw; public struct GLFWallocator; @@ -122,8 +120,7 @@ public struct GLFWwindow; // The hint should not affect the output because the shared prefix is shared by most of the names // Glfw should not have a prefix - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -151,8 +148,7 @@ public enum VkPresentIdKHR { } await identifySharedPrefixes.ExecuteAsync(context); // The prefixes should be identified as "VkPresent", not "Vk" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -185,11 +181,68 @@ public enum OcclusionQueryParameterNameNV await identifySharedPrefixes.ExecuteAsync(context); - // The declaration of the 2 NV member suffixes should make PrettifyNames trim less of the member name + // The declaration of the 2 NV member suffixes should make IdentifySharedPrefixes identify less of the member name as the shared prefix // IdentifySharedPrefixes should only use the unaffixed name for prefix identification // The shared prefix should be "GL_PIXEL" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task IdentifiesSharedPrefix_WhenPrefixesDeclared() + { + var project = TestUtils + .CreateTestProject() + .AddDocument( + "D3D12.gen.cs", + """ + public struct D3D12_BUFFER_BARRIER; + + [NameAffix("Prefix", "Interface", "I")] + public struct ID3D12Device; + """ + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var identifySharedPrefixes = new IdentifySharedPrefixes( + new DummyOptions( + new IdentifySharedPrefixes.Configuration() + ) + ); + + await identifySharedPrefixes.ExecuteAsync(context); + + // The declaration of the I- prefix should lead to "D3D12" being identified as the shared prefix + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); + } + + [Test] + public async Task IdentifiesSharedPrefix_WhenPrefixesDeclared_WithHint() + { + var project = TestUtils + .CreateTestProject() + .AddDocument( + "D3D12.gen.cs", + """ + [NameAffix("Prefix", "Interface", "I")] + public struct ID3D12Device; + """ + ) + .Project; + + var context = new DummyModContext() { SourceProject = project }; + + var identifySharedPrefixes = new IdentifySharedPrefixes( + new DummyOptions( + new IdentifySharedPrefixes.Configuration() { GlobalPrefixHints = ["D3D12"] } + ) + ); + + await identifySharedPrefixes.ExecuteAsync(context); + + // The declaration of the I- prefix should lead to "D3D12" being identified as the shared prefix + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -370,8 +423,7 @@ public enum VkPresentModeKHR // This test should run without erroring // This is to catch potential regressions // where the names without affixes would conflict - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -393,8 +445,7 @@ public async Task IdentifiesPrefix_WhenMatchingHint() await identifySharedPrefixes.ExecuteAsync(context); // The type prefix should be identified as Vk - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -416,8 +467,7 @@ public async Task DoesNotIdentifyPrefix_WhenSingleName_WithNoHint() await identifySharedPrefixes.ExecuteAsync(context); // No prefix should be identified - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -442,8 +492,7 @@ public async Task DoesNotIdentifyPrefix_WhenSingleName_WithNonMatchingHint() await identifySharedPrefixes.ExecuteAsync(context); // No prefix should be identified - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -472,8 +521,7 @@ public enum GLEnum { } // The presence of the NameAffix attribute should prevent the GL- prefix of GLEnum from being identified as a shared prefix // This is because IdentifySharedPrefixes should only use the unaffixed name for prefix identification - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -507,8 +555,7 @@ public enum FragmentShaderDestModMask await identifySharedPrefixes.ExecuteAsync(context); // The identified prefix should be "GL" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -540,8 +587,7 @@ public enum EvalTargetNV await identifySharedPrefixes.ExecuteAsync(context); // The identified prefix should be "GL" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -572,8 +618,7 @@ public enum EvalMapsModeNV await identifySharedPrefixes.ExecuteAsync(context); // The identified prefix should be "GL" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -604,7 +649,6 @@ public enum ContextFlagsEXT await identifySharedPrefixes.ExecuteAsync(context); // The identified prefix should be "ALC" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } } diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.ConflictsAreResolved_ForMethodsAndConstants.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.ConflictsAreResolved_ForMethodsAndConstants.verified.txt index 82cbc8b186..d9dcdd33b2 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.ConflictsAreResolved_ForMethodsAndConstants.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.ConflictsAreResolved_ForMethodsAndConstants.verified.txt @@ -1,7 +1,8 @@ -public class Sdl +// Sdl.gen.cs +public class Sdl { public static delegate* MainValue => &Main; [NameAffix("Prefix", "SharedPrefix", "SDL")] public static extern int Main(int argc, sbyte** argv); -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly.verified.txt index 2fcf850618..deb6c464e6 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly.verified.txt @@ -1,4 +1,5 @@ -public class AL +// AL.gen.cs +public class AL { [NameAffix("Prefix", "SharedPrefix", "al")] [NameAffix("Suffix", "KhronosNonVendorSuffix", "Direct")] @@ -14,4 +15,4 @@ public void GetBufferPtrvDirectSOFT() { } -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly_ReversedPriority.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly_ReversedPriority.verified.txt index dbf9e98f30..33f6d331b2 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly_ReversedPriority.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.FallbackIsChosenCorrectly_ReversedPriority.verified.txt @@ -1,4 +1,5 @@ -public class AL +// AL.gen.cs +public class AL { [NameAffix("Prefix", "SharedPrefix", "al")] [NameAffix("Suffix", "KhronosNonVendorSuffix", "Direct")] @@ -14,4 +15,4 @@ public void alGetBufferPtrDirectSOFT() { } -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.InconsistentCasing_LettersFollowingNumbers_WhenAffixesDeclared.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.InconsistentCasing_LettersFollowingNumbers_WhenAffixesDeclared.verified.txt index 8721f7cebd..5f50bddb0a 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.InconsistentCasing_LettersFollowingNumbers_WhenAffixesDeclared.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.InconsistentCasing_LettersFollowingNumbers_WhenAffixesDeclared.verified.txt @@ -1,4 +1,5 @@ -public enum GLEnum +// Test.gen.cs +public enum GLEnum { [NameAffix("Prefix", "SharedPrefix", "GL")] [NameAffix("Suffix", "KhronosVendor", "EXT")] @@ -16,4 +17,4 @@ public enum ALEnum [NameAffix("Prefix", "SharedPrefix", "AL")] [NameAffix("Suffix", "KhronosVendor", "SOFT")] Mono32FSOFT = 65552, -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.Regression_UnexpectedCasingChangesInFormatEnums.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.Regression_UnexpectedCasingChangesInFormatEnums.verified.txt index a7cb359827..8277416872 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.Regression_UnexpectedCasingChangesInFormatEnums.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.Regression_UnexpectedCasingChangesInFormatEnums.verified.txt @@ -1,4 +1,5 @@ -public enum InternalFormat +// InternalFormat.gen.cs +public enum InternalFormat { [NameAffix("Prefix", "SharedPrefix", "GL")] [NameAffix("Suffix", "KhronosVendor", "ARB")] @@ -6,4 +7,4 @@ [NameAffix("Prefix", "SharedPrefix", "GL")] [NameAffix("Suffix", "KhronosVendor", "ARB")] Rgb32FARB = 34837, -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes.verified.txt index df3e0cb7be..ad9f0fe846 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes.verified.txt @@ -1,4 +1,5 @@ -[NameAffix("Suffix", "Test", "ShouldBeInOutputName")] +// SDL.gen.cs +[NameAffix("Suffix", "Test", "ShouldBeInOutputName")] public struct GamepadBindingShouldBeInOutputName { } @@ -11,4 +12,4 @@ public struct GamepadBindingShouldBeInOutputNameInput [NameAffix("Prefix", "NestedStructParent", nameof(GamepadBindingShouldBeInOutputNameInput))] public struct GamepadBindingShouldBeInOutputNameInputAxis { -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_FromParentScope.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_FromParentScope.verified.txt index 7b445817ef..79d4eb7f07 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_FromParentScope.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_FromParentScope.verified.txt @@ -1,6 +1,7 @@ -[NameAffix("Suffix", "Test", "Suffix")] +// Test.gen.cs +[NameAffix("Suffix", "Test", "Suffix")] public struct ASuffix { [NameAffix("Suffix", "Test", nameof(ASuffix))] public static int BASuffix; -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_WhenOverridden.verified.txt b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_WhenOverridden.verified.txt index 4a49dc4b7a..fb246d5e41 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_WhenOverridden.verified.txt +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.SuccessfullyUsesReferencedAffixes_WhenOverridden.verified.txt @@ -1,4 +1,5 @@ -public struct BufferCallbackSOFT; +// AL.gen.cs +public struct BufferCallbackSOFT; [NameAffix("Prefix", "FunctionPointerParent", nameof(BufferCallbackSOFT))] [NameAffix("Suffix", "FunctionPointerDelegateType", "Delegate")] -public delegate int BufferCallbackSOFTDelegate(); \ No newline at end of file +public delegate int BufferCallbackSOFTDelegate(); diff --git a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.cs b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.cs index 57a296fbec..23259aca7d 100644 --- a/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.cs +++ b/tests/SilkTouch/SilkTouch/Naming/PrettifyNamesTests.cs @@ -1,7 +1,6 @@ // 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.Extensions.Logging.Abstractions; using Silk.NET.SilkTouch.Mods; @@ -66,8 +65,7 @@ public enum InternalFormat // // While the core issue is already covered by another test, // this test is kept because the format enums tend to be a bit sensitive to codebase changes - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -129,8 +127,7 @@ public enum ALEnum // NameUtilsTests.Prettify_Capital_AfterNumber_DoesNotAffect_PreviousWord tests for the underlying issue // // Note that the NameAffix attributes do affect the output - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -198,8 +195,7 @@ public void alGetBufferPtrvDirectSOFT() { } // The expected output is: // GetBufferPtrDirectSOFT // GetBufferPtrvDirectSOFT - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -265,8 +261,7 @@ public void alGetBufferPtrvDirectSOFT() { } // The expected output is: // GetBufferPtrDirectSOFT // alGetBufferPtrDirectSOFT (affixes are not prettified by default) - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -299,8 +294,7 @@ public struct GamepadBindingInputAxis { } await prettifyNames.ExecuteAsync(context); // All names should start with GamepadBindingShouldBeInOutputName - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -334,8 +328,7 @@ public struct A // A should become ASuffix // B should become BASuffix - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -370,8 +363,7 @@ public struct ALBUFFERCALLBACKTYPESOFT; await prettifyNames.ExecuteAsync(context); // Both names should be affected by the override - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -481,7 +473,6 @@ public class Sdl // Expected: // Property is named "MainValue" // Method is named "Main" - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } } diff --git a/tests/SilkTouch/SilkTouch/TestUtils.cs b/tests/SilkTouch/SilkTouch/TestUtils.cs index f6564f8cc0..06f7864f6f 100644 --- a/tests/SilkTouch/SilkTouch/TestUtils.cs +++ b/tests/SilkTouch/SilkTouch/TestUtils.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; +using System.Text; using Microsoft.CodeAnalysis; namespace Silk.NET.SilkTouch.UnitTests; @@ -16,4 +18,30 @@ public static Project CreateTestProject() => "TestAssembly", LanguageNames.CSharp ); + + public static async Task VerifyDocumentsAsync( + IEnumerable documents, + [CallerFilePath] string sourcePath = "" + ) + { + var builder = new StringBuilder(); + var isFirst = true; + foreach (var document in documents.OrderBy(doc => doc.Name)) + { + if (!isFirst) + { + builder.AppendLine(); + } + + isFirst = false; + + builder.Append("// "); + builder.AppendLine(document.Name); + + var root = await document.GetSyntaxRootAsync(); + builder.AppendLine(root!.NormalizeWhitespace().ToString()); + } + + await Verify(builder.ToString(), sourceFile: sourcePath); + } } diff --git a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_MaybeBool_FieldsAndProperties.verified.txt b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_MaybeBool_FieldsAndProperties.verified.txt index 54c5c448bc..d44d4011d6 100644 --- a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_MaybeBool_FieldsAndProperties.verified.txt +++ b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_MaybeBool_FieldsAndProperties.verified.txt @@ -1,4 +1,5 @@ -public struct Transform +// Test.gen.cs +public struct Transform { [NativeTypeName("TestBool32")] public MaybeBool OptionField; @@ -21,4 +22,4 @@ public struct NoTransform [NativeTypeName("TestBool32 : 1")] public uint OptionAutoProperty { get; set; } -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_Utf8String_StaticConstProperties.verified.txt b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_Utf8String_StaticConstProperties.verified.txt index b67ed9fbf4..017ea0765e 100644 --- a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_Utf8String_StaticConstProperties.verified.txt +++ b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.Transforms_Utf8String_StaticConstProperties.verified.txt @@ -1,4 +1,5 @@ -public struct Test +// Test.gen.cs +public struct Test { public static Utf8String Text => "Hello world!"u8; -} \ No newline at end of file +} diff --git a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.cs b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.cs index 7b92efeb61..d852356f5f 100644 --- a/tests/SilkTouch/SilkTouch/TransformPropertiesTests.cs +++ b/tests/SilkTouch/SilkTouch/TransformPropertiesTests.cs @@ -1,7 +1,6 @@ // 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 Silk.NET.SilkTouch.Mods; namespace Silk.NET.SilkTouch.UnitTests; @@ -43,8 +42,7 @@ public struct Test await transformProperties.ExecuteAsync(context); // Test.Text should be transformed to use the Utf8String type - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } [Test] @@ -99,7 +97,6 @@ public struct NoTransform // // Note: [NativeTypeName("TestBool32 : 1")] can be found in bitfield structs // This is currently not handled since this is an unlikely case - var result = await context.SourceProject.Documents.First().GetSyntaxRootAsync(); - await Verify(result!.NormalizeWhitespace().ToString()); + await TestUtils.VerifyDocumentsAsync(context.SourceProject.Documents); } }