From 4a1aaf6cb9893064dbc9f9586be149d996fe9b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Tue, 18 Nov 2025 18:03:28 +0100 Subject: [PATCH 01/15] Added source generator skeleton --- .../TypeRegistrationGenerator.cs | 70 +++++++++++++++++++ .../build.bat | 1 + .../build.sh | 1 + 3 files changed, 72 insertions(+) create mode 100644 Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs create mode 100644 Tools/Unity.InputSystem.SourceGenerator/build.bat create mode 100755 Tools/Unity.InputSystem.SourceGenerator/build.sh diff --git a/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs b/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs new file mode 100644 index 0000000000..96e1d3575a --- /dev/null +++ b/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs @@ -0,0 +1,70 @@ +// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via +// in-memory generated source performing manual registration of types. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using System.Threading; +using System.Xml.Linq; + +[Generator] +public sealed class MyInterfaceGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Filter syntax + var typeDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax, + static (ctx, _) => (TypeDeclarationSyntax)ctx.Node) + .Where(t => t is not null); + + // Combine syntax with compilation so we can do symbol checks + var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); + + // Finally, register source output generator + context.RegisterSourceOutput(candidateTypes, static (spc, source) => + { + var (compilation, typeDecls) = source; + Process(spc, compilation, typeDecls); + }); + } + + private static void Process(SourceProductionContext context, Compilation compilation, + ImmutableArray declarations) + { + var interfaceSymbol = compilation.GetTypeByMetadataName("UnityEngine.InputSystem.IInputInteraction"); + if (interfaceSymbol is null) + return; // interface not in this compilation + + foreach (var decl in declarations) + { + // Get semantic model + var model = compilation.GetSemanticModel(decl.SyntaxTree); + if (model.GetDeclaredSymbol(decl) is not INamedTypeSymbol typeSymbol) + continue; + + // Skip if evaluated symbol is not implementing interface + if (!ImplementsInterface(typeSymbol, interfaceSymbol)) + continue; + + // Generate type registration code and add to source + var source = GenerateFor(typeSymbol); + context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); + } + } + + private static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol symbol) + => type.AllInterfaces.Contains(symbol); + //=> type.AllInterfaces.Contains(;, SymbolEqualityComparer.Default); + + private static string GenerateFor(INamedTypeSymbol type) + { + var sb = new StringBuilder(); + return sb.ToString(); + } +} diff --git a/Tools/Unity.InputSystem.SourceGenerator/build.bat b/Tools/Unity.InputSystem.SourceGenerator/build.bat new file mode 100644 index 0000000000..f8d017869c --- /dev/null +++ b/Tools/Unity.InputSystem.SourceGenerator/build.bat @@ -0,0 +1 @@ +dotnet build -c Debug && dotnet build -c Release \ No newline at end of file diff --git a/Tools/Unity.InputSystem.SourceGenerator/build.sh b/Tools/Unity.InputSystem.SourceGenerator/build.sh new file mode 100755 index 0000000000..f8d017869c --- /dev/null +++ b/Tools/Unity.InputSystem.SourceGenerator/build.sh @@ -0,0 +1 @@ +dotnet build -c Debug && dotnet build -c Release \ No newline at end of file From 7d5022f214c83ac7c487e131c3e152167cba4d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Thu, 20 Nov 2025 10:47:07 +0100 Subject: [PATCH 02/15] More work on source generator for type registration --- .../Unity.InputSystem.SourceGenerator.dll | Bin 0 -> 14336 bytes ...Unity.InputSystem.SourceGenerator.dll.meta | 75 ++++++++ .../.gitignore | 1 + .../InterfaceTypeRegistrationGenerator.cs | 179 ++++++++++++++++++ .../TypeRegistrationGenerator.cs | 70 ------- 5 files changed, 255 insertions(+), 70 deletions(-) create mode 100644 Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll create mode 100644 Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll.meta create mode 100644 Tools/Unity.InputSystem.SourceGenerator/.gitignore create mode 100644 Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs delete mode 100644 Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll b/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll new file mode 100644 index 0000000000000000000000000000000000000000..310b2795a1eb92816f4a33e591ee0541b3357e9a GIT binary patch literal 14336 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 Y_(FJKi}UUMlUIEqterFRbN0ah0U}c@kpKVy literal 0 HcmV?d00001 diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll.meta b/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll.meta new file mode 100644 index 0000000000..05512adc40 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll.meta @@ -0,0 +1,75 @@ +fileFormatVersion: 2 +guid: 0ef65cc9c692944d48f27bc53d62748a +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 3 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + Android: + enabled: 0 + settings: + AndroidLibraryDependee: UnityLibrary + AndroidSharedLibraryType: Executable + CPU: ARMv7 + Any: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + Exclude tvOS: 1 + Editor: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + Linux64: + enabled: 0 + settings: + CPU: AnyCPU + OSXUniversal: + enabled: 0 + settings: + CPU: AnyCPU + Win: + enabled: 0 + settings: + CPU: AnyCPU + Win64: + enabled: 0 + settings: + CPU: AnyCPU + WindowsStoreApps: + enabled: 0 + settings: + CPU: AnyCPU + iOS: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + tvOS: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tools/Unity.InputSystem.SourceGenerator/.gitignore b/Tools/Unity.InputSystem.SourceGenerator/.gitignore new file mode 100644 index 0000000000..7447f89a59 --- /dev/null +++ b/Tools/Unity.InputSystem.SourceGenerator/.gitignore @@ -0,0 +1 @@ +/bin \ No newline at end of file diff --git a/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs b/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs new file mode 100644 index 0000000000..cb56b593ff --- /dev/null +++ b/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs @@ -0,0 +1,179 @@ +// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via +// in-memory generated source performing manual registration of types. +// +// Potential further improvements: +// - Assembly filtering to narrow scope. +// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default); +// - Generate diagnostic warnings for types implementing interfaces with private visibility? +// - Improve generated type name generation to guarantee no clash. + +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Unity.InputSystem.SourceGenerator; + +/// +/// Syntax and symbol helpers. +/// +static class Helpers +{ + public static bool IsEffectivelyPublic(INamedTypeSymbol type) + { + // The type itself must be public + if (type.DeclaredAccessibility != Accessibility.Public) + return false; + + // Every containing type must also be public + for (var container = type.ContainingType; container is not null; container = container.ContainingType) + { + if (container.DeclaredAccessibility != Accessibility.Public) + return false; + } + + return true; + } + + public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol) + => type.AllInterfaces.Contains(interfaceSymbol); + + public static bool ExtendsClass(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) + => SymbolEqualityComparer.Default.Equals(type, baseSymbol); + + public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) + { + // If you *don't* want to match A itself, start from type.BaseType instead. + for (var current = type; current is not null; current = current.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(current, baseSymbol)) + return true; + } + + return false; + } +} + +[Generator] +public class InterfaceTypeRegistrationGenerator : IIncrementalGenerator +{ + internal const string RegistrationTemplateBegin = @"using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class @C +{ +#if UNITY_EDITOR + static @C() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void Register() + { + Debug.Log(""Auto-registering @T via source generated type @C"");"; + + internal const string RegistrationTemplateEnd = @" + } +} +"; + + private readonly string _interface; + private readonly string _template; + private readonly System.Func _accept; + + protected InterfaceTypeRegistrationGenerator(string @interface, string template, + System.Func accept) + { + _interface = @interface; + _template = template; + _accept = accept; + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Filter syntax + var typeDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax, + static (ctx, _) => (TypeDeclarationSyntax)ctx.Node) + .Where(t => t is not null); + + // Combine syntax with compilation so we can do symbol checks + var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); + + // Finally, register source output generator + context.RegisterSourceOutput(candidateTypes, (spc, source) => + { + var (compilation, typeDecls) = source; + Process(spc, compilation, typeDecls, _interface, _template, _accept); + }); + } + + private static void Process(SourceProductionContext context, Compilation compilation, + ImmutableArray declarations, string @interface, string template, + System.Func accept) + { + var interfaceSymbol = compilation.GetTypeByMetadataName(@interface); + if (interfaceSymbol is null) + return; // symbol not in this compilation + + foreach (var decl in declarations) + { + // Get semantic model + var model = compilation.GetSemanticModel(decl.SyntaxTree); + if (model.GetDeclaredSymbol(decl) is not { } typeSymbol) + continue; + + // Skip if we shouldn't accept type + if (!accept(typeSymbol, interfaceSymbol)) + continue; + + // Generate type registration code and add to source + var source = GenerateFor(typeSymbol, template); + context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); + } + } + + + + private static string GenerateFor(INamedTypeSymbol type, string template) + { + var fullyQualifiedTypeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return template.Replace("@C", type.Name + "Registration").Replace("@T", fullyQualifiedTypeName); + } +} + +/// +/// Source generator that registers public types implementing IInputProcessor. +/// +[Generator] +public sealed class ProcessorRegistration() : InterfaceTypeRegistrationGenerator(Base, Template, + static (symbol, baseSymbol) => Helpers.IsEffectivelyPublic(symbol) && + Helpers.IsOrInheritsFrom(symbol, baseSymbol)) +{ + private const string Base = "UnityEngine.InputSystem.InputProcessor"; + + private const string Template = RegistrationTemplateBegin + + " InputSystem.RegisterProcessor(typeof(@T));" + + RegistrationTemplateEnd; +} + +/// +/// Source generator that registers public types implementing IInputInteraction. +/// +[Generator] +public sealed class InteractionRegistration() : InterfaceTypeRegistrationGenerator(Interface, Template, + static (symbol, @interface) => Helpers.IsEffectivelyPublic(symbol) && + Helpers.ImplementsInterface(symbol, @interface)) +{ + private const string Interface = "UnityEngine.InputSystem.IInputInteraction"; + + private const string Template = RegistrationTemplateBegin + + " InputSystem.RegisterInteraction(typeof(@T));" + + RegistrationTemplateEnd; +} diff --git a/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs b/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs deleted file mode 100644 index 96e1d3575a..0000000000 --- a/Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via -// in-memory generated source performing manual registration of types. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Text; -using System.Threading; -using System.Xml.Linq; - -[Generator] -public sealed class MyInterfaceGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Filter syntax - var typeDeclarations = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax, - static (ctx, _) => (TypeDeclarationSyntax)ctx.Node) - .Where(t => t is not null); - - // Combine syntax with compilation so we can do symbol checks - var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); - - // Finally, register source output generator - context.RegisterSourceOutput(candidateTypes, static (spc, source) => - { - var (compilation, typeDecls) = source; - Process(spc, compilation, typeDecls); - }); - } - - private static void Process(SourceProductionContext context, Compilation compilation, - ImmutableArray declarations) - { - var interfaceSymbol = compilation.GetTypeByMetadataName("UnityEngine.InputSystem.IInputInteraction"); - if (interfaceSymbol is null) - return; // interface not in this compilation - - foreach (var decl in declarations) - { - // Get semantic model - var model = compilation.GetSemanticModel(decl.SyntaxTree); - if (model.GetDeclaredSymbol(decl) is not INamedTypeSymbol typeSymbol) - continue; - - // Skip if evaluated symbol is not implementing interface - if (!ImplementsInterface(typeSymbol, interfaceSymbol)) - continue; - - // Generate type registration code and add to source - var source = GenerateFor(typeSymbol); - context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); - } - } - - private static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol symbol) - => type.AllInterfaces.Contains(symbol); - //=> type.AllInterfaces.Contains(;, SymbolEqualityComparer.Default); - - private static string GenerateFor(INamedTypeSymbol type) - { - var sb = new StringBuilder(); - return sb.ToString(); - } -} From 989707c8bfa4bb19c893f7d4692f9d8e40061899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Thu, 20 Nov 2025 22:23:16 +0100 Subject: [PATCH 03/15] Source generator tweaks and improvements. --- .../InterfaceTypeRegistrationGenerator.cs | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs b/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs index cb56b593ff..f14feaafe9 100644 --- a/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs +++ b/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs @@ -7,6 +7,7 @@ // - Generate diagnostic warnings for types implementing interfaces with private visibility? // - Improve generated type name generation to guarantee no clash. +using System.Collections.Generic; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; @@ -16,11 +17,18 @@ namespace Unity.InputSystem.SourceGenerator; -/// -/// Syntax and symbol helpers. -/// static class Helpers { + private static readonly HashSet ExcludedAssemblies = + [ + "Unity.InputSystem" + ]; + + public static bool IsAcceptedAssembly(INamedTypeSymbol symbol) + { + return !ExcludedAssemblies.Contains(symbol.ContainingAssembly.Identity.Name); + } + public static bool IsEffectivelyPublic(INamedTypeSymbol type) { // The type itself must be public @@ -28,7 +36,9 @@ public static bool IsEffectivelyPublic(INamedTypeSymbol type) return false; // Every containing type must also be public - for (var container = type.ContainingType; container is not null; container = container.ContainingType) + for (var container = type.ContainingType; + container is not null; + container = container.ContainingType) { if (container.DeclaredAccessibility != Accessibility.Public) return false; @@ -40,9 +50,6 @@ public static bool IsEffectivelyPublic(INamedTypeSymbol type) public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol) => type.AllInterfaces.Contains(interfaceSymbol); - public static bool ExtendsClass(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) - => SymbolEqualityComparer.Default.Equals(type, baseSymbol); - public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) { // If you *don't* want to match A itself, start from type.BaseType instead. @@ -57,7 +64,7 @@ public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol base } [Generator] -public class InterfaceTypeRegistrationGenerator : IIncrementalGenerator +public class TypeRegistrationGenerator : IIncrementalGenerator { internal const string RegistrationTemplateBegin = @"using System; using UnityEngine; @@ -72,10 +79,10 @@ class @C static @C() { Register(); } #endif - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] static void Register() { - Debug.Log(""Auto-registering @T via source generated type @C"");"; + Debug.Log(""Registering @T (@A) via generated type @C"");"; internal const string RegistrationTemplateEnd = @" } @@ -86,7 +93,7 @@ static void Register() private readonly string _template; private readonly System.Func _accept; - protected InterfaceTypeRegistrationGenerator(string @interface, string template, + protected TypeRegistrationGenerator(string @interface, string template, System.Func accept) { _interface = @interface; @@ -134,30 +141,27 @@ private static void Process(SourceProductionContext context, Compilation compila continue; // Generate type registration code and add to source - var source = GenerateFor(typeSymbol, template); + var fullyQualifiedTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var source = template.Replace("@C", typeSymbol.Name + "Registration") + .Replace("@T", fullyQualifiedTypeName) + .Replace("@A", typeSymbol.ContainingAssembly.Identity.Name); + + // Finally, add source to compilation context context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); } } - - - - private static string GenerateFor(INamedTypeSymbol type, string template) - { - var fullyQualifiedTypeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return template.Replace("@C", type.Name + "Registration").Replace("@T", fullyQualifiedTypeName); - } } /// -/// Source generator that registers public types implementing IInputProcessor. +/// Source generator that registers public types extending InputProcessor. /// [Generator] -public sealed class ProcessorRegistration() : InterfaceTypeRegistrationGenerator(Base, Template, - static (symbol, baseSymbol) => Helpers.IsEffectivelyPublic(symbol) && +public sealed class InputProcessorRegistration() : TypeRegistrationGenerator(Base, Template, + static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && Helpers.IsOrInheritsFrom(symbol, baseSymbol)) { private const string Base = "UnityEngine.InputSystem.InputProcessor"; - private const string Template = RegistrationTemplateBegin + " InputSystem.RegisterProcessor(typeof(@T));" + RegistrationTemplateEnd; @@ -167,13 +171,28 @@ public sealed class ProcessorRegistration() : InterfaceTypeRegistrationGenerator /// Source generator that registers public types implementing IInputInteraction. /// [Generator] -public sealed class InteractionRegistration() : InterfaceTypeRegistrationGenerator(Interface, Template, - static (symbol, @interface) => Helpers.IsEffectivelyPublic(symbol) && +public sealed class InputInteractionRegistration() : TypeRegistrationGenerator(Interface, Template, + static (symbol, @interface) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && Helpers.ImplementsInterface(symbol, @interface)) { private const string Interface = "UnityEngine.InputSystem.IInputInteraction"; - private const string Template = RegistrationTemplateBegin + " InputSystem.RegisterInteraction(typeof(@T));" + RegistrationTemplateEnd; } + +/// +/// Source generator that registers public types derived from InputBindingComposite. +/// +[Generator] +public sealed class InputBindingCompositeRegistration() : TypeRegistrationGenerator(Base, Template, + static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && + Helpers.IsOrInheritsFrom(symbol, baseSymbol)) +{ + private const string Base = "UnityEngine.InputSystem.InputBindingComposite"; + private const string Template = RegistrationTemplateBegin + + " InputSystem.RegisterBindingComposite(typeof(@T), null);" + + RegistrationTemplateEnd; +} \ No newline at end of file From b1badc9b6d758199fcd89e9490b5b4251c5b2213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 13:36:17 +0100 Subject: [PATCH 04/15] Added automated test suite for source generator. --- Tools/Roslyn/.gitignore | 3 + Tools/Roslyn/README.md | 71 +++++++ .../IInputInteraction.cs | 5 + .../InputBindingComposite.cs | 5 + .../Unity.InputSystem.Mocks/InputProcessor.cs | 5 + ...MyBindingComposite_Generated.g.received.cs | 17 ++ ...MyBindingComposite_Generated.g.verified.cs | 17 ++ ...dsBase#MyProcessor_Generated.g.verified.cs | 1 + ...MyBindingComposite_Generated.g.received.cs | 17 ++ ...MyBindingComposite_Generated.g.verified.cs | 17 ++ ...aUsing#MyProcessor_Generated.g.verified.cs | 1 + ...utBindingCompositeTypeRegistrationTests.cs | 19 ++ ...action#MyProcessor_Generated.g.verified.cs | 17 ++ ...aUsing#MyProcessor_Generated.g.verified.cs | 17 ++ .../InputProcessorTypeRegistrationTests.cs | 19 ++ ...tion#MyInteraction_Generated.g.verified.cs | 17 ++ ...sing#MyInteraction_Generated.g.verified.cs | 17 ++ .../InteractionTypeRegistrationTests.cs | 19 ++ .../ModuleInitializer.cs | 8 + .../TestHelper.cs | 116 ++++++++++ .../Helpers.cs | 50 +++++ .../InputBindingCompositeRegistration.cs | 18 ++ .../InputInteractionRegistration.cs | 28 +++ .../InputProcessorRegistration.cs | 28 +++ .../TypeRegistrationGenerator.cs | 100 +++++++++ .../build.bat | 0 Tools/Roslyn/build.sh | 1 + Tools/Roslyn/coverlet.runsettings | 10 + Tools/Roslyn/test.bat | 1 + Tools/Roslyn/test.sh | 1 + .../.gitignore | 1 - .../InterfaceTypeRegistrationGenerator.cs | 198 ------------------ .../build.sh | 1 - 33 files changed, 645 insertions(+), 200 deletions(-) create mode 100644 Tools/Roslyn/.gitignore create mode 100644 Tools/Roslyn/README.md create mode 100644 Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyInteraction_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyInteraction_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/ModuleInitializer.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputInteractionRegistration.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs rename Tools/{Unity.InputSystem.SourceGenerator => Roslyn}/build.bat (100%) create mode 100755 Tools/Roslyn/build.sh create mode 100644 Tools/Roslyn/coverlet.runsettings create mode 100644 Tools/Roslyn/test.bat create mode 100755 Tools/Roslyn/test.sh delete mode 100644 Tools/Unity.InputSystem.SourceGenerator/.gitignore delete mode 100644 Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs delete mode 100755 Tools/Unity.InputSystem.SourceGenerator/build.sh diff --git a/Tools/Roslyn/.gitignore b/Tools/Roslyn/.gitignore new file mode 100644 index 0000000000..ca272a9ca2 --- /dev/null +++ b/Tools/Roslyn/.gitignore @@ -0,0 +1,3 @@ +*/bin +coverletlog* +TestResults \ No newline at end of file diff --git a/Tools/Roslyn/README.md b/Tools/Roslyn/README.md new file mode 100644 index 0000000000..8a809660b4 --- /dev/null +++ b/Tools/Roslyn/README.md @@ -0,0 +1,71 @@ +# README - Unity.InputSystem.SourceGenerator + +## Overview + +This directory contains source generators for the Unity Input System and associated automated tests. + +The source generator solution supports the following scenarios: +- Source generation for automatic type registration of custom interaction types implementing + [`UnityEngine.InputSystem.IInputInteraction`](../../Packages/com.unity.inputsystem/InputSystem/Actions/IInputInteraction.cs). +- Source generation for automatic type registration of custom processors derived from + [`UnityEngine.InputSystem.InputProcessor`](../../Packages/com.unity.inputsystem/InputSystem/Controls/InputProcessor.cs). +- Source generation for automatic type registration of custom composite bindings derived from + [`UnityEngine.InputSystem.InputBinding`](../../Packages/com.unity.inputsystem/InputSystem/Actions/InputBinding.cs). + +This allows generating required registration boilerplate code at compile-time instead of writing manual registration code. +In addition, it eliminates the need to rely on slow and memory consuming run-time operations using +[.NET Reflection API](https://learn.microsoft.com/en-us/dotnet/fundamentals/reflection/reflection). + +## How to build and run tests + +The simplest way to build the source generators are via the provided convenience scripts (internally using standard `dotnet` commands): + +To build using macOS or *nix to build both `Debug` and `Release` targets: +``` +./build.sh +``` +To build using Windows Command line prompt to build both `Debug` and `Release` targets: +``` +build +``` + +To run tests and generate a test coverage report using macOS or *nix, use: +``` +./test.sh +``` +To run tests and generate a test coverage report using Windows, use: +``` +test +``` + +To have more control over the build or to build individual targets, inspect the above mentioned scripts which +illustrate what `dotnet` commands are used. Consult [.NET CLI tools documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet) +for additional options. + +When building the source generator, binaries are located under `./bin/`. For `Release` builds, the +resulting binary is also automatically copied into the designated folder location of the Input System package. + +Note that you may have to install `.NET Runtime` or add it to path if test run fails with error and prompts you that `Microsoft.NETCore.App` is outdated or missing. + +## Dependencies + +The source generators themselves do not have any other dependencies than `.NET SDK` (including CLI tooling). + +Test packages have additional dependencies as follows: +- `Microsoft.NET.Test.Sdk` - Microsoft .NET test support. +- `NUnit` - NUnit testing framework. +- `NUnit.Analyzers` - Analyzer NUnit support. +- `NUnit3TestAdapter` - Adapter for running NUnit tests. +- `Microsoft.CodeAnalysis.CSharp` - Roslyn support. +- `Verify` - Diff-based verification tool that simplifies source generator testing by reviewing and accepting diffs as part of the development workflow. +- `Verify.NUnit` - NUnit adapter for `Verify` NuGet package. +- `Verify.SourceGenerators` - Source generator adapter for `Verify` NuGet package. + +All dependencies are managed via NuGet. + +## Distribution + +The resulting source generator binary is automatically copied into the Input System package. Note that the binary may +generate a diff even when nothing has changed due to timestamps within binary or due to compiler version differences. +It is recommended that the source generator binary is only commited as part of pull requests actually modifying +`Tools~/Roslyn/*`. diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs new file mode 100644 index 0000000000..851d45dcce --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs @@ -0,0 +1,5 @@ +// ReSharper disable CheckNamespace +namespace UnityEngine.InputSystem; +// ReSharper restore CheckNamespace + +public interface IInputInteraction { } \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs new file mode 100644 index 0000000000..2ec0d53d10 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs @@ -0,0 +1,5 @@ +// ReSharper disable CheckNamespace +namespace UnityEngine.InputSystem; +// ReSharper restore CheckNamespace + +public class InputBindingComposite { } \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs new file mode 100644 index 0000000000..159c86adc9 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs @@ -0,0 +1,5 @@ +// ReSharper disable CheckNamespace +namespace UnityEngine.InputSystem; +// ReSharper restore CheckNamespace + +public class InputProcessor { } \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs new file mode 100644 index 0000000000..79edd88565 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs @@ -0,0 +1,17 @@ +//HintName: MyBindingComposite_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyBindingCompositeRegistration +{ +#if UNITY_EDITOR + static MyBindingCompositeRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs new file mode 100644 index 0000000000..79edd88565 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyBindingComposite_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyBindingCompositeRegistration +{ +#if UNITY_EDITOR + static MyBindingCompositeRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs new file mode 100644 index 0000000000..79edd88565 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs @@ -0,0 +1,17 @@ +//HintName: MyBindingComposite_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyBindingCompositeRegistration +{ +#if UNITY_EDITOR + static MyBindingCompositeRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.verified.cs new file mode 100644 index 0000000000..79edd88565 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyBindingComposite_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyBindingCompositeRegistration +{ +#if UNITY_EDITOR + static MyBindingCompositeRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs new file mode 100644 index 0000000000..86700d8dcc --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs @@ -0,0 +1,19 @@ +namespace Unity.InputSystem.SourceGenerator.Tests; + +[TestFixture] +[Parallelizable(ParallelScope.All)] // Safe to run all tests in parallel +public class InputBindingCompositeTypeRegistrationTests +{ + private static Task Verify(string source) => TestHelper.Verify(new InputBindingCompositeRegistration(), + source, typeof(object), typeof(UnityEngine.InputSystem.InputBindingComposite)); + + [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); + + [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing() => + Verify(@"using UnityEngine.InputSystem; public class MyBindingComposite : InputBindingComposite { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBase() => + Verify("public class MyBindingComposite : UnityEngine.InputSystem.InputBindingComposite { }"); +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..fa7a07fac4 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyProcessor_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyProcessor_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyProcessorRegistration +{ +#if UNITY_EDITOR + static MyProcessorRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterProcessor(typeof(global::MyProcessor)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..fa7a07fac4 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyProcessor_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyProcessor_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyProcessorRegistration +{ +#if UNITY_EDITOR + static MyProcessorRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterProcessor(typeof(global::MyProcessor)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs new file mode 100644 index 0000000000..dce35caea1 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs @@ -0,0 +1,19 @@ +namespace Unity.InputSystem.SourceGenerator.Tests; + +[TestFixture] +[Parallelizable(ParallelScope.All)] // Safe to run all tests in parallel +public class InputProcessorTypeRegistrationTests +{ + private static Task Verify(string source) => TestHelper.Verify(new InputProcessorRegistration(), + source, typeof(object), typeof(UnityEngine.InputSystem.InputProcessor)); + + [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); + + [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing() => + Verify(@"using UnityEngine.InputSystem; public class MyProcessor : InputProcessor { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction() => + Verify("public class MyProcessor : UnityEngine.InputSystem.InputProcessor { }"); +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyInteraction_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyInteraction_Generated.g.verified.cs new file mode 100644 index 0000000000..3955f4ad62 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction#MyInteraction_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyInteraction_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyInteractionRegistration +{ +#if UNITY_EDITOR + static MyInteractionRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterInteraction(typeof(global::MyInteraction)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyInteraction_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyInteraction_Generated.g.verified.cs new file mode 100644 index 0000000000..3955f4ad62 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing#MyInteraction_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyInteraction_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyInteractionRegistration +{ +#if UNITY_EDITOR + static MyInteractionRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterInteraction(typeof(global::MyInteraction)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs new file mode 100644 index 0000000000..f633a9788e --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs @@ -0,0 +1,19 @@ +namespace Unity.InputSystem.SourceGenerator.Tests; + +[TestFixture] +[Parallelizable(ParallelScope.All)] // Safe to run all tests in parallel +public class InteractionTypeRegistrationTests +{ + private static Task Verify(string source) => TestHelper.Verify(new InputInteractionRegistration(), + source, typeof(object), typeof(UnityEngine.InputSystem.IInputInteraction)); + + [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); + + [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing() => + Verify(@"using UnityEngine.InputSystem; public class MyInteraction : IInputInteraction { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction() => + Verify(@"public class MyInteraction : UnityEngine.InputSystem.IInputInteraction { }"); +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/ModuleInitializer.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/ModuleInitializer.cs new file mode 100644 index 0000000000..0e98a722cb --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/ModuleInitializer.cs @@ -0,0 +1,8 @@ +namespace Unity.InputSystem.SourceGenerator.Tests; + +internal static class ModuleInitializer +{ + // Verify requires initialization. This is the recommended way. + [System.Runtime.CompilerServices.ModuleInitializer] + public static void Init() => VerifySourceGenerators.Initialize(); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs new file mode 100644 index 0000000000..c328cf0456 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs @@ -0,0 +1,116 @@ +using System.Collections.Immutable; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Unity.InputSystem.SourceGenerator.Tests; + +static class TestHelper +{ + private class DefaultDiagnosticFilter + { + private readonly DiagnosticSeverity _diagnosticSeverity; + + public DefaultDiagnosticFilter(DiagnosticSeverity minSeverity = DiagnosticSeverity.Warning) + { + _diagnosticSeverity = minSeverity; + } + + public bool Accept(Diagnostic diagnostic) + { + return diagnostic.Severity >= _diagnosticSeverity && + + // We want to ignore "error CS5001: Program does not contain a static 'Main' method suitable for an entry point" + // since its expected for this scenario. + diagnostic.Id != "CS5001"; + } + } + + public static IEnumerable FilterDiagnostics(ImmutableArray diagnostics, + Predicate filter) + { + for (int i = 0; i < diagnostics.Length; ++i) + { + if (filter(diagnostics[i])) + yield return diagnostics[i]; + } + } + + public static string DiagnosticsToString(ImmutableArray diagnostics) + { + if (diagnostics == null) + return string.Empty; + if (diagnostics.Length == 0) + return string.Empty; + + var builder = new StringBuilder(); + for (int i = 0; i < diagnostics.Length; ++i) + { + if (i > 0) + builder.Append('\n'); + builder.Append(diagnostics[i]); + } + return builder.ToString(); + } + + // public static Task Verify(string source) where T : IIncrementalGenerator, new() => + // Verify(new T(), source); + + // public static Task Verify(IIncrementalGenerator generator, string source, bool checkDriverResult = true) + // { + // return Verify(generator, source, ImmutableArray.Create(), checkDriverResult); + // } + + public static Task Verify(IIncrementalGenerator generator, string source, + IEnumerable references, bool checkDriverResult = true) + { + // Configure custom compilation to include optional references + var syntaxTree = CSharpSyntaxTree.ParseText(source); + var compilation = CSharpCompilation.Create( + assemblyName: "Tests", + syntaxTrees: new[] { syntaxTree }, + references: references); + + // Run generators + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGenerators(compilation); + + // Assert that compilation was successful and did not generate diagnostics. + // Note that warnings and errors related to not being a complete program may be expected. + // E.g. warning CS0649: Field 'MyInputStruct.length' is never assigned to, and will always have its default value 0. + var compilationDiagnostics = compilation.GetDiagnostics(); + if (compilationDiagnostics.Length > 0) + { + var filter = new DefaultDiagnosticFilter(); + var filteredDiagnostics = FilterDiagnostics(compilationDiagnostics, + (d) => filter.Accept(d)).ToImmutableArray(); + Assert.That(filteredDiagnostics.Length, Is.EqualTo(0), + DiagnosticsToString(filteredDiagnostics)); + } + + // Optionally check returned driver result (This may generate warnings for referenced assemblies if any) + if (checkDriverResult) + { + var result = driver.GetRunResult(); + Assert.That(result.Diagnostics.Length, Is.EqualTo(0)); + } + + // Pass the driver to Verify for output verification + return Verifier.Verify(driver); + } + + public static Task Verify(IIncrementalGenerator generator, string source, params Type[] types) + { + // Construct references to assembly locations and then utilize referenced assemblies to solve + // problem with referencing "implementation assemblies" instead of "reference assemblies". + var referencedAssemblies = Assembly.GetEntryAssembly()!.GetReferencedAssemblies(); + var references = new List(types.Length + referencedAssemblies.Length); + foreach (var type in types) + references.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); + foreach (var assembly in referencedAssemblies) + references.Add(MetadataReference.CreateFromFile(Assembly.Load(assembly).Location)); + + return Verify(generator, source, references); + } +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs new file mode 100644 index 0000000000..0bfb1c906f --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Unity.InputSystem.SourceGenerator; + +static class Helpers +{ + private static readonly HashSet ExcludedAssemblies = + [ + "Unity.InputSystem" + ]; + + public static bool IsAcceptedAssembly(INamedTypeSymbol symbol) + { + return !ExcludedAssemblies.Contains(symbol.ContainingAssembly.Identity.Name); + } + + public static bool IsEffectivelyPublic(INamedTypeSymbol type) + { + // The type itself must be public + if (type.DeclaredAccessibility != Accessibility.Public) + return false; + + // Every containing type must also be public + for (var container = type.ContainingType; + container is not null; + container = container.ContainingType) + { + if (container.DeclaredAccessibility != Accessibility.Public) + return false; + } + + return true; + } + + public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol) + => type.AllInterfaces.Contains(interfaceSymbol); + + public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) + { + // If you *don't* want to match A itself, start from type.BaseType instead. + for (var current = type; current is not null; current = current.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(current, baseSymbol)) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs new file mode 100644 index 0000000000..806b2f2205 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs @@ -0,0 +1,18 @@ +using Microsoft.CodeAnalysis; + +namespace Unity.InputSystem.SourceGenerator; + +/// +/// Source generator that registers public types derived from InputBindingComposite. +/// +[Generator] +public sealed class InputBindingCompositeRegistration() : TypeRegistrationGenerator(Base, Template, + static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && + Helpers.IsOrInheritsFrom(symbol, baseSymbol)) +{ + private const string Base = "UnityEngine.InputSystem.InputBindingComposite"; + private const string Template = RegistrationTemplateBegin + + "InputSystem.RegisterBindingComposite(typeof(@T), null);" + + RegistrationTemplateEnd; +} \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputInteractionRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputInteractionRegistration.cs new file mode 100644 index 0000000000..e5dcbd6425 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputInteractionRegistration.cs @@ -0,0 +1,28 @@ +// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via +// in-memory generated source performing manual registration of types. +// +// Potential further improvements: +// - Assembly filtering to narrow scope. +// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default); +// - Generate diagnostic warnings for types implementing interfaces with private visibility? +// - Improve generated type name generation to guarantee no clash. + +using Microsoft.CodeAnalysis; + +namespace Unity.InputSystem.SourceGenerator; + +/// +/// Source generator that registers public types implementing IInputInteraction. +/// +[Generator] +public sealed class InputInteractionRegistration() : TypeRegistrationGenerator(Interface, Template, + static (symbol, @interface) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && + Helpers.ImplementsInterface(symbol, @interface)) +{ + private const string Interface = "UnityEngine.InputSystem.IInputInteraction"; + private const string Template = RegistrationTemplateBegin + + "InputSystem.RegisterInteraction(typeof(@T));" + + RegistrationTemplateEnd; +} + diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs new file mode 100644 index 0000000000..3c046c1c55 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs @@ -0,0 +1,28 @@ +// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via +// in-memory generated source performing manual registration of types. +// +// Potential further improvements: +// - Assembly filtering to narrow scope. +// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default); +// - Generate diagnostic warnings for types implementing interfaces with private visibility? +// - Improve generated type name generation to guarantee no clash. + +using Microsoft.CodeAnalysis; + +namespace Unity.InputSystem.SourceGenerator; + +/// +/// Source generator that registers public types extending InputProcessor. +/// +[Generator] +public sealed class InputProcessorRegistration() : TypeRegistrationGenerator(Base, Template, + static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && + Helpers.IsEffectivelyPublic(symbol) && + Helpers.IsOrInheritsFrom(symbol, baseSymbol)) +{ + private const string Base = "UnityEngine.InputSystem.InputProcessor"; + private const string Template = RegistrationTemplateBegin + + "InputSystem.RegisterProcessor(typeof(@T));" + + RegistrationTemplateEnd; +} + diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs new file mode 100644 index 0000000000..4bc520f3c8 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs @@ -0,0 +1,100 @@ +// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via +// in-memory generated source performing manual registration of types. +// +// Potential further improvements: +// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default); +// - Generate diagnostic warnings for types implementing interfaces with private visibility? +// - Improve generated type name generation to guarantee no clash. + +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Unity.InputSystem.SourceGenerator; + +public class TypeRegistrationGenerator : IIncrementalGenerator +{ + internal const string RegistrationTemplateBegin = @"using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class @C +{ +#if UNITY_EDITOR + static @C() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => "; + + internal const string RegistrationTemplateEnd = @" +} +"; + + private readonly string _interface; + private readonly string _template; + private readonly System.Func _accept; + + protected TypeRegistrationGenerator(string @interface, string template, + System.Func accept) + { + _interface = @interface; + _template = template; + _accept = accept; + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Filter syntax + var typeDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax, + static (ctx, _) => (TypeDeclarationSyntax)ctx.Node) + .Where(t => t is not null); + + // Combine syntax with compilation so we can do symbol checks + var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); + + // Finally, register source output generator + context.RegisterSourceOutput(candidateTypes, (spc, source) => + { + var (compilation, typeDecls) = source; + Process(spc, compilation, typeDecls, _interface, _template, _accept); + }); + } + + private static void Process(SourceProductionContext context, Compilation compilation, + ImmutableArray declarations, string @interface, string template, + System.Func accept) + { + var interfaceSymbol = compilation.GetTypeByMetadataName(@interface); + if (interfaceSymbol is null) + return; // symbol not in this compilation + + foreach (var decl in declarations) + { + // Get semantic model + var model = compilation.GetSemanticModel(decl.SyntaxTree); + if (model.GetDeclaredSymbol(decl) is not { } typeSymbol) + continue; + + // Skip if we shouldn't accept type + if (!accept(typeSymbol, interfaceSymbol)) + continue; + + // Generate type registration code and add to source + var fullyQualifiedTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var source = template.Replace("@C", typeSymbol.Name + "Registration") + .Replace("@T", fullyQualifiedTypeName); + + // Finally, add source to compilation context + context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); + } + } +} diff --git a/Tools/Unity.InputSystem.SourceGenerator/build.bat b/Tools/Roslyn/build.bat similarity index 100% rename from Tools/Unity.InputSystem.SourceGenerator/build.bat rename to Tools/Roslyn/build.bat diff --git a/Tools/Roslyn/build.sh b/Tools/Roslyn/build.sh new file mode 100755 index 0000000000..af9046e49f --- /dev/null +++ b/Tools/Roslyn/build.sh @@ -0,0 +1 @@ +source build.bat \ No newline at end of file diff --git a/Tools/Roslyn/coverlet.runsettings b/Tools/Roslyn/coverlet.runsettings new file mode 100644 index 0000000000..e3ac895402 --- /dev/null +++ b/Tools/Roslyn/coverlet.runsettings @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Tools/Roslyn/test.bat b/Tools/Roslyn/test.bat new file mode 100644 index 0000000000..759d34787a --- /dev/null +++ b/Tools/Roslyn/test.bat @@ -0,0 +1 @@ +dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings --diag:coverletlog.txt && dotnet reportgenerator "-reports:Unity.InputSystem.SourceGenerator.Tests/TestResults/**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html \ No newline at end of file diff --git a/Tools/Roslyn/test.sh b/Tools/Roslyn/test.sh new file mode 100755 index 0000000000..60ab340a67 --- /dev/null +++ b/Tools/Roslyn/test.sh @@ -0,0 +1 @@ +source test.bat \ No newline at end of file diff --git a/Tools/Unity.InputSystem.SourceGenerator/.gitignore b/Tools/Unity.InputSystem.SourceGenerator/.gitignore deleted file mode 100644 index 7447f89a59..0000000000 --- a/Tools/Unity.InputSystem.SourceGenerator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/bin \ No newline at end of file diff --git a/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs b/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs deleted file mode 100644 index f14feaafe9..0000000000 --- a/Tools/Unity.InputSystem.SourceGenerator/InterfaceTypeRegistrationGenerator.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via -// in-memory generated source performing manual registration of types. -// -// Potential further improvements: -// - Assembly filtering to narrow scope. -// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default); -// - Generate diagnostic warnings for types implementing interfaces with private visibility? -// - Improve generated type name generation to guarantee no clash. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace Unity.InputSystem.SourceGenerator; - -static class Helpers -{ - private static readonly HashSet ExcludedAssemblies = - [ - "Unity.InputSystem" - ]; - - public static bool IsAcceptedAssembly(INamedTypeSymbol symbol) - { - return !ExcludedAssemblies.Contains(symbol.ContainingAssembly.Identity.Name); - } - - public static bool IsEffectivelyPublic(INamedTypeSymbol type) - { - // The type itself must be public - if (type.DeclaredAccessibility != Accessibility.Public) - return false; - - // Every containing type must also be public - for (var container = type.ContainingType; - container is not null; - container = container.ContainingType) - { - if (container.DeclaredAccessibility != Accessibility.Public) - return false; - } - - return true; - } - - public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol) - => type.AllInterfaces.Contains(interfaceSymbol); - - public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) - { - // If you *don't* want to match A itself, start from type.BaseType instead. - for (var current = type; current is not null; current = current.BaseType) - { - if (SymbolEqualityComparer.Default.Equals(current, baseSymbol)) - return true; - } - - return false; - } -} - -[Generator] -public class TypeRegistrationGenerator : IIncrementalGenerator -{ - internal const string RegistrationTemplateBegin = @"using System; -using UnityEngine; -using UnityEngine.InputSystem; - -#if UNITY_EDITOR -[UnityEditor.InitializeOnLoad] -#endif -class @C -{ -#if UNITY_EDITOR - static @C() { Register(); } -#endif - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - static void Register() - { - Debug.Log(""Registering @T (@A) via generated type @C"");"; - - internal const string RegistrationTemplateEnd = @" - } -} -"; - - private readonly string _interface; - private readonly string _template; - private readonly System.Func _accept; - - protected TypeRegistrationGenerator(string @interface, string template, - System.Func accept) - { - _interface = @interface; - _template = template; - _accept = accept; - } - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Filter syntax - var typeDeclarations = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax, - static (ctx, _) => (TypeDeclarationSyntax)ctx.Node) - .Where(t => t is not null); - - // Combine syntax with compilation so we can do symbol checks - var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); - - // Finally, register source output generator - context.RegisterSourceOutput(candidateTypes, (spc, source) => - { - var (compilation, typeDecls) = source; - Process(spc, compilation, typeDecls, _interface, _template, _accept); - }); - } - - private static void Process(SourceProductionContext context, Compilation compilation, - ImmutableArray declarations, string @interface, string template, - System.Func accept) - { - var interfaceSymbol = compilation.GetTypeByMetadataName(@interface); - if (interfaceSymbol is null) - return; // symbol not in this compilation - - foreach (var decl in declarations) - { - // Get semantic model - var model = compilation.GetSemanticModel(decl.SyntaxTree); - if (model.GetDeclaredSymbol(decl) is not { } typeSymbol) - continue; - - // Skip if we shouldn't accept type - if (!accept(typeSymbol, interfaceSymbol)) - continue; - - // Generate type registration code and add to source - var fullyQualifiedTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var source = template.Replace("@C", typeSymbol.Name + "Registration") - .Replace("@T", fullyQualifiedTypeName) - .Replace("@A", typeSymbol.ContainingAssembly.Identity.Name); - - // Finally, add source to compilation context - context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8)); - } - } -} - -/// -/// Source generator that registers public types extending InputProcessor. -/// -[Generator] -public sealed class InputProcessorRegistration() : TypeRegistrationGenerator(Base, Template, - static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && - Helpers.IsEffectivelyPublic(symbol) && - Helpers.IsOrInheritsFrom(symbol, baseSymbol)) -{ - private const string Base = "UnityEngine.InputSystem.InputProcessor"; - private const string Template = RegistrationTemplateBegin + - " InputSystem.RegisterProcessor(typeof(@T));" + - RegistrationTemplateEnd; -} - -/// -/// Source generator that registers public types implementing IInputInteraction. -/// -[Generator] -public sealed class InputInteractionRegistration() : TypeRegistrationGenerator(Interface, Template, - static (symbol, @interface) => Helpers.IsAcceptedAssembly(symbol) && - Helpers.IsEffectivelyPublic(symbol) && - Helpers.ImplementsInterface(symbol, @interface)) -{ - private const string Interface = "UnityEngine.InputSystem.IInputInteraction"; - private const string Template = RegistrationTemplateBegin + - " InputSystem.RegisterInteraction(typeof(@T));" + - RegistrationTemplateEnd; -} - -/// -/// Source generator that registers public types derived from InputBindingComposite. -/// -[Generator] -public sealed class InputBindingCompositeRegistration() : TypeRegistrationGenerator(Base, Template, - static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && - Helpers.IsEffectivelyPublic(symbol) && - Helpers.IsOrInheritsFrom(symbol, baseSymbol)) -{ - private const string Base = "UnityEngine.InputSystem.InputBindingComposite"; - private const string Template = RegistrationTemplateBegin + - " InputSystem.RegisterBindingComposite(typeof(@T), null);" + - RegistrationTemplateEnd; -} \ No newline at end of file diff --git a/Tools/Unity.InputSystem.SourceGenerator/build.sh b/Tools/Unity.InputSystem.SourceGenerator/build.sh deleted file mode 100755 index f8d017869c..0000000000 --- a/Tools/Unity.InputSystem.SourceGenerator/build.sh +++ /dev/null @@ -1 +0,0 @@ -dotnet build -c Debug && dotnet build -c Release \ No newline at end of file From c60158e14a6231f0a99eee19802fad1201b78523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:10:30 +0100 Subject: [PATCH 05/15] Added code coverage tool and configuration as well as instruction. Removed obsolete files. Updated gitignore. Added separate test and coverage scripts. --- Tools/Roslyn/.config/dotnet-tools.json | 13 ++++++ Tools/Roslyn/.gitignore | 3 +- Tools/Roslyn/README.md | 40 +++++++++++++++---- ...MyBindingComposite_Generated.g.received.cs | 17 -------- ...dsBase#MyProcessor_Generated.g.verified.cs | 1 - ...MyBindingComposite_Generated.g.received.cs | 17 -------- ...aUsing#MyProcessor_Generated.g.verified.cs | 1 - Tools/Roslyn/build.bat | 0 Tools/Roslyn/test.bat | 2 +- Tools/Roslyn/testcov.bat | 1 + Tools/Roslyn/testcov.sh | 1 + 11 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 Tools/Roslyn/.config/dotnet-tools.json delete mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs delete mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs delete mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs delete mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs mode change 100644 => 100755 Tools/Roslyn/build.bat mode change 100644 => 100755 Tools/Roslyn/test.bat create mode 100755 Tools/Roslyn/testcov.bat create mode 100755 Tools/Roslyn/testcov.sh diff --git a/Tools/Roslyn/.config/dotnet-tools.json b/Tools/Roslyn/.config/dotnet-tools.json new file mode 100644 index 0000000000..96b7185fdd --- /dev/null +++ b/Tools/Roslyn/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-reportgenerator-globaltool": { + "version": "5.5.0", + "commands": [ + "reportgenerator" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/Tools/Roslyn/.gitignore b/Tools/Roslyn/.gitignore index ca272a9ca2..ed2a944ce2 100644 --- a/Tools/Roslyn/.gitignore +++ b/Tools/Roslyn/.gitignore @@ -1,3 +1,4 @@ */bin coverletlog* -TestResults \ No newline at end of file +TestResults +coveragereport \ No newline at end of file diff --git a/Tools/Roslyn/README.md b/Tools/Roslyn/README.md index 8a809660b4..dee4b40b71 100644 --- a/Tools/Roslyn/README.md +++ b/Tools/Roslyn/README.md @@ -16,7 +16,7 @@ This allows generating required registration boilerplate code at compile-time in In addition, it eliminates the need to rely on slow and memory consuming run-time operations using [.NET Reflection API](https://learn.microsoft.com/en-us/dotnet/fundamentals/reflection/reflection). -## How to build and run tests +## How to build The simplest way to build the source generators are via the provided convenience scripts (internally using standard `dotnet` commands): @@ -29,6 +29,15 @@ To build using Windows Command line prompt to build both `Debug` and `Release` t build ``` +To have more control over the build or to build individual targets, inspect the above mentioned scripts which +illustrate what `dotnet` commands are used. Consult [.NET CLI tools documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet) +for additional options. + +When building the source generator, binaries are located under `./bin/`. For `Release` builds, the +resulting binary is also automatically copied into the designated folder location of the Input System package. + +## How to run tests + To run tests and generate a test coverage report using macOS or *nix, use: ``` ./test.sh @@ -38,14 +47,31 @@ To run tests and generate a test coverage report using Windows, use: test ``` -To have more control over the build or to build individual targets, inspect the above mentioned scripts which -illustrate what `dotnet` commands are used. Consult [.NET CLI tools documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet) -for additional options. +Note that you may have to install `.NET Runtime` or add it to path if test run fails with error and prompts you that `Microsoft.NETCore.App` is outdated or missing. -When building the source generator, binaries are located under `./bin/`. For `Release` builds, the -resulting binary is also automatically copied into the designated folder location of the Input System package. +## How to run tests with test coverage report -Note that you may have to install `.NET Runtime` or add it to path if test run fails with error and prompts you that `Microsoft.NETCore.App` is outdated or missing. +In order to generate test coverage reports you need to install `reportgenerator`. + +It is restored as a global tool with: +``` +dotnet tool restore +``` + +It is installed as a global tool with: +``` +dotnet new tool-manifest # if you don't already have a .config/dotnet-tools.json +dotnet tool install dotnet-reportgenerator-globaltool +``` + +To run tests and generate a test coverage report using macOS or *nix, use: +``` +./testcov.sh +``` +To run tests and generate a test coverage report using Windows, use: +``` +testcov +``` ## Dependencies diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs deleted file mode 100644 index 79edd88565..0000000000 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyBindingComposite_Generated.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: MyBindingComposite_Generated.g.cs -using System; -using UnityEngine; -using UnityEngine.InputSystem; - -#if UNITY_EDITOR -[UnityEditor.InitializeOnLoad] -#endif -class MyBindingCompositeRegistration -{ -#if UNITY_EDITOR - static MyBindingCompositeRegistration() { Register(); } -#endif - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); -} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs deleted file mode 100644 index 5f282702bb..0000000000 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs deleted file mode 100644 index 79edd88565..0000000000 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyBindingComposite_Generated.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: MyBindingComposite_Generated.g.cs -using System; -using UnityEngine; -using UnityEngine.InputSystem; - -#if UNITY_EDITOR -[UnityEditor.InitializeOnLoad] -#endif -class MyBindingCompositeRegistration -{ -#if UNITY_EDITOR - static MyBindingCompositeRegistration() { Register(); } -#endif - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - static void Register() => InputSystem.RegisterBindingComposite(typeof(global::MyBindingComposite), null); -} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs deleted file mode 100644 index 5f282702bb..0000000000 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Tools/Roslyn/build.bat b/Tools/Roslyn/build.bat old mode 100644 new mode 100755 diff --git a/Tools/Roslyn/test.bat b/Tools/Roslyn/test.bat old mode 100644 new mode 100755 index 759d34787a..3a2a137787 --- a/Tools/Roslyn/test.bat +++ b/Tools/Roslyn/test.bat @@ -1 +1 @@ -dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings --diag:coverletlog.txt && dotnet reportgenerator "-reports:Unity.InputSystem.SourceGenerator.Tests/TestResults/**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html \ No newline at end of file +dotnet test \ No newline at end of file diff --git a/Tools/Roslyn/testcov.bat b/Tools/Roslyn/testcov.bat new file mode 100755 index 0000000000..759d34787a --- /dev/null +++ b/Tools/Roslyn/testcov.bat @@ -0,0 +1 @@ +dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings --diag:coverletlog.txt && dotnet reportgenerator "-reports:Unity.InputSystem.SourceGenerator.Tests/TestResults/**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html \ No newline at end of file diff --git a/Tools/Roslyn/testcov.sh b/Tools/Roslyn/testcov.sh new file mode 100755 index 0000000000..1459ac717f --- /dev/null +++ b/Tools/Roslyn/testcov.sh @@ -0,0 +1 @@ +source testcov.bat \ No newline at end of file From 45552e8aa0b4fe2addf5b6c1d28b04ca36cc87a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:24:33 +0100 Subject: [PATCH 06/15] Added tests to cover case when a nested public type is within a restricted scope. --- ...nputBindingCompositeTypeRegistrationTests.cs | 10 +++++++++- .../InputProcessorTypeRegistrationTests.cs | 10 +++++++++- ...erface#MyInteraction_Generated.g.verified.cs | 17 +++++++++++++++++ ...aUsing#MyInteraction_Generated.g.verified.cs | 17 +++++++++++++++++ .../InteractionTypeRegistrationTests.cs | 14 +++++++++++--- 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfaceViaUsing#MyInteraction_Generated.g.verified.cs diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs index 86700d8dcc..c1113da827 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs @@ -9,7 +9,15 @@ private static Task Verify(string source) => TestHelper.Verify(new InputBindingC [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); - [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => + Verify(@"using UnityEngine.InputSystem; class MyBindingComposite : InputBindingComposite { }"); + + [Test] public Task ShouldDoNothing_IfNestedPublicClassExtendsBaseInsideRestrictedScope() => + Verify(@"using UnityEngine.InputSystem; +class Internal +{ + public class MyBindingComposite : InputBindingComposite {} +}"); [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing() => Verify(@"using UnityEngine.InputSystem; public class MyBindingComposite : InputBindingComposite { }"); diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs index dce35caea1..de55789ce9 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs @@ -9,7 +9,15 @@ private static Task Verify(string source) => TestHelper.Verify(new InputProcesso [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); - [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => + Verify(@"using UnityEngine.InputSystem; class MyProcessor : InputProcessor { }"); + + [Test] public Task ShouldDoNothing_IfNestedPublicClassExtendsBaseInsideRestrictedScope() => + Verify(@"using UnityEngine.InputSystem; +class Internal +{ + public class MyProcessor : InputProcessor { } +}"); [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing() => Verify(@"using UnityEngine.InputSystem; public class MyProcessor : InputProcessor { }"); diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs new file mode 100644 index 0000000000..3955f4ad62 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyInteraction_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyInteractionRegistration +{ +#if UNITY_EDITOR + static MyInteractionRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterInteraction(typeof(global::MyInteraction)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfaceViaUsing#MyInteraction_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfaceViaUsing#MyInteraction_Generated.g.verified.cs new file mode 100644 index 0000000000..3955f4ad62 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfaceViaUsing#MyInteraction_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyInteraction_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyInteractionRegistration +{ +#if UNITY_EDITOR + static MyInteractionRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterInteraction(typeof(global::MyInteraction)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs index f633a9788e..929859c25d 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs @@ -9,11 +9,19 @@ private static Task Verify(string source) => TestHelper.Verify(new InputInteract [Test] public Task ShouldDoNothing_ForEmptySource() => Verify(string.Empty); - [Test] public Task ShouldDoNothing_IfInternalClassImplementsIInputInteraction() => Verify(string.Empty); + [Test] public Task ShouldDoNothing_IfInternalClassImplementsInterface() => + Verify(@"using UnityEngine.InputSystem; class MyInteraction : IInputInteraction { }"); + + [Test] public Task ShouldDoNothing_IfNestedPublicClassImplementInterfaceInsideConstrainedScope() => + Verify(@"using UnityEngine.InputSystem; +class Internal +{ + public class MyProcessor : IInputInteraction { } +}"); - [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing() => + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfaceViaUsing() => Verify(@"using UnityEngine.InputSystem; public class MyInteraction : IInputInteraction { }"); - [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction() => + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface() => Verify(@"public class MyInteraction : UnityEngine.InputSystem.IInputInteraction { }"); } \ No newline at end of file From f257ebd1a76b608f0b310ceff7c3f7d223902341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:34:27 +0100 Subject: [PATCH 07/15] Added namespace test coverage. --- ...e#MyBindingComposite_Generated.g.verified.cs | 17 +++++++++++++++++ ...nputBindingCompositeTypeRegistrationTests.cs | 6 ++++++ ...endsBase#MyProcessor_Generated.g.verified.cs | 17 +++++++++++++++++ ...endsBase#MyProcessor_Generated.g.verified.cs | 17 +++++++++++++++++ ...ViaUsing#MyProcessor_Generated.g.verified.cs | 17 +++++++++++++++++ .../InputProcessorTypeRegistrationTests.cs | 10 ++++++++-- ...erface#MyInteraction_Generated.g.verified.cs | 17 +++++++++++++++++ .../InteractionTypeRegistrationTests.cs | 6 ++++++ 8 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs create mode 100644 Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs new file mode 100644 index 0000000000..6cb914cbc8 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyBindingComposite_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyBindingComposite_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyBindingCompositeRegistration +{ +#if UNITY_EDITOR + static MyBindingCompositeRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterBindingComposite(typeof(global::Ns.Outer.MyBindingComposite), null); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs index c1113da827..243d9f4b1b 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs @@ -24,4 +24,10 @@ [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsi [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBase() => Verify("public class MyBindingComposite : UnityEngine.InputSystem.InputBindingComposite { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase() => + Verify(@"namespace Ns; +public class Outer { + public class MyBindingComposite : UnityEngine.InputSystem.InputBindingComposite { } +}"); } \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..b14eee2a6a --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyProcessor_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyProcessorRegistration +{ +#if UNITY_EDITOR + static MyProcessorRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterProcessor(typeof(global::Ns.Outer.MyProcessor)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..fa7a07fac4 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBase#MyProcessor_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyProcessor_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyProcessorRegistration +{ +#if UNITY_EDITOR + static MyProcessorRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterProcessor(typeof(global::MyProcessor)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs new file mode 100644 index 0000000000..fa7a07fac4 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing#MyProcessor_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyProcessor_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyProcessorRegistration +{ +#if UNITY_EDITOR + static MyProcessorRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterProcessor(typeof(global::MyProcessor)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs index de55789ce9..4209323b3e 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs @@ -19,9 +19,15 @@ class Internal public class MyProcessor : InputProcessor { } }"); - [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteractionViaUsing() => + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing() => Verify(@"using UnityEngine.InputSystem; public class MyProcessor : InputProcessor { }"); - [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsIInputInteraction() => + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBase() => Verify("public class MyProcessor : UnityEngine.InputSystem.InputProcessor { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase() => + Verify(@"namespace Ns; +public class Outer { + public class MyProcessor : UnityEngine.InputSystem.InputProcessor { } +}"); } \ No newline at end of file diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs new file mode 100644 index 0000000000..074c5db9d2 --- /dev/null +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsInterface#MyInteraction_Generated.g.verified.cs @@ -0,0 +1,17 @@ +//HintName: MyInteraction_Generated.g.cs +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +#if UNITY_EDITOR +[UnityEditor.InitializeOnLoad] +#endif +class MyInteractionRegistration +{ +#if UNITY_EDITOR + static MyInteractionRegistration() { Register(); } +#endif + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + static void Register() => InputSystem.RegisterInteraction(typeof(global::Ns.Outer.MyInteraction)); +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs index 929859c25d..033eec8790 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs @@ -24,4 +24,10 @@ [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsInterfa [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassImplementsInterface() => Verify(@"public class MyInteraction : UnityEngine.InputSystem.IInputInteraction { }"); + + [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsInterface() => + Verify(@"namespace Ns; +public class Outer { + public class MyInteraction : UnityEngine.InputSystem.IInputInteraction { } +}"); } \ No newline at end of file From 5e5e3d669d3e8d7f649e80978599917a3108dbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:36:56 +0100 Subject: [PATCH 08/15] Formatting fixes --- Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs | 2 +- Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs | 2 +- Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs | 2 +- .../InputBindingCompositeTypeRegistrationTests.cs | 2 +- .../InputProcessorTypeRegistrationTests.cs | 2 +- .../InteractionTypeRegistrationTests.cs | 2 +- .../Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs index 851d45dcce..c2c1cd363c 100644 --- a/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/IInputInteraction.cs @@ -2,4 +2,4 @@ namespace UnityEngine.InputSystem; // ReSharper restore CheckNamespace -public interface IInputInteraction { } \ No newline at end of file +public interface IInputInteraction { } diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs index 2ec0d53d10..245a25ccd6 100644 --- a/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/InputBindingComposite.cs @@ -2,4 +2,4 @@ namespace UnityEngine.InputSystem; // ReSharper restore CheckNamespace -public class InputBindingComposite { } \ No newline at end of file +public class InputBindingComposite { } diff --git a/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs b/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs index 159c86adc9..e22eabfef3 100644 --- a/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs +++ b/Tools/Roslyn/Unity.InputSystem.Mocks/InputProcessor.cs @@ -2,4 +2,4 @@ namespace UnityEngine.InputSystem; // ReSharper restore CheckNamespace -public class InputProcessor { } \ No newline at end of file +public class InputProcessor { } diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs index 243d9f4b1b..1b877a0fba 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputBindingCompositeTypeRegistrationTests.cs @@ -30,4 +30,4 @@ [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase public class Outer { public class MyBindingComposite : UnityEngine.InputSystem.InputBindingComposite { } }"); -} \ No newline at end of file +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs index 4209323b3e..1c2d3ba0d1 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs @@ -30,4 +30,4 @@ [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassExtendsBase public class Outer { public class MyProcessor : UnityEngine.InputSystem.InputProcessor { } }"); -} \ No newline at end of file +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs index 033eec8790..d8e377c955 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InteractionTypeRegistrationTests.cs @@ -30,4 +30,4 @@ [Test] public Task ShouldGenerateRegistrationCode_IfNestedPublicClassImplementsI public class Outer { public class MyInteraction : UnityEngine.InputSystem.IInputInteraction { } }"); -} \ No newline at end of file +} diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs index c328cf0456..8e738a01f9 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/TestHelper.cs @@ -113,4 +113,4 @@ public static Task Verify(IIncrementalGenerator generator, string source, params return Verify(generator, source, references); } -} \ No newline at end of file +} From 0bb9faf3849c3c29edca7da046b8c838a1732247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:41:17 +0100 Subject: [PATCH 09/15] Added Roslyn.sln and formatting fixes --- Tools/Roslyn/.config/dotnet-tools.json | 2 +- Tools/Roslyn/.gitignore | 2 +- Tools/Roslyn/Roslyn.sln | 40 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Tools/Roslyn/Roslyn.sln diff --git a/Tools/Roslyn/.config/dotnet-tools.json b/Tools/Roslyn/.config/dotnet-tools.json index 96b7185fdd..00689077b9 100644 --- a/Tools/Roslyn/.config/dotnet-tools.json +++ b/Tools/Roslyn/.config/dotnet-tools.json @@ -10,4 +10,4 @@ "rollForward": false } } -} \ No newline at end of file +} diff --git a/Tools/Roslyn/.gitignore b/Tools/Roslyn/.gitignore index ed2a944ce2..37d1421755 100644 --- a/Tools/Roslyn/.gitignore +++ b/Tools/Roslyn/.gitignore @@ -1,4 +1,4 @@ */bin coverletlog* TestResults -coveragereport \ No newline at end of file +coveragereport diff --git a/Tools/Roslyn/Roslyn.sln b/Tools/Roslyn/Roslyn.sln new file mode 100644 index 0000000000..9789ae6e93 --- /dev/null +++ b/Tools/Roslyn/Roslyn.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.InputSystem.SourceGenerator", "Unity.InputSystem.SourceGenerator\Unity.InputSystem.SourceGenerator.csproj", "{CD0E7260-107F-4C7D-BC63-8B24EC8853F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.InputSystem.SourceGenerator.Tests", "Unity.InputSystem.SourceGenerator.Tests\Unity.InputSystem.SourceGenerator.Tests.csproj", "{904BB251-1F1A-46A2-B2E1-89FB7F0C4295}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{669D18C6-87D7-4FB6-8096-87F12203EAC9}" + ProjectSection(SolutionItems) = preProject + .config/dotnet-tools.json = .config/dotnet-tools.json + .gitignore = .gitignore + coverlet.runsettings = coverlet.runsettings + README.md = README.md + build.sh = build.sh + build.bat = build.bat + test.sh = test.sh + test.bat = test.bat + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.InputSystem.Mocks", "Unity.InputSystem.Mocks\Unity.InputSystem.Mocks.csproj", "{A75AFB3D-C4E9-49FF-9B0F-F37BC0F93AFE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD0E7260-107F-4C7D-BC63-8B24EC8853F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD0E7260-107F-4C7D-BC63-8B24EC8853F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD0E7260-107F-4C7D-BC63-8B24EC8853F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD0E7260-107F-4C7D-BC63-8B24EC8853F1}.Release|Any CPU.Build.0 = Release|Any CPU + {904BB251-1F1A-46A2-B2E1-89FB7F0C4295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {904BB251-1F1A-46A2-B2E1-89FB7F0C4295}.Debug|Any CPU.Build.0 = Debug|Any CPU + {904BB251-1F1A-46A2-B2E1-89FB7F0C4295}.Release|Any CPU.ActiveCfg = Release|Any CPU + {904BB251-1F1A-46A2-B2E1-89FB7F0C4295}.Release|Any CPU.Build.0 = Release|Any CPU + {A75AFB3D-C4E9-49FF-9B0F-F37BC0F93AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A75AFB3D-C4E9-49FF-9B0F-F37BC0F93AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A75AFB3D-C4E9-49FF-9B0F-F37BC0F93AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A75AFB3D-C4E9-49FF-9B0F-F37BC0F93AFE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 7a7262ea0fd3688612f2409a2589199181408a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:42:09 +0100 Subject: [PATCH 10/15] Fixed formatting. --- .../InputBindingCompositeRegistration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs index 806b2f2205..1508803f63 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs @@ -15,4 +15,4 @@ public sealed class InputBindingCompositeRegistration() : TypeRegistrationGenera private const string Template = RegistrationTemplateBegin + "InputSystem.RegisterBindingComposite(typeof(@T), null);" + RegistrationTemplateEnd; -} \ No newline at end of file +} From 27c35d587568673d13222589e966ce2ba498ced1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:57:43 +0100 Subject: [PATCH 11/15] Formatting fixes. --- Tools/Roslyn/build.bat | 2 +- Tools/Roslyn/build.sh | 2 +- Tools/Roslyn/coverlet.runsettings | 2 +- Tools/Roslyn/test.bat | 2 +- Tools/Roslyn/test.sh | 2 +- Tools/Roslyn/testcov.bat | 2 +- Tools/Roslyn/testcov.sh | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tools/Roslyn/build.bat b/Tools/Roslyn/build.bat index f8d017869c..948f41ccdd 100755 --- a/Tools/Roslyn/build.bat +++ b/Tools/Roslyn/build.bat @@ -1 +1 @@ -dotnet build -c Debug && dotnet build -c Release \ No newline at end of file +dotnet build -c Debug && dotnet build -c Release diff --git a/Tools/Roslyn/build.sh b/Tools/Roslyn/build.sh index af9046e49f..e4b1c439d9 100755 --- a/Tools/Roslyn/build.sh +++ b/Tools/Roslyn/build.sh @@ -1 +1 @@ -source build.bat \ No newline at end of file +source build.bat diff --git a/Tools/Roslyn/coverlet.runsettings b/Tools/Roslyn/coverlet.runsettings index e3ac895402..10433f6611 100644 --- a/Tools/Roslyn/coverlet.runsettings +++ b/Tools/Roslyn/coverlet.runsettings @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/Tools/Roslyn/test.bat b/Tools/Roslyn/test.bat index 3a2a137787..04162e90a2 100755 --- a/Tools/Roslyn/test.bat +++ b/Tools/Roslyn/test.bat @@ -1 +1 @@ -dotnet test \ No newline at end of file +dotnet test diff --git a/Tools/Roslyn/test.sh b/Tools/Roslyn/test.sh index 60ab340a67..f00dd3f68a 100755 --- a/Tools/Roslyn/test.sh +++ b/Tools/Roslyn/test.sh @@ -1 +1 @@ -source test.bat \ No newline at end of file +source test.bat diff --git a/Tools/Roslyn/testcov.bat b/Tools/Roslyn/testcov.bat index 759d34787a..2430977be9 100755 --- a/Tools/Roslyn/testcov.bat +++ b/Tools/Roslyn/testcov.bat @@ -1 +1 @@ -dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings --diag:coverletlog.txt && dotnet reportgenerator "-reports:Unity.InputSystem.SourceGenerator.Tests/TestResults/**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html \ No newline at end of file +dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings --diag:coverletlog.txt && dotnet reportgenerator "-reports:Unity.InputSystem.SourceGenerator.Tests/TestResults/**/coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html diff --git a/Tools/Roslyn/testcov.sh b/Tools/Roslyn/testcov.sh index 1459ac717f..1aba682f1d 100755 --- a/Tools/Roslyn/testcov.sh +++ b/Tools/Roslyn/testcov.sh @@ -1 +1 @@ -source testcov.bat \ No newline at end of file +source testcov.bat From 8c78a93ac201ebc0cdabe2f382eaf358cb2a15c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 14:58:48 +0100 Subject: [PATCH 12/15] Formatting fix --- Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs index 0bfb1c906f..1179f509bd 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs @@ -47,4 +47,4 @@ public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol base return false; } -} \ No newline at end of file +} From fe1d0e9234d773448066767ee77de8407bdec04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 15:05:30 +0100 Subject: [PATCH 13/15] Fixed an issue where base type could be incorrectly evaluated. --- .../Unity.InputSystem.SourceGenerator.dll | Bin 14336 -> 15872 bytes .../InputProcessorTypeRegistrationTests.cs | 2 +- .../InputBindingCompositeRegistration.cs | 2 +- .../InputProcessorRegistration.cs | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll b/Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll index 310b2795a1eb92816f4a33e591ee0541b3357e9a..0055725f9522022513c982c87183692125e6027d 100644 GIT binary patch delta 5330 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 Y_(FJKi}UUMlUIEqterFRbN0ah0U}c@kpKVy diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs index 1c2d3ba0d1..955a283cfd 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator.Tests/InputProcessorTypeRegistrationTests.cs @@ -18,7 +18,7 @@ class Internal { public class MyProcessor : InputProcessor { } }"); - + [Test] public Task ShouldGenerateRegistrationCode_IfPublicClassExtendsBaseViaUsing() => Verify(@"using UnityEngine.InputSystem; public class MyProcessor : InputProcessor { }"); diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs index 1508803f63..46a8111194 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputBindingCompositeRegistration.cs @@ -9,7 +9,7 @@ namespace Unity.InputSystem.SourceGenerator; public sealed class InputBindingCompositeRegistration() : TypeRegistrationGenerator(Base, Template, static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && Helpers.IsEffectivelyPublic(symbol) && - Helpers.IsOrInheritsFrom(symbol, baseSymbol)) + Helpers.IsOrInheritsFrom(symbol.BaseType, baseSymbol)) { private const string Base = "UnityEngine.InputSystem.InputBindingComposite"; private const string Template = RegistrationTemplateBegin + diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs index 3c046c1c55..b6e629da8f 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/InputProcessorRegistration.cs @@ -18,7 +18,7 @@ namespace Unity.InputSystem.SourceGenerator; public sealed class InputProcessorRegistration() : TypeRegistrationGenerator(Base, Template, static (symbol, baseSymbol) => Helpers.IsAcceptedAssembly(symbol) && Helpers.IsEffectivelyPublic(symbol) && - Helpers.IsOrInheritsFrom(symbol, baseSymbol)) + Helpers.IsOrInheritsFrom(symbol.BaseType, baseSymbol)) { private const string Base = "UnityEngine.InputSystem.InputProcessor"; private const string Template = RegistrationTemplateBegin + From b4022a98e0e296a8dd3faa05c2ee4c2d23874c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Fri, 21 Nov 2025 15:09:35 +0100 Subject: [PATCH 14/15] Code cleanup --- Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs index 1179f509bd..59fc4576ad 100644 --- a/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs +++ b/Tools/Roslyn/Unity.InputSystem.SourceGenerator/Helpers.cs @@ -17,11 +17,9 @@ public static bool IsAcceptedAssembly(INamedTypeSymbol symbol) public static bool IsEffectivelyPublic(INamedTypeSymbol type) { - // The type itself must be public if (type.DeclaredAccessibility != Accessibility.Public) return false; - - // Every containing type must also be public + for (var container = type.ContainingType; container is not null; container = container.ContainingType) @@ -35,10 +33,9 @@ public static bool IsEffectivelyPublic(INamedTypeSymbol type) public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol) => type.AllInterfaces.Contains(interfaceSymbol); - + public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol) { - // If you *don't* want to match A itself, start from type.BaseType instead. for (var current = type; current is not null; current = current.BaseType) { if (SymbolEqualityComparer.Default.Equals(current, baseSymbol)) From 5b8745d89606d7edaa5e80bacfc283d06ed285f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Mon, 24 Nov 2025 11:57:29 +0100 Subject: [PATCH 15/15] Tentative enable fix that seems to work in practise but fails tests. --- .../InputSystem/InputManager.cs | 2 +- .../InputSystem/InputSystem.cs | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 30649e5269..0bd4e27737 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -2027,7 +2027,7 @@ internal void InitializeData() composites.AddTypeRegistration("TwoModifiers", typeof(TwoModifiersComposite)); // Register custom types by reflection - RegisterCustomTypes(); + //RegisterCustomTypes(); } void RegisterCustomTypes(Type[] types) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs b/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs index 432f3bea7b..e8e2e4652e 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputSystem.cs @@ -879,7 +879,7 @@ public static void RegisterProcessor(Type type, string name = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - + // Default name to name of type without Processor suffix. if (string.IsNullOrEmpty(name)) { @@ -895,6 +895,8 @@ public static void RegisterProcessor(Type type, string name = null) if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(precompiledLayouts[key].metadata, name, ';')) s_Manager.m_Layouts.precompiledLayouts.Remove(key); } + + Debug.Log($"Registering {type} with alias {name}"); s_Manager.processors.AddTypeRegistration(name, type); } @@ -3597,7 +3599,9 @@ internal static void InitializeInEditor(IInputRuntime runtime = null) // this would cancel the import of large assets that are dependent on the InputSystem package and import it as a dependency. EditorApplication.delayCall += ShowRestartWarning; - RunInitialUpdate(); + // Run initial update but defer enabling actions until before-scene initialization hook. + // This allows type registrations to execute before attempting to enable Project-wide input actions. + RunInitialUpdate(false); k_InputInitializeInEditorMarker.End(); } @@ -3763,7 +3767,7 @@ private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettin #endif // This is the point where we initialise project-wide actions for the Player - EnableActions(); + // EnableActions(); } #endif // UNITY_EDITOR @@ -3771,6 +3775,13 @@ private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettin [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void RunInitialUpdate() { + RunInitialUpdate(true); + } + + private static void RunInitialUpdate(bool enableActions) + { + Debug.Log($"RunInitialUpdate enableActions={enableActions}"); + // Request an initial Update so that user methods such as Start and Awake // can access the input devices. // @@ -3779,6 +3790,10 @@ private static void RunInitialUpdate() // mask but will still restore devices. This means we're not actually processing input, // but we will force the runtime to push its devices. Update(InputUpdateType.None); + + // If this was invoked as a runtime initialization hook we enable actions + if (enableActions) + EnableActions(); } #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION @@ -3902,7 +3917,7 @@ private static void Reset(bool enableRemoting = false, IInputRuntime runtime = n EnhancedTouchSupport.Reset(); // This is the point where we initialise project-wide actions for the Editor Play-mode, Editor Tests and Player Tests. - EnableActions(); + //EnableActions(); k_InputResetMarker.End(); }