From 5395a9b0f16d589f61ddb185396e7bbb0d4c3646 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:31:04 +1000 Subject: [PATCH 1/2] feat(bindings): generate native platform adapters - Select typed observation, conversion, command and collection mechanisms by affinity. - Preserve higher-scoring custom providers, cached selection and refresh behavior. - Keep generated value delivery typed and emit native support only when used. - Remove redundant analysis, pipeline dependencies and avoidable collection copies. - Cover platform contracts with tests and validated GcVerbose benchmarks on .NET 10/11 and NativeAOT. - Document native bindings and benefits for regular JIT applications. --- CLAUDE.md | 48 +-- README.md | 166 +++++---- src/Directory.Build.props | 5 +- ...activeUI.Binding.Analyzer.Roslyn413.csproj | 4 + .../Analyzers/BindingInvocationAnalyzer.cs | 16 +- .../ReactiveUI.Binding.Analyzer.csproj | 4 + .../PublicAPI/net10.0/PublicAPI.txt | 1 + .../PublicAPI/net11.0/PublicAPI.txt | 1 + .../PublicAPI/net462/PublicAPI.txt | 1 + .../PublicAPI/net47/PublicAPI.txt | 1 + .../PublicAPI/net471/PublicAPI.txt | 1 + .../PublicAPI/net472/PublicAPI.txt | 1 + .../PublicAPI/net48/PublicAPI.txt | 1 + .../PublicAPI/net481/PublicAPI.txt | 1 + .../PublicAPI/net8.0/PublicAPI.txt | 1 + .../PublicAPI/net9.0/PublicAPI.txt | 1 + .../Fallback/ObservationAffinityChecker.cs | 137 +++++--- .../Fallback/RuntimeBindingConverter.cs | 8 +- .../Observables/AppliedChangeObservable.cs | 3 + .../BindingGenerator.cs | 137 ++------ .../CodeGeneration/BindCodeGenerator.cs | 66 +--- .../BindCommandCodeGenerator.cs | 16 +- .../CodeGeneration/BindToCodeGenerator.cs | 48 ++- .../CodeGeneration/BindingEmitterHelpers.cs | 25 +- .../CodeGeneration/ConversionEmitter.cs | 169 +++++++++ .../ObservationCodeGenerator.cs | 89 ++--- .../CodeGeneration/OneWayBindCodeGenerator.cs | 24 +- .../CodeGeneration/RuntimeFlavourRewriter.cs | 11 +- .../CodeGeneration/TypeReferenceArity.cs | 92 +++++ .../Constants.cs | 3 - .../CommandBindingHelperGenerator.cs | 52 +++ .../InvocationHelperRequirements.cs | 312 +++++++++++++++++ .../Generators/ObservationHelperGenerator.cs | 23 +- .../Generators/RegistrationGenerator.cs | 82 ----- .../ViewLocatorDispatchGenerator.cs | 1 - .../Helpers/BindToExtractor.cs | 18 +- .../Helpers/BindingExtractor.cs | 20 +- .../Helpers/CommandExtractor.cs | 141 ++------ .../Helpers/ExtractorValidation.cs | 5 +- .../Helpers/InteractionExtractor.cs | 2 +- .../Helpers/InvokeCommandExtractor.cs | 2 +- .../Helpers/NativeCommandMembers.cs | 89 +++++ .../Helpers/NativeTypeIdentity.cs | 35 ++ .../Helpers/ObservationExtractor.cs | 2 +- .../Helpers/SyntaxHelpers.cs | 8 +- .../Helpers/TypeDetectionExtractor.cs | 86 +++-- .../Helpers/WhenAnyObservableExtractor.cs | 2 +- .../BindCommandInvocationGenerator.cs | 13 +- .../BindInteractionInvocationGenerator.cs | 3 - .../Invocations/BindInvocationGenerator.cs | 3 - .../BindOneWayInvocationGenerator.cs | 3 - .../BindTwoWayInvocationGenerator.cs | 3 - .../Invocations/InvocationPipeline.cs | 14 +- .../InvokeCommandInvocationGenerator.cs | 3 - .../OneWayBindInvocationGenerator.cs | 3 - .../Invocations/WhenAnyInvocationGenerator.cs | 3 - .../WhenAnyObservableInvocationGenerator.cs | 3 - .../WhenAnyValueInvocationGenerator.cs | 3 - .../WhenChangedInvocationGenerator.cs | 3 - .../WhenChangingInvocationGenerator.cs | 3 - .../Models/BindCommandInvocationInfo.cs | 9 +- .../Models/BindToInvocationInfo.cs | 9 +- .../Models/BindingInvocationInfo.cs | 12 +- .../Models/ConversionInfo.cs | 21 ++ .../Models/ConversionParameters.cs | 10 + .../Models/NativeCommandInfo.cs | 13 + .../Models/NativeCommandKind.cs | 21 ++ .../Models/NotificationEventInfo.cs | 10 + .../Models/ObservablePropertyInfo.cs | 6 +- .../Models/ObservableTypeInfo.cs | 24 -- .../Models/ObservationExpression.cs | 18 + .../Models/PlatformObservationInfo.cs | 20 ++ .../Models/PropertyPathSegment.cs | 4 +- .../Models/SetMethodEmission.cs | 20 ++ .../Models/SetMethodInfo.cs | 14 + .../AndroidCommandBindingPlugin.cs | 38 ++ .../AppKitCommandBindingPlugin.cs | 45 +++ .../CommandBinding/AppKitCommandEmitter.cs | 133 +++++++ .../CommandBinding/AppKitCommandSymbols.cs | 28 ++ .../CommandBinding/CommandControlEmitter.cs | 46 +++ .../CommandEventBindingEmitter.cs | 11 +- .../CommandBinding/CommandParameterEmitter.cs | 63 ++++ .../CommandPropertyBindingPlugin.cs | 75 +++- .../DefaultEventBindingPlugin.cs | 51 ++- .../EventCommandBindingEmitter.cs | 58 ++++ .../EventCommandBindingPlugin.cs | 131 ------- .../EventEnabledBindingPlugin.cs | 83 ++++- .../CommandBinding/NativeCommandEmitter.cs | 87 +++++ .../CommandBinding/NativeCommandSymbols.cs | 38 ++ .../UIKitCommandBindingPlugin.cs | 37 ++ .../CommandBinding/UIKitCommandSymbols.cs | 41 +++ .../UIKitControlCommandBindingPlugin.cs | 37 ++ .../Plugins/CommandBindingPluginRegistry.cs | 39 ++- .../Conversion/AndroidConversionPlugin.cs | 18 + .../Conversion/AppleConversionPlugin.cs | 93 +++++ .../BooleanStringConversionPlugin.cs | 23 ++ .../Conversion/ConversionPluginRegistry.cs | 66 ++++ .../Plugins/Conversion/ConversionSymbols.cs | 59 ++++ .../Conversion/EqualityConversionPlugin.cs | 18 + .../Conversion/GuidStringConversionPlugin.cs | 23 ++ .../Plugins/Conversion/IConversionPlugin.cs | 19 + .../Conversion/LanguageConversionPlugin.cs | 22 ++ .../Conversion/MauiConversionPlugin.cs | 18 + .../NullableValueConversionPlugin.cs | 30 ++ .../NumericStringConversionPlugin.cs | 39 +++ .../Conversion/StringConversionExpressions.cs | 51 +++ .../StringIdentityConversionPlugin.cs | 18 + .../TemporalStringConversionPlugin.cs | 25 ++ .../Plugins/Conversion/UnoConversionPlugin.cs | 18 + .../Plugins/Conversion/UriConversionPlugin.cs | 28 ++ .../Conversion/VisibilityConversion.cs | 72 ++++ .../Conversion/VisibilityHintExpressions.cs | 54 +++ .../Conversion/WinUIConversionPlugin.cs | 18 + .../Plugins/Conversion/WpfConversionPlugin.cs | 18 + .../Plugins/ICommandBindingHelperPlugin.cs | 15 + .../Plugins/IObservationPlugin.cs | 88 +---- .../Plugins/IPlatformCommandBindingPlugin.cs | 17 + .../Plugins/IPlatformObservationPlugin.cs | 18 + .../AfterChangeObservationPlugin.cs | 220 ------------ .../Observation/AndroidObservationPlugin.cs | 224 +++--------- .../Observation/AppKitObservationPlugin.cs | 69 ++++ .../Observation/AppleNotificationEmitter.cs | 41 +++ .../Observation/ChainRegistrationEmitter.cs | 16 +- .../DependencyPropertyObservationEmitter.cs | 42 +++ .../Observation/INPCObservationPlugin.cs | 42 ++- .../Observation/KVOObservationPlugin.cs | 325 +++--------------- .../Observation/KvoObservationEmitter.cs | 236 +++++++++++++ .../NativeEventSubscriptionEmitter.cs | 34 ++ .../Observation/NativeObservableEmitter.cs | 97 ++++++ .../Observation/NativeObservationEmitter.cs | 39 +++ .../Observation/NotifyPropertyEmitter.cs | 8 +- .../NotifyPropertyObservationPlugin.cs | 93 ----- .../ObservationEmissionExtensions.cs | 124 +++++++ .../Plugins/Observation/PlatformSymbols.cs | 120 +++++++ .../Observation/PocoObservationPlugin.cs | 76 ++++ .../ReactiveObjectObservationPlugin.cs | 40 ++- .../Observation/UIKitObservationPlugin.cs | 121 +++++++ .../UIKitValueObservationPlugin.cs | 71 ++++ .../Observation/UnoObservationPlugin.cs | 53 +++ .../Observation/WinFormsObservationPlugin.cs | 114 ++---- .../Observation/WinUIObservationPlugin.cs | 210 +---------- .../Observation/WpfObservationEmitter.cs | 38 ++ .../Observation/WpfObservationPlugin.cs | 130 ++----- .../Plugins/ObservationPluginRegistry.cs | 61 +++- .../Plugins/ObservedProperties.cs | 2 +- .../SetMethod/CollectionSetMethodEmitter.cs | 102 ++++++ .../Plugins/SetMethod/ISetMethodPlugin.cs | 18 + .../Plugins/SetMethod/PanelSetMethodPlugin.cs | 18 + .../SetMethod/SetMethodPluginRegistry.cs | 39 +++ .../SetMethod/TableLayoutSetMethodPlugin.cs | 18 + .../SetMethod/WinFormsCollectionSymbols.cs | 92 +++++ .../RoslynHelpers.cs | 2 +- .../PublicAPI/net10.0/PublicAPI.txt | 1 + .../PublicAPI/net11.0/PublicAPI.txt | 1 + .../PublicAPI/net462/PublicAPI.txt | 1 + .../PublicAPI/net47/PublicAPI.txt | 1 + .../PublicAPI/net471/PublicAPI.txt | 1 + .../PublicAPI/net472/PublicAPI.txt | 1 + .../PublicAPI/net48/PublicAPI.txt | 1 + .../PublicAPI/net481/PublicAPI.txt | 1 + .../PublicAPI/net8.0/PublicAPI.txt | 1 + .../PublicAPI/net9.0/PublicAPI.txt | 1 + src/benchmarks/README.md | 43 +-- ...iveUI.Binding.Benchmarks.ReactiveUI.csproj | 2 +- .../Mocks/Android/NumberPicker.cs | 23 ++ .../Mocks/Android/View.cs | 21 ++ .../Mocks/NativeAdapterCommand.cs | 29 ++ .../Mocks/NativeAdapterView.cs | 28 ++ .../Mocks/TypedAdapterTarget.cs | 37 ++ .../Mocks/WinForms/Button.cs | 12 + .../Mocks/WinForms/Control.cs | 56 +++ .../NativeAdapterBenchmark.cs | 139 ++++++++ .../ReactiveUI.Binding.Benchmarks.csproj | 2 +- .../TypedAdapterBenchmark.cs | 89 +++++ ...ding.Generator.Benchmarks.Roslyn413.csproj | 5 +- .../AdapterGenerationBenchmarks.cs | 196 +++++++++++ .../Program.cs | 8 +- ...tiveUI.Binding.Generator.Benchmarks.csproj | 5 +- .../Support/AdapterBenchmarkCorpus.cs | 173 ++++++++++ .../Support/GeneratorHarness.cs | 6 +- .../Shared/Configs/BenchmarkConfig.cs | 5 +- .../Shared/Configs/BenchmarkRunValidation.cs | 93 +++++ ...moryConfig.cs => NativeAotTimingConfig.cs} | 10 +- .../Shared/Configs/ProfilerConfig.cs | 5 - .../Shared/Platforms/unix/BenchmarkHost.cs | 7 +- .../Shared/Platforms/windows/BenchmarkHost.cs | 8 +- ...UI.Binding.Analyzer.Tests.Roslyn413.csproj | 2 +- ...gInvocationAnalyzerTests.NativeCommands.cs | 74 ++++ .../ReactiveUI.Binding.Analyzer.Tests.csproj | 2 +- ...UI.Binding.GeneratedCode.TestModels.csproj | 2 +- ...ctiveUI.Binding.GeneratedCode.Tests.csproj | 2 +- ...ng.SourceGenerators.Tests.Roslyn413.csproj | 4 +- .../AppleConversionParityTests.cs | 91 +++++ .../AppleObservationParityTests.cs | 185 ++++++++++ ...cNoParam#BindCommandDispatch.g.verified.cs | 45 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Fallback#BindCommandDispatch.g.verified.cs | 45 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ionParam#BindCommandDispatch.g.verified.cs | 89 +++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...bleParam#BindCommandDispatch.g.verified.cs | 82 ++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Property#BindCommandDispatch.g.verified.cs | 47 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...xprParam#BindCommandDispatch.g.verified.cs | 57 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ObsParam#BindCommandDispatch.g.verified.cs | 51 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...tomEvent#BindCommandDispatch.g.verified.cs | 43 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...mandPath#BindCommandDispatch.g.verified.cs | 63 ++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...tEnabled#BindCommandDispatch.g.verified.cs | 51 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...xprParam#BindCommandDispatch.g.verified.cs | 100 ++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ObsParam#BindCommandDispatch.g.verified.cs | 90 +++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ionParam#BindCommandDispatch.g.verified.cs | 89 +++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....NoEvent#BindCommandDispatch.g.verified.cs | 37 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...bleParam#BindCommandDispatch.g.verified.cs | 82 ++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.2STB_GEI#BindDispatch.g.verified.cs | 238 ++++++++++--- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.MB#BindDispatch.g.verified.cs | 238 ++++++++++--- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.SP_S2S#BindDispatch.g.verified.cs | 120 +++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.SP_S2S_CFP#BindDispatch.g.verified.cs | 120 +++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.SP_WC#BindDispatch.g.verified.cs | 40 +-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BG.SP_WCSched#BindDispatch.g.verified.cs | 40 +-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...back#BindInteractionDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Path#BindInteractionDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...odel#BindInteractionDispatch.g.verified.cs | 6 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ViewModel#ObservationHelpers.g.verified.cs | 31 ++ ...dler#BindInteractionDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Path#BindInteractionDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...odel#BindInteractionDispatch.g.verified.cs | 6 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ViewModel#ObservationHelpers.g.verified.cs | 31 ++ ...dler#BindInteractionDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...dler#BindInteractionDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ct_Source#BindOneWayDispatch.g.verified.cs | 57 ++- ...#GeneratedBinderRegistration.g.verified.cs | 26 -- ...ct_Source#ObservationHelpers.g.verified.cs | 2 +- .../BOG.MB#BindOneWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BOG.MSTB#BindOneWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....MSTB_CFP#BindOneWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ct_Source#BindOneWayDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 26 -- ...OG.SP_I2I#BindOneWayDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...OG.SP_S2S#BindOneWayDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_S2S_CFP#BindOneWayDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...BOG.SP_WC#BindOneWayDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_WCSched#BindOneWayDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Scheduler#BindOneWayDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ct_Target#BindTwoWayDispatch.g.verified.cs | 107 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 26 -- ...ct_Target#ObservationHelpers.g.verified.cs | 2 +- .../BTG.MB#BindTwoWayDispatch.g.verified.cs | 202 +++++++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BTG.MSTB#BindTwoWayDispatch.g.verified.cs | 202 +++++++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....MSTB_CFP#BindTwoWayDispatch.g.verified.cs | 202 +++++++++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...indOneWay#BindOneWayDispatch.g.verified.cs | 52 ++- ...indOneWay#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ject_Both#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...TG.SP_I2I#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...TG.SP_S2S#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_S2S_CFP#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...BTG.SP_WC#BindTwoWayDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_WCSched#BindTwoWayDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Scheduler#BindTwoWayDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ifferingTypes#BindToDispatch.g.verified.cs | 45 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ameTypeString#BindToDispatch.g.verified.cs | 42 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ypeString_CFP#BindToDispatch.g.verified.cs | 42 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...oG.WCOverride#BindToDispatch.g.verified.cs | 45 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...onversionHint#BindToDispatch.g.verified.cs | 45 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../BindCommandCodeGeneratorHelperTests.cs | 9 +- ...ationCodeGeneratorHelperTests.DeepChain.cs | 10 +- ...deGeneratorHelperTests.MethodGeneration.cs | 11 +- .../CodeGeneration/TypeReferenceArityTests.cs | 36 ++ .../ConversionEdgeParityTests.cs | 67 ++++ .../ConversionOverrideParityTests.cs | 113 ++++++ .../Generators/BindingGeneratorTests.cs | 128 +++++++ .../Helpers/ApplePlatformSource.cs | 6 + .../Helpers/CollectibleAssemblyLoadContext.cs | 12 +- .../Helpers/CommandExtractorHelperTests.cs | 22 +- .../Helpers/NativeObservationTestModels.cs | 35 ++ .../Helpers/NativeTypeIdentityTests.cs | 32 ++ .../Helpers/TestHelper.cs | 12 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...operty#InvokeCommandDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...ty_CFP#InvokeCommandDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ndPath#InvokeCommandDispatch.g.verified.cs | 28 +- .../KvoEligibilityParityTests.cs | 55 +++ .../MismatchedPropertyTypeBindingTests.cs | 8 +- .../Models/ModelEqualityTests.cs | 127 ------- .../NativeCommandOverrideParityTests.cs | 118 +++++++ .../NativeCommandParityTests.cs | 289 ++++++++++++++++ ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....2STB_GEI#OneWayBindDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../OBG.MB#OneWayBindDispatch.g.verified.cs | 102 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...BG.SP_I2I#OneWayBindDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...BG.SP_S2S#OneWayBindDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_S2S_CFP#OneWayBindDispatch.g.verified.cs | 52 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...OBG.SP_WS#OneWayBindDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...P_WSSched#OneWayBindDispatch.g.verified.cs | 10 +- .../ObservationContractsParityTests.cs | 147 ++++++++ .../ObservationOverrideParityTests.cs | 132 +++++++ ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ..._Detected#ObservationHelpers.g.verified.cs | 132 ------- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- .../PlatformAdapterTests.cs | 80 +++++ .../PlatformDetectionSnapshotTests.cs | 5 +- .../Plugins/ObservationPluginTests.cs | 142 ++++---- .../Plugins/PropertyCapabilityTests.cs | 22 +- .../PropertyObservationCapabilityTests.cs | 3 +- ...veUI.Binding.SourceGenerators.Tests.csproj | 6 +- .../BindOneWayRuntimeTests.cs | 6 +- .../ConversionPluginRuntimeTests.cs | 96 ++++++ .../WhenChangedRuntimeTests.cs | 6 +- .../StandardConversionParityTests.cs | 157 +++++++++ .../UnanalyzableInvocationTests.cs | 5 +- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...#GeneratedBinderRegistration.g.verified.cs | 24 -- ...pfInvoker#BindOneWayDispatch.g.verified.cs | 50 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...nFormsInvoker#BindToDispatch.g.verified.cs | 42 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...argetOnly#BindTwoWayDispatch.g.verified.cs | 100 +++++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...fInvoker#BindCommandDispatch.g.verified.cs | 47 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...pfInvoker#OneWayBindDispatch.g.verified.cs | 50 ++- .../VisibilityHintParityTests.cs | 86 +++++ ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WAG.MI_STS#WhenAnyDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WAG.MP_2P#WhenAnyDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WAG.MP_DC#WhenAnyDispatch.g.verified.cs | 37 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WAG.SP_DC#WhenAnyDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WAG.SP_INPC#WhenAnyDispatch.g.verified.cs | 11 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....SP_INPC_CFP#WhenAnyDispatch.g.verified.cs | 11 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...CL#WhenAnyObservableDispatch.g.verified.cs | 54 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ge#WhenAnyObservableDispatch.g.verified.cs | 54 ++- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ge#WhenAnyObservableDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...WS#WhenAnyObservableDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...TS#WhenAnyObservableDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...le#WhenAnyObservableDispatch.g.verified.cs | 11 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...FP#WhenAnyObservableDispatch.g.verified.cs | 11 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...DC#WhenAnyObservableDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...AVG.DPC#WhenAnyValueDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_2P#WhenAnyValueDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_3P#WhenAnyValueDispatch.g.verified.cs | 29 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...perties#WhenAnyValueDispatch.g.verified.cs | 47 +-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...perties#WhenAnyValueDispatch.g.verified.cs | 110 ++---- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_WS#WhenAnyValueDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...perties#WhenAnyValueDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...SP_INPC#WhenAnyValueDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...NPC_CFP#WhenAnyValueDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...WCG.4LDC#WhenChangedDispatch.g.verified.cs | 64 ++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Property#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- .../WCG.DPC#WhenChangedDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Property#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ..._Property#ObservationHelpers.g.verified.cs | 2 +- ...Property#WhenChangedDispatch.g.verified.cs | 12 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...iewModel#WhenChangedDispatch.g.verified.cs | 26 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...CG.MP_2P#WhenChangedDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...CG.MP_3P#WhenChangedDispatch.g.verified.cs | 29 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_WDC#WhenChangedDispatch.g.verified.cs | 37 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...CG.MP_WS#WhenChangedDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...ewModels#WhenChangedDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...givingDC#WhenChangedDispatch.g.verified.cs | 28 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Property#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....SP_INPC#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...INPC_CFP#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...veObject#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ..._Property#ObservationHelpers.g.verified.cs | 79 +++++ ...Property#WhenChangedDispatch.g.verified.cs | 20 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ..._Property#ObservationHelpers.g.verified.cs | 130 +++---- ...Property#WhenChangedDispatch.g.verified.cs | 17 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...Property#WhenChangedDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...nG.4LDC#WhenChangingDispatch.g.verified.cs | 59 ++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...LDC_CFP#WhenChangingDispatch.g.verified.cs | 59 ++-- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...CnG.DPC#WhenChangingDispatch.g.verified.cs | 27 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...DPC_CFP#WhenChangingDispatch.g.verified.cs | 27 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...roperty#WhenChangingDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_2P#WhenChangingDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ..._2P_CFP#WhenChangingDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_3P#WhenChangingDispatch.g.verified.cs | 26 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ....MP_WDC#WhenChangingDispatch.g.verified.cs | 35 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...G.MP_WS#WhenChangingDispatch.g.verified.cs | 18 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...roperty#WhenChangingDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...SP_INPC#WhenChangingDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...NPC_CFP#WhenChangingDispatch.g.verified.cs | 10 +- ...#GeneratedBinderRegistration.g.verified.cs | 25 -- ...eObject#WhenChangingDispatch.g.verified.cs | 10 +- .../WinFormsSetterOverrideParityTests.cs | 114 ++++++ .../WinFormsSetterParityTests.cs | 186 ++++++++++ .../ObservationAffinityCheckerTests.cs | 178 +++++++++- .../AppliedChangeObservableTests.cs | 13 + .../ReactiveUI.Binding.Tests.csproj | 2 +- ...eUI.Binding.WinForms.Tests.Reactive.csproj | 2 +- .../ReactiveUI.Binding.WinForms.Tests.csproj | 2 +- ...activeUI.Binding.Wpf.Tests.Reactive.csproj | 2 +- .../ReactiveUI.Binding.Wpf.Tests.csproj | 2 +- 518 files changed, 13133 insertions(+), 8075 deletions(-) create mode 100644 src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ConversionEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/TypeReferenceArity.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Generators/CommandBindingHelperGenerator.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Generators/InvocationHelperRequirements.cs delete mode 100644 src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeCommandMembers.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeTypeIdentity.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/ConversionInfo.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/ConversionParameters.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandInfo.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandKind.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/NotificationEventInfo.cs delete mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/ObservableTypeInfo.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/ObservationExpression.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/PlatformObservationInfo.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodEmission.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodInfo.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AndroidCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandSymbols.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandControlEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandParameterEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingEmitter.cs delete mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandSymbols.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandSymbols.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitControlCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AndroidConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AppleConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/BooleanStringConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionPluginRegistry.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionSymbols.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/EqualityConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/GuidStringConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/IConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/LanguageConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/MauiConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NullableValueConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NumericStringConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringConversionExpressions.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringIdentityConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/TemporalStringConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UnoConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UriConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityConversion.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityHintExpressions.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WinUIConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WpfConversionPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/ICommandBindingHelperPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformCommandBindingPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformObservationPlugin.cs delete mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AfterChangeObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppKitObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppleNotificationEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/DependencyPropertyObservationEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KvoObservationEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeEventSubscriptionEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservableEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservationEmitter.cs delete mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ObservationEmissionExtensions.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PlatformSymbols.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PocoObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitValueObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UnoObservationPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/CollectionSetMethodEmitter.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/ISetMethodPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/PanelSetMethodPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/SetMethodPluginRegistry.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/TableLayoutSetMethodPlugin.cs create mode 100644 src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/WinFormsCollectionSymbols.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/NumberPicker.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/View.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterCommand.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterView.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/TypedAdapterTarget.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Button.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Control.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/NativeAdapterBenchmark.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Benchmarks/TypedAdapterBenchmark.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/AdapterGenerationBenchmarks.cs create mode 100644 src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/Support/AdapterBenchmarkCorpus.cs create mode 100644 src/benchmarks/Shared/Configs/BenchmarkRunValidation.cs rename src/benchmarks/Shared/Configs/{NativeAotMemoryConfig.cs => NativeAotTimingConfig.cs} (78%) create mode 100644 src/tests/ReactiveUI.Binding.Analyzer.Tests/BindingInvocationAnalyzerTests.NativeCommands.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleConversionParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleObservationParityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#ObservationHelpers.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#ObservationHelpers.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/TypeReferenceArityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionEdgeParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionOverrideParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Generators/BindingGeneratorTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeObservationTestModels.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeTypeIdentityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/KvoEligibilityParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandOverrideParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandParityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationContractsParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationOverrideParityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.AndroidView_Detected#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#ObservationHelpers.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WinFormsComponent_Detected#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WpfDependencyObject_Detected#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformAdapterTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/ConversionPluginRuntimeTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/StandardConversionParityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.AbstractExcl#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DefaultAndContractViewsDispatchCorrectly#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DuplicateViewModelsAreDeduplicated#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleContractViewsWithoutDefault#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleViewForImplementations#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.NoViewFor#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleViewForImplementation#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithPrivateConstructor#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithoutParameterlessConstructor#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VisibilityHintParityTests.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NoInvocations#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#ObservationHelpers.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs delete mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterOverrideParityTests.cs create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterParityTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index 469ee712..309f5e2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ See: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=dotnet ### Prerequisites ```powershell -# Check .NET installation (.NET 8.0, 9.0, 10.0 and 11.0 required) +# Check .NET installation (.NET 10.0 and 11.0 for tests and benchmarks) dotnet --info # Restore NuGet packages @@ -187,7 +187,7 @@ src/ │ │ ├── PropertyPathSegment.cs # Per-segment: name, types, and how its declaring type notifies │ │ ├── ObservablePropertyInfo.cs # Per-property: DP field and change-event participation │ │ └── ViewRegistrationInfo.cs # Per-IViewFor: view dispatch mapping -│ ├── Plugins/ # Mechanism selection (Pipeline A) +│ ├── Plugins/ # Per-property mechanism selection │ │ ├── ObservationPluginRegistry.cs # Highest-affinity plugin that reaches a given property │ │ ├── ObservedProperties.cs # Whether one property participates in a type's mechanism │ │ └── Observation/ # One plugin per mechanism, scored from BindingAffinity @@ -198,13 +198,12 @@ src/ │ │ ├── INPCObservationPlugin.cs # INotifyPropertyChanged (Explicit, 5) │ │ ├── AndroidObservationPlugin.cs # Android View (Explicit, 5) │ │ ├── WpfObservationPlugin.cs # WPF DependencyObject (WpfDependencyObject, 4) -│ │ ├── NotifyPropertyObservationPlugin.cs # Base for the INPC-watching plugins above +│ │ ├── ObservationEmissionExtensions.cs # Shared expression and chain composition │ │ └── NotifyPropertyEmitter.cs # The observation those plugins all emit │ │ └── ViewThread/ # The invoker a generated binding carries for its target │ │ ├── ViewThreadPluginRegistry.cs # Matches a target's type to its platform's invoker │ │ └── Wpf/WinForms/MauiViewThreadPlugin.cs # One plugin per platform │ ├── Generators/ # Whole-compilation outputs -│ │ ├── RegistrationGenerator.cs # Consolidates all → [ModuleInitializer] │ │ ├── ObservationHelperGenerator.cs # Declares the KVO/WinUI helper classes, once per compilation │ │ ├── ViewThreadInvokerGenerator.cs # Declares the WPF/WinForms/MAUI invoker classes, once per compilation │ │ └── ViewLocatorDispatchGenerator.cs # IViewFor → AOT view dispatch (Pipeline C) @@ -243,11 +242,19 @@ src/ └── ReactiveUI.Binding.Tests/ # Runtime library tests ``` -### Three Pipelines +### Generation Pipelines -**Pipeline A (Type Detection)**: Scans classes with base lists → builds `ClassBindingInfo` POCOs with boolean flags for each notification mechanism (IReactiveObject, INPC, WPF DP, WinUI DP, KVO, WinForms, Android) and a per-property record of which of them each declared property actually participates in. Consolidates into a single `[ModuleInitializer]` registration. +**Property metadata** is captured while each invocation's property path is extracted. Each link records its +concrete owner, eligible native mechanisms, their scores, and members verified from Roslyn symbols. The same +extraction handles source and referenced types. Selection emits the binding directly. -Affinity values are the shared `BindingAffinity` scores the runtime library declares (`Fallback = 1` … `Kvo = 15`), the same numbers ReactiveUI's own plugins return, so a user-registered plugin and a generated one rank on one scale. +Observation, command and conversion mechanisms implement their interfaces directly. They use no plugin base +classes. Shared logic lives in static helpers with internal methods. Each mechanism owns its eligibility and +emission; registries compare affinity and retain declaration order on ties. + +Affinity values match the corresponding ReactiveUI mechanisms, including property-specific UIKit scores of 30, +Apple value notifications at 20, KVO at 15, and ordinary CLR fallback at 1. Registered providers and generated +mechanisms rank on the same scale. ### Mechanisms Travel With the Property Path @@ -261,8 +268,7 @@ The mechanism is captured during extraction, which already holds the property sy the detected-type set afterwards. That is a performance constraint, not a preference: binding one of these invocations is the single largest allocation in a generation pass (extension-method overload resolution and generic type inference dominate the `GcVerbose` trace), so a second semantic pass over the same call sites is -not affordable. It also means a type from a *referenced* assembly is observed correctly even though the -declaration scan never sees it. +not affordable. A type from a *referenced* assembly follows the same extraction path as a source type. **Pipeline B (Invocation Detection)** scans calls to 13 APIs: `WhenChanged`, `WhenChanging`, `WhenAnyValue`, `WhenAny`, `WhenAnyObservable`, `BindOneWay`, `BindTwoWay`, `OneWayBind`, `Bind`, `BindTo`, `BindCommand`, @@ -434,11 +440,10 @@ tokens - which is what keeps a consumer publishing ahead-of-time free of trim an generated path reaches the runtime expression engine; routing a whole binding to it instead would put `[RequiresUnreferencedCode]` back on every call site. -Leaving the override out is not a divergence anyone could see: the registration would apply to `WhenChanged` -and silently not to a binding of the same property. The check has to be on the path of every binding, so the -registered set is resolved once and kept rather than re-read from the locator per call - re-reading cost -~141 B and ~1.2 us per binding created, which a view full of bindings pays for repeatedly. `Refresh()` -drops the cache for a host that registers a plugin after its first binding. +The registered set and strongest custom vote are cached by runtime type, property and notification timing. +Each binding compares that vote with its generated score; the generated mechanism wins ties. `Refresh()` +replaces the cache generation, so an in-flight lookup cannot repopulate it with stale registrations. Generated +expressions and object adapters for a custom provider are constructed only when that provider wins. **Every binding writes on the view's owning thread.** ReactiveUI moves a write only on WPF. It does so for a two-way `Bind` and for swapping a control's `Command`. Here every binding API moves it, on WPF, WinForms and MAUI. @@ -721,17 +726,14 @@ All pipeline models are `sealed record` types with value equality. NEVER include ### Where the Observation Helper Classes Are Declared Some plugins (`KVOObservationPlugin`, `WinUIObservationPlugin`) emit observation code that instantiates helper -classes by bare name — `__KVOObservable`, `__KVOObserver`, `__WinUIDPObservable`. Every dispatch file is +classes by bare name — `__KVOObservable`, `__KVOObserver`, `__WinUIDPObservable`. Every dispatch file is another part of the same `__ReactiveUIGeneratedBindings` class, so one part declaring them is enough for all of them, and two parts declaring them is a duplicate-member error. -`ObservationHelperGenerator` therefore owns the declarations outright, in `ObservationHelpers.g.cs`. Emitters -only ever reference the helpers; none of them declare any. Which helpers to declare is decided from the -**detected types**, not from the call sites — a reference can only be emitted for a type -`CodeGeneratorHelpers.FindClassInfo` matched, so the declarations are a superset of the references whichever -binding API reaches for them. Deciding it from the call sites instead leaves any API whose call sites were not -enumerated — `BindOneWay`, `BindTwoWay`, `Bind`, `OneWayBind`, `WhenAny`, `WhenAnyObservable` — emitting -references to types nobody declared. +`ObservationHelperGenerator` owns these declarations in `ObservationHelpers.g.cs`. +`InvocationHelperRequirements` collects the selected mechanisms from every extracted invocation and chain +link. Observation and view-thread helpers are emitted only when a binding uses them. Every invocation pipeline +must contribute its requirements through this collector, using the same property selection as its emitter. ### Two-Layer Language Version Constraint @@ -885,7 +887,7 @@ build keeps working right up until Wine starts. Each copy chains to the reposito ## Important Notes -- **Required .NET SDKs:** .NET 8.0, 9.0, 10.0 and 11.0 +- **Test and benchmark runtimes:** .NET 10.0 and 11.0 - **Generator + Analyzer targets:** netstandard2.0 (Roslyn requirement) - **Runtime library targets:** net8.0;net9.0;net10.0;net11.0;net462;net47;net471;net472;net48;net481 - **No shallow clones:** Repository requires full clone for Nerdbank.GitVersioning diff --git a/README.md b/README.md index 8bf1ef31..487a4e17 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ or another property. This library lets you say that in one line. It writes the c - [Your first binding](#your-first-binding) - [How a property reports a change](#how-a-property-reports-a-change) - [The generator checks each property in a path](#the-generator-checks-each-property-in-a-path) -- [What the generator writes](#what-the-generator-writes) -- [How a call site reaches its generated code](#how-a-call-site-reaches-its-generated-code) +- [Trimming and NativeAOT](#trimming-and-nativeaot) +- [Compiler requirements](#compiler-requirements) - [When nothing claims the call](#when-nothing-claims-the-call) - [Installing](#installing) - [Supported frameworks](#supported-frameworks) @@ -47,7 +47,6 @@ or another property. This library lets you say that in one line. It writes the c - [Performance](#performance) - [Diagnostics](#diagnostics) - [Where this differs from ReactiveUI](#where-this-differs-from-reactiveui) -- [Layout](#layout) - [Core team](#core-team) - [Contribute](#contribute) @@ -170,16 +169,17 @@ not raise `PropertyChanged`. An iOS view does not raise it at all. Each way of reporting a change is a mechanism. The generator first finds the mechanisms a type offers. Each mechanism uses a different event, and attaches to it a different way. -| Mechanism | What it is | How it is observed | Before the change | -|-----------|------------|--------------------|-------------------| -| `INotifyPropertyChanged` | The .NET interface. It has one event for the whole object. The event names the property that changed. | Attach to `PropertyChanged`, keep the events for your property, and read the getter. | no | -| `INotifyPropertyChanging` | The matching interface, raised before the value is replaced. | Attach to `PropertyChanging` the same way. `WhenChanging` needs this. | yes | -| `IReactiveObject` | ReactiveUI's interface. It raises both events above. | As above, with both events. | yes | -| WPF dependency property | `TextBox.Text` is a `DependencyProperty`, not a plain C# property. It raises no `PropertyChanged`. | Get a descriptor from `DependencyPropertyDescriptor.FromProperty(...)`, then call `AddValueChanged`. | see below | -| WinUI and MAUI bindable property | The same idea on WinUI and MAUI. | Call `RegisterPropertyChangedCallback`. Release it with the token it returns. | no | -| WinForms component | WinForms has one event per property, named by convention. | Find the `{PropertyName}Changed` event and attach to it. | no | -| Apple KVO | Key-value observing. An `NSObject` reports changes this way on Apple platforms. | Call `NSObject.AddObserver` with `NSKeyValueObservingOptions`. | yes | -| Android view | An Android widget raises its own event, such as `TextView.TextChanged`. | Attach to that widget's event for the property. | no | +| Mechanism | Supported properties | Before the change | +|-----------|----------------------|-------------------| +| `INotifyPropertyChanged` | Properties that raise `PropertyChanged`, including MAUI bindable properties. | no | +| `INotifyPropertyChanging` | Properties that raise `PropertyChanging`. | yes | +| `IReactiveObject` | ReactiveUI properties that raise change notifications. | yes | +| WPF dependency property | Dependency properties such as `TextBox.Text`. | see below | +| WinUI and Uno dependency property | Properties backed by a framework dependency property. | no | +| WinForms component | Properties with a public `{PropertyName}Changed` event. | no | +| Apple KVO | Native `NSObject` properties and exported properties. | yes | +| UIKit and AppKit notifications | Supported control text, value, date and selection properties. | no | +| Android view | Supported widget properties such as `TextView.Text`. | no | > [!NOTE] > A type can offer more than one mechanism. A `ReactiveObject` in a WPF window implements @@ -208,42 +208,37 @@ When `Address` is replaced, the subscription detaches from the old `Address` and would otherwise write this part by hand, and it is easy to get wrong. > [!WARNING] -> If a property in the path raises no change event, its value is read once. The path is followed no further. -> Nothing tells you when the app runs. So the analyzer reports RXUIBIND010 when you build. +> If a property in the path raises no change event, replacing it cannot be detected. Properties below it can +> still be observed on the initial object. The analyzer reports RXUIBIND010 when you build. -## What the generator writes +## Trimming and NativeAOT -Here is the code it writes for `vm.WhenChanged(x => x.Name)` on a class that raises `PropertyChanged`: +For example, observing a WinForms text box uses its own change event: ```csharp -private static global::System.IObservable __WhenChanged_7FFFD2E8D6FC818E(MyViewModel obj) -{ - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( - obj, - ((Expression>)(__e => __e.Name)).Body, - "Name", - false, - 5, - (object __o) => ((MyViewModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, "Name", (INotifyPropertyChanged __o) => ((MyViewModel)__o).Name, true)); -} +var changes = textBox.WhenChanged(x => x.Text); ``` -The last argument is the subscription the generator chose. Everything it needs is fixed when you build: the -declaring type, the property name, and a getter. The getter is a direct call, not a lookup by name. +The central framework calls are direct property access and event subscription: -`Choose` also checks for an `ICreatesObservableForProperty` you registered yourself. It uses yours when yours -scores higher. See [Which mechanism wins](#which-mechanism-wins). +```csharp +EventHandler handler = (_, _) => observer.OnNext(textBox.Text); +textBox.TextChanged += handler; +// When the subscription is disposed: +textBox.TextChanged -= handler; +``` -> [!IMPORTANT] -> The generated code names every type and member it touches. So a trimmer keeps them, and a binding keeps -> working with `PublishTrimmed` and `PublishAot`. +The complete observation also delivers the initial value and handles disposal. Ordinary JIT projects benefit +from avoiding runtime expression analysis and property lookup, keeping values typed, and catching unsupported +bindings during the build. Trimming and NativeAOT support are additional benefits. -## How a call site reaches its generated code +Bindings with property paths known at build time support `PublishTrimmed` and `PublishAot`. Use the normal +binding APIs with inline property lambdas. The `Unsafe` APIs use reflection and carry trimming warnings. +Custom providers remain responsible for their own trimming and NativeAOT requirements. -A call site is a line where you call a method such as `WhenChanged`. How a call site reaches its generated code -depends on the C# compiler that builds your project. +## Compiler requirements + +Use supported build tools and C# 7.3 or later. ### Which compiler you have @@ -253,23 +248,15 @@ You do not choose the compiler directly. It comes with your build tools. Studio version. - Building with `dotnet build` uses the compiler that ships with that .NET SDK version. -| Your build tools | How a call reaches the generated code | +| Your build tools | Support | |------------------|---------------------------------------| -| Visual Studio 2022 17.13 or later, Visual Studio 2026, or .NET SDK 9.0.200 or later | Interception | -| Visual Studio 2022 17.8 to 17.12, or .NET SDK 8.0.100 to 9.0.1xx | A generated overload | +| Visual Studio 2022 17.13 or later, Visual Studio 2026, or .NET SDK 9.0.200 or later | Supported | +| Visual Studio 2022 17.8 to 17.12, or .NET SDK 8.0.100 to 9.0.1xx | Supported, with call-site restrictions reported by RXUIBIND009 | | Anything older | Not supported. The build fails with RXUIBIND100. | Microsoft's [Roslyn version table](https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support) lists the compiler in each Visual Studio version. -**Interception.** The compiler replaces your call with a call to the generated method. This works from any file -and any C# language version. - -**A generated overload.** The generator adds an overload that has to win C#'s normal method lookup. RXUIBIND009 -tells you where it cannot. - -Both ways run the same generated method. A binding behaves the same either way. - ### .NET Framework projects A .NET Framework project gets the same features as a .NET project. It needs the SDK-style project format and new @@ -284,7 +271,7 @@ enough build tools. An SDK-style project file names the SDK on its first line an ``` Build that project with Visual Studio 2022 17.13 or later, or with `dotnet build` on .NET SDK 9.0.200 or later. -It then uses interception, even at the C# 7.3 language version .NET Framework projects default to. +The C# 7.3 language version used by .NET Framework projects is supported. An old-style project file has no `Sdk` attribute and lists its source files one by one. It still works. It uses the compiler from the Visual Studio that builds it. @@ -297,17 +284,6 @@ Two build properties change this. - Set `ReactiveUIBindingEmitGeneratedCodeMarkers` to `false` to drop the `// ` header from generated files. Analyzer and compiler warnings inside those files then show up. -The package holds one copy of the generator and the analyzer per compiler generation: - -``` -analyzers/dotnet/roslyn4.8/cs/ <- Roslyn 4.8 to 4.12 -analyzers/dotnet/roslyn4.13/cs/ <- Roslyn 4.13 and newer -``` - -The .NET SDK picks the highest folder your compiler supports. An old-style project that does not use the .NET -SDK gets both folders. The package's build targets remove the folder your compiler does not use. Loading the -generator twice would write every generated file twice and fail your build. - ## When nothing claims the call Some call sites cannot be read when you build. Examples are a lambda stored in a variable, an expression built @@ -581,23 +557,63 @@ Each mechanism has a score. The highest-scoring mechanism that can reach the pro | Mechanism | Type it keys on | Score | |-----------|-----------------|------:| +| UIKit control notifications | Supported text, selection, date and switch properties | 30 | +| UIKit value changes | `UIKit.UIControl.Value` with `ValueChanged` | 20 | +| AppKit control notifications | Supported `AppKit.NSControl` value properties | 20 | | Apple KVO | `Foundation.NSObject` | 15 | | IReactiveObject | `ReactiveUI.IReactiveObject` | 10 | | WinForms component | `System.ComponentModel.Component` | 8 | | WinUI bindable property | `Microsoft.UI.Xaml.DependencyObject` | 6 | +| Uno dependency property | `Windows.UI.Xaml.DependencyObject` | 6 | | INotifyPropertyChanged | `System.ComponentModel.INotifyPropertyChanged` | 5 | | Android view | `Android.Views.View` | 5 | | WPF dependency property | `System.Windows.DependencyObject` | 4 | +| Plain property | A readable property without notifications | 1 | A mechanism has to reach the property. A plain C# property on a dependency object is not a dependency property. A component property with no `{PropertyName}Changed` event has nothing to attach to. Both fall through to the next mechanism down. +A plain property emits its current value when you subscribe. It cannot report later changes. + `INotifyPropertyChanged` and `Android.Views.View` share a score. `INotifyPropertyChanged` wins that tie. An `ICreatesObservableForProperty` you register yourself uses the same scores. It takes the property when it scores higher. The generated code wins a tie. +Call `ReactiveUI.Binding.Fallback.ObservationAffinityChecker.Refresh()` after changing observation-provider +registrations so subsequent subscriptions use the updated registrations. + +### Platform adapters + +Platform support uses the framework references in your application. No extra binding platform package or +platform registration is needed for these APIs. + +`BindCommand` supports Android `Click`, UIKit target/action touch handling, refresh-control `ValueChanged`, +bar-button `Clicked`, and AppKit `Target`/`Action`. It follows command and control replacements, tracks streamed +or property-based parameters, and detaches handlers when disposed. Registered command binders take over when +their score exceeds the selected adapter's score. + +| Command mechanism | Score | +|-------------------|------:| +| UIKit refresh control or bar button | 10 | +| UIKit touch target or Android click | 9 | +| `Command` and `CommandParameter` properties | 5 | +| AppKit target/action or an event with `Enabled` | 4 | +| An event without `Enabled` | 3 | + +Supplying `toEvent` selects that event instead of the control's default command mechanism. + +`BindTo` and `OneWayBind` can populate WinForms panel and table-layout control collections from collections of +derived controls. They update the existing collection, including a read-only `Controls` property. They suspend +layout during the write and resume it even when a collection operation throws. The generated setter scores 10; +a registered `ISetMethodBindingConverter` must score higher to replace it. + +Generated conversions cover numeric, boolean, GUID, date and time strings; numeric nullable values; URIs; +framework visibility enums; and Apple `NSDate` values. Visibility conversions honor the framework's inversion +and hidden-value hints. Registered typed converters must beat the generated score. An explicit converter +override takes precedence over conversion voting. + ## Which thread a binding writes on A UI framework lets only one thread touch a view. That thread is the view's owning thread. A view model can raise @@ -773,7 +789,7 @@ build instead. From the helper, call the `Unsafe` twin to bind by reflection. ReactiveUI logs a warning the first time it observes a property on a type that raises no change event. RXUIBIND010 reports the same thing when you build. A generator can only report it then. The observation behaves -the same either way. The value is read once, and the path is followed no further. +the same either way. The value is read when you subscribe; replacing that property cannot be detected. ### A write that throws behaves the same @@ -781,30 +797,6 @@ A write that throws is logged against the bound expression. It is rethrown as a when it has an inner exception. The setter threw on the thread that raised the change, so no caller can catch it. Swallowing the exception would hide the failure. -## Layout - -``` -src/ - ReactiveUI.Binding/ Runtime package, lightweight observables - ReactiveUI.Binding.Reactive/ Runtime package, System.Reactive schedulers - ReactiveUI.Binding.Shared/ The runtime source, compiled by both packages above - ReactiveUI.Binding.SourceGenerators/ The generator, and the shipped props and targets - ReactiveUI.Binding.Analyzer/ The RXUIBIND analyzers - ReactiveUI.Binding.*.Roslyn413/ The same generator and analyzer against Roslyn 4.13 - ReactiveUI.Binding.Wpf*/ WPF integration, a standard and a .Reactive package - ReactiveUI.Binding.WinForms*/ WinForms integration, a standard and a .Reactive package - ReactiveUI.Binding.Maui*/ MAUI integration, a standard and a .Reactive package - benchmarks/ BenchmarkDotNet projects - tests/ Test projects and the shared scenario sources -``` - -A `*.Shared` folder holds source, not a project. Each package that uses it compiles its own copy. That is how one -set of code builds both a standard package and its `.Reactive` twin. `ReactiveShim.props` looks for the -`.Reactive` suffix on the project name. It defines `REACTIVE_SHIM` and maps the scheduler type to `IScheduler`. - -The generator and the analyzer target netstandard2.0, because Roslyn requires it. Generated code compiles as C# -7.3, so the oldest supported project can build it. - ## Contribute ReactiveUI.Binding.SourceGenerators uses an OSI-approved open source license. You can use and share it freely, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 66575786..fa8613f9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -35,14 +35,15 @@ $(BindingLegacyTargets);$(BindingWindowsTargets) - net8.0;net9.0;net10.0;net11.0 + net10.0;net11.0 + net10.0-windows10.0.19041.0;net11.0-windows10.0.19041.0 net10.0;net11.0 $(BindingTestTargets) - $(BindingWindowsTargets) + $(BindingTestPlatformTargets) diff --git a/src/ReactiveUI.Binding.Analyzer.Roslyn413/ReactiveUI.Binding.Analyzer.Roslyn413.csproj b/src/ReactiveUI.Binding.Analyzer.Roslyn413/ReactiveUI.Binding.Analyzer.Roslyn413.csproj index 43960129..5af59fc2 100644 --- a/src/ReactiveUI.Binding.Analyzer.Roslyn413/ReactiveUI.Binding.Analyzer.Roslyn413.csproj +++ b/src/ReactiveUI.Binding.Analyzer.Roslyn413/ReactiveUI.Binding.Analyzer.Roslyn413.csproj @@ -33,6 +33,10 @@ + + + + diff --git a/src/ReactiveUI.Binding.Analyzer/Analyzers/BindingInvocationAnalyzer.cs b/src/ReactiveUI.Binding.Analyzer/Analyzers/BindingInvocationAnalyzer.cs index b0ffcc50..feb4ce38 100644 --- a/src/ReactiveUI.Binding.Analyzer/Analyzers/BindingInvocationAnalyzer.cs +++ b/src/ReactiveUI.Binding.Analyzer/Analyzers/BindingInvocationAnalyzer.cs @@ -9,6 +9,8 @@ using Microsoft.CodeAnalysis.Operations; using ReactiveUI.Binding.Helpers; using ReactiveUI.Binding.SourceGenerators; +using ReactiveUI.Binding.SourceGenerators.Helpers; +using ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; namespace ReactiveUI.Binding.Analyzer.Analyzers; @@ -588,16 +590,18 @@ private static bool IsToEventSpecified(ImmutableArray argume /// true if a default bindable event is found; otherwise, false. private static bool HasDefaultBindableEvent(INamedTypeSymbol controlType) { + if (AppKitCommandSymbols.CanBind(controlType) || UIKitCommandSymbols.CanBindTouch(controlType) + || UIKitCommandSymbols.ControlEvent(controlType) is not null) + { + return true; + } + string[] defaultEvents = ["Click", "TouchUpInside", "MouseUp", "Pressed"]; for (var j = 0; j < defaultEvents.Length; j++) { - var members = controlType.GetMembers(defaultEvents[j]); - for (var k = 0; k < members.Length; k++) + if (NativeCommandMembers.HasEvent(controlType, defaultEvents[j])) { - if (members[k] is IEventSymbol) - { - return true; - } + return true; } } diff --git a/src/ReactiveUI.Binding.Analyzer/ReactiveUI.Binding.Analyzer.csproj b/src/ReactiveUI.Binding.Analyzer/ReactiveUI.Binding.Analyzer.csproj index 1f909b5d..f88e64a7 100644 --- a/src/ReactiveUI.Binding.Analyzer/ReactiveUI.Binding.Analyzer.csproj +++ b/src/ReactiveUI.Binding.Analyzer/ReactiveUI.Binding.Analyzer.csproj @@ -31,6 +31,10 @@ + + + + diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net10.0/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net10.0/PublicAPI.txt index 8d916186..00d019ba 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net10.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net10.0/PublicAPI.txt @@ -1989,6 +1989,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net11.0/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net11.0/PublicAPI.txt index 8d916186..00d019ba 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net11.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net11.0/PublicAPI.txt @@ -1989,6 +1989,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net462/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net462/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net462/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net462/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net47/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net47/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net47/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net47/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net471/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net471/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net471/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net471/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net472/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net472/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net472/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net472/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net48/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net48/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net48/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net48/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net481/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net481/PublicAPI.txt index d881b992..ebe62c29 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net481/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net481/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net8.0/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net8.0/PublicAPI.txt index 8d916186..00d019ba 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net8.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net8.0/PublicAPI.txt @@ -1989,6 +1989,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Reactive/PublicAPI/net9.0/PublicAPI.txt b/src/ReactiveUI.Binding.Reactive/PublicAPI/net9.0/PublicAPI.txt index 8d916186..00d019ba 100644 --- a/src/ReactiveUI.Binding.Reactive/PublicAPI/net9.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding.Reactive/PublicAPI/net9.0/PublicAPI.txt @@ -1989,6 +1989,7 @@ namespace ReactiveUI.Binding.Reactive.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.Reactive.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding.Shared/Fallback/ObservationAffinityChecker.cs b/src/ReactiveUI.Binding.Shared/Fallback/ObservationAffinityChecker.cs index a373069a..9202b623 100644 --- a/src/ReactiveUI.Binding.Shared/Fallback/ObservationAffinityChecker.cs +++ b/src/ReactiveUI.Binding.Shared/Fallback/ObservationAffinityChecker.cs @@ -2,6 +2,7 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Collections.Concurrent; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -18,21 +19,19 @@ namespace ReactiveUI.Binding.Fallback; /// override source-generated observation at runtime. /// /// -/// Every generated observation and binding asks this before it does anything else, so the answer is on the -/// path of every binding an application creates. The registered set is resolved once and kept rather than -/// re-read from the locator each time: asking the locator allocates an enumeration per call, which a view -/// with many bindings pays for repeatedly and never gets anything back for. drops the -/// resolved set for a host that registers a plugin after the first binding. +/// Registrations and their best score for each type, property and notification timing are cached until +/// . The generated mechanism wins ties. Custom providers own their reflection and AOT requirements. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class ObservationAffinityChecker { - /// The resolved plugins, or null while none have been resolved yet. - private static ICreatesObservableForProperty[]? _plugins; + /// The registrations and scores belonging to the current refresh generation. + private static SelectionCache _cache = new(); - /// Re-reads the registered plugins, for a host that registers them after the first binding. + /// Invalidates registrations and scores for subsequent selections. + /// A selection overlapping refresh may finish using its captured registrations. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Refresh() => Interlocked.Exchange(ref _plugins, null); + public static void Refresh() => Interlocked.Exchange(ref _cache, new()); /// Returns if a registered outranks . /// The type being observed. @@ -46,22 +45,8 @@ public static class ObservationAffinityChecker /// WinForms and KVO plugins all answer 0 for a property their mechanism does not reach, whatever the type - /// so asking without one makes every mechanism-specific registration score 0 and lose by construction. /// - public static bool HasHigherAffinityPlugin(Type type, string propertyName, int generatedAffinity, bool beforeChanged) - { - ArgumentExceptionHelper.ThrowIfNull(type); - ArgumentExceptionHelper.ThrowIfNull(propertyName); - - var plugins = Resolve(); - for (var i = 0; i < plugins.Length; i++) - { - if (plugins[i].GetAffinityForObject(type, propertyName, beforeChanged) > generatedAffinity) - { - return true; - } - } - - return false; - } + public static bool HasHigherAffinityPlugin(Type type, string propertyName, int generatedAffinity, bool beforeChanged) => + FindHigherAffinityPlugin(type, propertyName, generatedAffinity, beforeChanged) is not null; /// Finds the registered that outranks . /// The type being observed. @@ -71,9 +56,8 @@ public static bool HasHigherAffinityPlugin(Type type, string propertyName, int g /// The highest-scoring registration that beats the generated one, or when none does. /// or is null. /// - /// Generated code asks for the registration itself rather than for a yes-or-no, so the winner is scored - /// once and then observed through. Answering only "is there one" costs a second scan to find it again, - /// on a path every binding runs. + /// The cached custom score is compared with each call's generated affinity, so two generated mechanisms + /// observing the same property share scoring without sharing the outcome of that comparison. /// public static ICreatesObservableForProperty? FindHigherAffinityPlugin( Type type, @@ -84,43 +68,82 @@ public static bool HasHigherAffinityPlugin(Type type, string propertyName, int g ArgumentExceptionHelper.ThrowIfNull(type); ArgumentExceptionHelper.ThrowIfNull(propertyName); - var plugins = Resolve(); - var bestScore = generatedAffinity; - ICreatesObservableForProperty? best = null; + var selection = Volatile.Read(ref _cache).Find(type, propertyName, beforeChanged); + return selection.Affinity > generatedAffinity ? selection.Plugin : null; + } + + /// Identifies the inputs to a custom provider's affinity vote. + /// The type being observed. + /// The property being observed. + /// Whether notification occurs before the change. + [DebuggerDisplay("{Type.Name,nq}.{PropertyName,nq}, BeforeChanged = {BeforeChanged}")] + private readonly record struct ObservationKey(Type Type, string PropertyName, bool BeforeChanged); + + /// Keeps the strongest custom vote independently of any generated mechanism's affinity. + /// The winning registration, or null when no registration wins. + /// The registration's property-specific score. + [DebuggerDisplay("Affinity = {Affinity}")] + private readonly record struct PluginSelection(ICreatesObservableForProperty? Plugin, int Affinity); + + /// Owns registrations and scored votes so refresh cannot receive a stale publication. + private sealed class SelectionCache + { + /// The strongest vote for each observed property and notification timing. + private readonly ConcurrentDictionary _selections = new(); + + /// The cache factory, retained to avoid creating a delegate on cache hits. + private readonly Func _select; - for (var i = 0; i < plugins.Length; i++) + /// The resolved registrations, or null before resolution. + private ICreatesObservableForProperty[]? _plugins; + + /// Initializes a new instance of the class. + public SelectionCache() => _select = Select; + + /// Returns the strongest cached vote without allocating property entries for an empty registry. + /// The type being observed. + /// The property being observed. + /// Whether notification occurs before the change. + /// The strongest custom vote, or an empty selection when no registrations exist. + public PluginSelection Find(Type type, string propertyName, bool beforeChanged) => + Resolve().Length == 0 ? default : _selections.GetOrAdd(new(type, propertyName, beforeChanged), _select); + + /// Scores registrations for one observation, preserving registration order on ties. + /// The inputs to each registration's affinity vote. + /// The highest scoring registration and its score. + private PluginSelection Select(ObservationKey key) { - var score = plugins[i].GetAffinityForObject(type, propertyName, beforeChanged); - if (score <= bestScore) + var plugins = Resolve(); + var bestScore = int.MinValue; + ICreatesObservableForProperty? best = null; + + for (var i = 0; i < plugins.Length; i++) { - continue; + var score = plugins[i].GetAffinityForObject(key.Type, key.PropertyName, key.BeforeChanged); + if (score <= bestScore) + { + continue; + } + + bestScore = score; + best = plugins[i]; } - bestScore = score; - best = plugins[i]; + return new(best, bestScore); } - return best; - } - - /// Resolves the registered plugins once and keeps them. - /// The registered plugins, empty when none is registered. - /// - /// Publishing with a compare-exchange rather than a lock means the read that every binding makes is a - /// plain field read. Two threads racing the first resolve both ask the locator and one array is discarded, - /// which costs less than making every later caller take a lock to avoid it. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ICreatesObservableForProperty[] Resolve() - { - var resolved = Volatile.Read(ref _plugins); - if (resolved is not null) + /// Publishes registrations only into the cache generation that requested them. + /// The resolved registrations. + private ICreatesObservableForProperty[] Resolve() { - return resolved; - } - - ICreatesObservableForProperty[] built = [.. AppLocator.Current.GetServices()]; + var resolved = Volatile.Read(ref _plugins); + if (resolved is not null) + { + return resolved; + } - return Interlocked.CompareExchange(ref _plugins, built, null) ?? built; + ICreatesObservableForProperty[] built = [.. AppLocator.Current.GetServices()]; + return Interlocked.CompareExchange(ref _plugins, built, null) ?? built; + } } } diff --git a/src/ReactiveUI.Binding.Shared/Fallback/RuntimeBindingConverter.cs b/src/ReactiveUI.Binding.Shared/Fallback/RuntimeBindingConverter.cs index 86607851..7bee2ffb 100644 --- a/src/ReactiveUI.Binding.Shared/Fallback/RuntimeBindingConverter.cs +++ b/src/ReactiveUI.Binding.Shared/Fallback/RuntimeBindingConverter.cs @@ -46,9 +46,14 @@ public static bool TryConvert< out TTo result) { var toType = typeof(TTo); - object? boxed = value; var fromType = typeof(TFrom); + var resolved = converterOverride ?? BindingConverters.Current.ResolveConverter(fromType, toType); + if (resolved is IBindingTypeConverter typedConverter) + { + return typedConverter.TryConvert(value, conversionHint, out result!); + } + object? boxed = value; object? converted; if (converterOverride is not null) @@ -64,7 +69,6 @@ public static bool TryConvert< return false; } - var resolved = BindingConverters.Current.ResolveConverter(fromType, toType); if (BindingTypeConverterDispatch.TryConvertAny(resolved, fromType, boxed, toType, conversionHint, out converted) && converted is TTo typed) { diff --git a/src/ReactiveUI.Binding.Shared/Observables/AppliedChangeObservable.cs b/src/ReactiveUI.Binding.Shared/Observables/AppliedChangeObservable.cs index b2560946..f98cd914 100644 --- a/src/ReactiveUI.Binding.Shared/Observables/AppliedChangeObservable.cs +++ b/src/ReactiveUI.Binding.Shared/Observables/AppliedChangeObservable.cs @@ -25,6 +25,9 @@ public sealed class AppliedChangeObservable : IObservable /// The observers a change is delivered to, replaced whenever the set changes. private IObserver[] _observers = []; + /// Gets whether constructing a public change notification has any recipient. + public bool HasObservers => Volatile.Read(ref _observers).Length != 0; + /// Reports a change the binding has written. /// The change that was written. public void OnNext(BindingChange value) diff --git a/src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs index 9a3c391b..a903e32f 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs @@ -11,17 +11,13 @@ using ReactiveUI.Binding.SourceGenerators.Helpers; using ReactiveUI.Binding.SourceGenerators.Invocations; using ReactiveUI.Binding.SourceGenerators.Models; -using ReactiveUI.Binding.SourceGenerators.Plugins; -using ReactiveUI.Binding.SourceGenerators.Plugins.ViewThread; namespace ReactiveUI.Binding.SourceGenerators; /// /// The main incremental source generator entry point for ReactiveUI property observation and binding. -/// Orchestrates three pipelines: -/// Pipeline A (Type Detection): Detects notification mechanisms and generates high-affinity fallback binders. -/// Pipeline B (Invocation Detection): Detects WhenChanged/WhenChanging/Bind calls and generates per-invocation code. -/// Pipeline C (View Dispatch): Scans IViewFor<T> implementations and generates AOT-safe view locator dispatch. +/// Invocation extraction captures notification mechanisms from each bound property's owner. +/// View detection supplies the independent IViewFor<T> dispatch mappings. /// [Generator] public class BindingGenerator : IIncrementalGenerator @@ -36,37 +32,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) RegisterSharedAttributeOutput(in context, languageFeatures); - // Pipeline A: Shared type detection - var allClasses = DetectTypes(in context); - - // Single plugin-based step replaces 7 separate filter calls. - // Each type is matched against the plugin registry; the highest-affinity - // matching plugin determines the observation kind and capabilities. - var allObservableTypes = allClasses - .Select(static (classInfo, _) => - { - var plugin = ObservationPluginRegistry.GetBestPlugin(classInfo); - return plugin is null ? null : new ObservableTypeInfo( - classInfo.FullyQualifiedName, - classInfo.MetadataName, - plugin.ObservationKind, - plugin.Affinity, - plugin.SupportsBeforeChanged, - classInfo.Properties); - }) - .Where(static x => x is not null) - .Select(static (x, _) => x!); - - // Consolidate all observable types → single RegisterSourceOutput - var consolidated = allObservableTypes.Collect(); - - context.RegisterSourceOutput( - consolidated.Combine(languageFeatures), - static (ctx, data) => RegistrationGenerator.Generate(ctx, data.Left, data.Right)); - - RegisterObservationHelperOutput(in context, allObservableTypes, languageFeatures); - RegisterViewThreadInvokerOutput(in context, languageFeatures); - // Pipeline C: View locator dispatch (IViewFor scanning) ViewLocatorDispatchGenerator.Register(context, languageFeatures); @@ -91,21 +56,35 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var bindInteraction = Detect(in context, RoslynHelpers.IsBindInteractionInvocation, InteractionExtractor.ExtractBindInteractionInvocation); var bindTo = Detect(in context, RoslynHelpers.IsBindToInvocation, BindToExtractor.ExtractBindToInvocation); var invokeCommand = Detect(in context, RoslynHelpers.IsInvokeCommandInvocation, InvokeCommandExtractor.ExtractInvokeCommandInvocation); + var helpers = InvocationHelperRequirements.Select(whenChanged); + helpers = InvocationHelperRequirements.Combine(helpers, whenChanging); + helpers = InvocationHelperRequirements.Combine(helpers, whenAnyValue); + helpers = InvocationHelperRequirements.Combine(helpers, whenAny); + helpers = InvocationHelperRequirements.Combine(helpers, whenAnyObservable); + helpers = InvocationHelperRequirements.Combine(helpers, bindOneWay); + helpers = InvocationHelperRequirements.Combine(helpers, bindTwoWay); + helpers = InvocationHelperRequirements.Combine(helpers, oneWayBind); + helpers = InvocationHelperRequirements.Combine(helpers, bind); + helpers = InvocationHelperRequirements.Combine(helpers, bindCommand); + helpers = InvocationHelperRequirements.Combine(helpers, bindInteraction); + helpers = InvocationHelperRequirements.Combine(helpers, bindTo); + helpers = InvocationHelperRequirements.Combine(helpers, invokeCommand); + RegisterHelperOutput(in context, helpers, languageFeatures); // Each invocation generator receives the language-feature snapshot to control dispatch/output - WhenChangedInvocationGenerator.Register(context, whenChanged, allClasses, languageFeatures); - WhenChangingInvocationGenerator.Register(context, whenChanging, allClasses, languageFeatures); - BindOneWayInvocationGenerator.Register(context, bindOneWay, allClasses, languageFeatures); - BindTwoWayInvocationGenerator.Register(context, bindTwoWay, allClasses, languageFeatures); - OneWayBindInvocationGenerator.Register(context, oneWayBind, allClasses, languageFeatures); - BindInvocationGenerator.Register(context, bind, allClasses, languageFeatures); - WhenAnyValueInvocationGenerator.Register(context, whenAnyValue, allClasses, languageFeatures); - WhenAnyInvocationGenerator.Register(context, whenAny, allClasses, languageFeatures); - WhenAnyObservableInvocationGenerator.Register(context, whenAnyObservable, allClasses, languageFeatures); - BindInteractionInvocationGenerator.Register(context, bindInteraction, allClasses, languageFeatures); - BindCommandInvocationGenerator.Register(context, bindCommand, allClasses, languageFeatures); + WhenChangedInvocationGenerator.Register(context, whenChanged, languageFeatures); + WhenChangingInvocationGenerator.Register(context, whenChanging, languageFeatures); + BindOneWayInvocationGenerator.Register(context, bindOneWay, languageFeatures); + BindTwoWayInvocationGenerator.Register(context, bindTwoWay, languageFeatures); + OneWayBindInvocationGenerator.Register(context, oneWayBind, languageFeatures); + BindInvocationGenerator.Register(context, bind, languageFeatures); + WhenAnyValueInvocationGenerator.Register(context, whenAnyValue, languageFeatures); + WhenAnyInvocationGenerator.Register(context, whenAny, languageFeatures); + WhenAnyObservableInvocationGenerator.Register(context, whenAnyObservable, languageFeatures); + BindInteractionInvocationGenerator.Register(context, bindInteraction, languageFeatures); + BindCommandInvocationGenerator.Register(context, bindCommand, languageFeatures); BindToInvocationGenerator.Register(context, bindTo, languageFeatures); - InvokeCommandInvocationGenerator.Register(context, invokeCommand, allClasses, languageFeatures); + InvokeCommandInvocationGenerator.Register(context, invokeCommand, languageFeatures); } /// Reads the C# language version the consumer is compiling with. @@ -136,64 +115,24 @@ internal static bool HasAccessibleExpressionAttribute(Compilation compilation) && compilation.IsSymbolAccessibleWithin(attribute, compilation.Assembly); } - /// Detects every type the emitters may need a notification mechanism for. - /// The generator initialization context. - /// One entry per detected type, from declarations and from call sites alike. - /// - /// Only declarations are scanned here. A type the consumer merely references reaches the emitters through - /// the property path instead, which carries how each segment's declaring type notifies - and that costs - /// nothing extra, because extraction already holds the symbol. Resolving those types from the call sites - /// separately would mean binding every binding invocation a second time, and that binding is the single - /// largest allocation in a generation pass. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IncrementalValuesProvider DetectTypes( - in IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider - .CreateSyntaxProvider( - RoslynHelpers.IsClassWithBaseList, - TypeDetectionExtractor.ExtractClassBindingInfo) - .Where(static x => x is not null) - .Select(static (x, _) => x!); - - /// - /// Declares the observation helper classes that generated observation code instantiates by name, once - /// for the whole compilation. - /// + /// Declares only the helpers selected by extracted binding and observation calls. /// The generator initialization context. - /// Every detected type that has an observation plugin. + /// The distinct helper requirements across the invocation pipelines. /// The consumer's language-feature snapshot, which names the namespace. - /// - /// Keyed to the detected types rather than to the call sites, which keeps the declarations a superset of - /// the references: observation code can only name a helper for a detected type, whichever binding API - /// reaches for it. Collapsing the per-type kinds to a distinct set first means adding another type of an - /// already-seen kind leaves this output cached. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void RegisterObservationHelperOutput( + private static void RegisterHelperOutput( in IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider observableTypes, - IncrementalValueProvider languageFeatures) => + IncrementalValueProvider helpers, + IncrementalValueProvider languageFeatures) + { context.RegisterSourceOutput( - observableTypes - .Select(static (typeInfo, _) => typeInfo.ObservationKind) - .Collect() - .Select(static (kinds, _) => ObservationHelperGenerator.SelectHelperKinds(kinds)) - .Combine(languageFeatures), + helpers.Select(static (selection, _) => selection.ObservationKinds).Combine(languageFeatures), static (ctx, data) => ObservationHelperGenerator.Generate(ctx, data.Left, data.Right)); - /// Declares the invoker classes generated bindings carry, for each UI platform the compilation references. - /// The generator initialization context. - /// The consumer's language-feature snapshot, which names the namespace. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void RegisterViewThreadInvokerOutput( - in IncrementalGeneratorInitializationContext context, - IncrementalValueProvider languageFeatures) => context.RegisterSourceOutput( - context.CompilationProvider - .Select(static (compilation, _) => ViewThreadPluginRegistry.InvokersIn(compilation)) - .Combine(languageFeatures), + helpers.Select(static (selection, _) => selection.ViewThreadInvokers).Combine(languageFeatures), static (ctx, data) => ViewThreadInvokerGenerator.Generate(ctx, data.Left, data.Right)); + } /// Runs one syntax scan and keeps the call sites it could extract. /// The extracted call-site model. @@ -463,7 +402,7 @@ private static EquatableArray CollectTypePaths(INamespaceSymbol root) } else if (member is INamedTypeSymbol { DeclaredAccessibility: Accessibility.Public } type) { - _ = names.Add(prefix + type.Name); + _ = names.Add(prefix + type.MetadataName); } } } diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCodeGenerator.cs index 61ac6426..6dac544b 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCodeGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCodeGenerator.cs @@ -107,7 +107,6 @@ internal static void GenerateBindMethod( ViewObservableName); var (viewModelVar, viewVar) = BindingEmitterHelpers.EmitDualStreamStages(sb, DispatchApi, inv); - (viewModelVar, viewVar) = EmitRegistryConversionStages(sb, inv, viewModelVar, viewVar); EmitTwoWaySubscription(sb, inv, viewModelVar, viewVar, viewPropertyAccess, viewModelSetAccess); } @@ -179,30 +178,39 @@ private static void EmitTwoWaySubscription( string viewPropertyAccess, string viewModelSetAccess) { + var changeType = $"global::System.ValueTuple"; _ = sb.AppendLine() .Append(" var __vmTagged = new ").Append(MapSignal).Append('<').Append(inv.TargetPropertyTypeFullName).Append(", ") - .Append(BindingChange).Append(">(").Append(viewModelVar).Append(", v => new ").Append(BindingChange).AppendLine("(v, true));") + .Append(changeType).Append(">(").Append(viewModelVar).Append(", v => new ").Append(changeType) + .Append("(true, v, default(").Append(inv.SourcePropertyTypeFullName).AppendLine(")));") .Append(" var __viewTagged = new ").Append(MapSignal).Append('<').Append(inv.SourcePropertyTypeFullName).Append(", ") - .Append(BindingChange).Append(">(").Append(viewVar).Append(", v => new ").Append(BindingChange).AppendLine("(v, false));") - .Append(" var __sides = new ").Append(MergeSignal).Append('<').Append(BindingChange).AppendLine(">(__vmTagged, __viewTagged);"); + .Append(changeType).Append(">(").Append(viewVar).Append(", v => new ").Append(changeType) + .Append("(false, default(").Append(inv.TargetPropertyTypeFullName).AppendLine("), v));") + .Append(" var __sides = new ").Append(MergeSignal).Append('<').Append(changeType).AppendLine(">(__vmTagged, __viewTagged);"); var routedVar = BindingEmitterHelpers.EmitViewThreadStage(sb, inv, "__sides", "__routed", "view", inv.TargetViewThreadInvoker); _ = sb.AppendLine(" var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable();") .AppendLine().Append(" var disposable = ").Append(BindingErrors).Append(".Subscribe(").Append(routedVar).AppendLine(", __change =>") .AppendLine(GeneratedSyntax.StatementBlockOpen) - .AppendLine(" if (__change.FromViewModel)") + .AppendLine(" if (__change.Item1)") .AppendLine(" {") - .Append(" var value = (").Append(inv.TargetPropertyTypeFullName).AppendLine(")__change.Value;") + .AppendLine(" var value = __change.Item2;") .Append(" ").Append(viewPropertyAccess).AppendLine() + .AppendLine(" if (changed.HasObservers)") + .AppendLine(" {") + .Append(" changed.OnNext(new ").Append(BindingChange).AppendLine("(value, true));") + .AppendLine(" }") .AppendLine(" }") .AppendLine(" else") .AppendLine(" {") - .Append(" var value = (").Append(inv.SourcePropertyTypeFullName).AppendLine(")__change.Value;") + .AppendLine(" var value = __change.Item3;") .Append(" ").Append(viewModelSetAccess).AppendLine() + .AppendLine(" if (changed.HasObservers)") + .AppendLine(" {") + .Append(" changed.OnNext(new ").Append(BindingChange).AppendLine("(value, false));") + .AppendLine(" }") .AppendLine(" }") - .AppendLine() - .AppendLine(" changed.OnNext(__change);") .Append(" }, \"").Append(CodeGeneratorHelpers.EscapeString(inv.SourceExpressionText)).Append(" / ") .Append(CodeGeneratorHelpers.EscapeString(inv.TargetExpressionText)).AppendLine("\");") .AppendLine().Append(" return new global::ReactiveUI.Binding.ReactiveBinding<").Append(inv.TargetTypeFullName).Append(", ") @@ -210,44 +218,4 @@ private static void EmitTwoWaySubscription( .AppendLine(" global::ReactiveUI.Binding.BindingDirection.TwoWay,").AppendLine(" disposable);") .AppendLine(" }").AppendLine(); } - - /// Emits the stages that convert each direction to the type the other side declares. - /// The string builder to append to. - /// The binding invocation info. - /// The variable holding the view model side's values. - /// The variable holding the view side's values. - /// The variables to subscribe each direction to. - /// - /// Two-way needs both: each direction assigns across the same type gap, in opposite directions. - /// - private static BindingObservables EmitRegistryConversionStages( - StringBuilder sb, - BindingInvocationInfo inv, - string viewModelVar, - string viewVar) - { - if (!BindingEmitterHelpers.RequiresRegistryConversion(inv)) - { - return new(viewModelVar, viewVar); - } - - const string convertedViewModelVar = "convertedVmObs"; - const string convertedViewVar = "convertedViewObs"; - - BindingEmitterHelpers.EmitRegistryConversion( - sb, - viewModelVar, - convertedViewModelVar, - inv.SourcePropertyTypeFullName, - inv.TargetPropertyTypeFullName); - - BindingEmitterHelpers.EmitRegistryConversion( - sb, - viewVar, - convertedViewVar, - inv.TargetPropertyTypeFullName, - inv.SourcePropertyTypeFullName); - - return new(convertedViewModelVar, convertedViewVar); - } } diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCommandCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCommandCodeGenerator.cs index 46c63aad..5995bb99 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCommandCodeGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindCommandCodeGenerator.cs @@ -7,6 +7,7 @@ using System.Text; using ReactiveUI.Binding.SourceGenerators.Models; using ReactiveUI.Binding.SourceGenerators.Plugins; +using ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; namespace ReactiveUI.Binding.SourceGenerators.CodeGeneration; @@ -200,19 +201,16 @@ internal static void GenerateBindCommandMethod( .AppendLine(" return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;").AppendLine(GeneratedSyntax.StatementBlockClose) .AppendLine(); - var controlAccess = CodeGeneratorHelpers.BuildPropertyAccessChain("view", inv.ControlPropertyPath); - EmitViewModelObservations(sb, inv, viewModelClassInfo, viewClassInfo); + CommandControlEmitter.EmitRebinding(sb, inv, viewClassInfo, suffix); var plugin = CommandBindingPluginRegistry.GetBestPlugin(inv); var generatedAffinity = plugin is not null ? plugin.Affinity : -1; - var hasEvent = inv.ResolvedEventName is not null; - - EmitCommandAffinityCheck(sb, inv, controlAccess, generatedAffinity, hasEvent); + EmitCommandAffinityCheck(sb, inv, "__control", generatedAffinity, inv.HasExplicitEvent); if (plugin is not null) { - plugin.EmitBinding(sb, inv, controlAccess, supportsNullable); + plugin.EmitBinding(sb, inv, "__control", supportsNullable); } else { @@ -253,8 +251,10 @@ internal static void EmitCommandAffinityCheck( .AppendLine(" {") .AppendLine(" __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;") .Append(" global::System.IObservable __paramObs = ").Append(paramObsExpr).AppendLine(";") - .Append(" __serial.Disposable = __customBinder.BindCommandToObject<").Append(inv.ControlTypeFullName).AppendLine(">(") - .Append(" __cmd, ").Append(controlAccess).AppendLine(", __paramObs)") + .Append(" __serial.Disposable = __customBinder.BindCommandToObject<").Append(inv.ControlTypeFullName) + .Append(hasEvent ? $", {inv.ResolvedEventArgsTypeFullName ?? "global::System.EventArgs"}" : string.Empty).AppendLine(">(") + .Append(" __cmd, ").Append(controlAccess).Append(", __paramObs") + .Append(hasEvent ? $", \"{inv.ResolvedEventName}\"" : string.Empty).AppendLine(")") .AppendLine(" ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;") .AppendLine(" });") .AppendLine(" return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial);") diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindToCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindToCodeGenerator.cs index 944438a1..021ffd15 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindToCodeGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindToCodeGenerator.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Text; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; using static ReactiveUI.Binding.SourceGenerators.CodeGeneration.GeneratedTypeNames; @@ -252,37 +253,60 @@ internal static void GenerateCallerFilePathOverload( /// The stable method-name suffix. internal static void GenerateBindToMethod(StringBuilder sb, BindToInvocationInfo inv, string suffix) { - var directAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( - TargetParameterName, - inv.TargetPropertyPath, - "value", - DirectSubscriptionBodyIndent); - var convertedAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( - TargetParameterName, - inv.TargetPropertyPath, - "__converted", - ConvertedSubscriptionBodyIndent); var targetPathComment = CodeGeneratorHelpers.BuildPropertyPathString(inv.TargetPropertyPath); var extraParams = FormatExtraMethodParams(inv); // Direct assignment is only safe when the value type matches the property type and the caller did // not supply an explicit converter. A conversion hint alone is meaningless for identity assignment. var directAssign = inv.SourceValueTypeFullName == inv.TargetPropertyTypeFullName && !inv.HasConverterOverride; + var sourceVariable = "source"; _ = sb.Append(" private static ").Append(GeneratedTypeNames.IDisposable).Append(" __BindTo_").Append(suffix).Append('(') .Append(ObservableOf(inv.SourceValueTypeFullName)).Append(" source, ").Append(inv.TargetTypeFullName).Append(" target").Append(extraParams) .AppendLine(")").AppendLine(GeneratedSyntax.MemberBodyOpen).Append(" // BindTo: observable -> ").Append(targetPathComment).AppendLine(); + if (inv.SetMethod is { } setMethod) + { + _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(" var __setSource = "), "source", TargetParameterName, inv.TargetViewThreadInvoker).AppendLine(";"); + CollectionSetMethodEmitter.EmitSubscription(sb, new(TargetParameterName, inv.TargetPropertyPath, inv.SourceValueTypeFullName, setMethod, inv.TargetExpressionText, false), "__setSource"); + _ = sb.AppendLine(" return __setSubscription;").AppendLine(GeneratedSyntax.MemberBodyClose); + return; + } + + if (inv.Conversion is not null) + { + ConversionEmitter.EmitStage( + sb, + sourceVariable, + "__convertedSource", + inv.SourceValueTypeFullName, + inv.TargetPropertyTypeFullName, + inv.Conversion, + new(inv.HasConversionHint ? "conversionHint" : "null", inv.HasConverterOverride ? "converterOverride" : "null")); + sourceVariable = "__convertedSource"; + directAssign = true; + } + if (directAssign) { - _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(ReturnPrefix).Append(BindingErrors).Append(".Subscribe("), "source", TargetParameterName, inv.TargetViewThreadInvoker) + var directAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( + TargetParameterName, + inv.TargetPropertyPath, + "value", + DirectSubscriptionBodyIndent); + _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(ReturnPrefix).Append(BindingErrors).Append(".Subscribe("), sourceVariable, TargetParameterName, inv.TargetViewThreadInvoker) .AppendLine(", value =>").AppendLine(GeneratedSyntax.StatementBlockOpen) .Append(" ").Append(directAssignment).AppendLine().Append(" }, \"") .Append(CodeGeneratorHelpers.EscapeString(inv.TargetExpressionText)).AppendLine("\");").AppendLine(GeneratedSyntax.MemberBodyClose).AppendLine(); } else { - _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(ReturnPrefix).Append(BindingErrors).Append(".Subscribe("), "source", TargetParameterName, inv.TargetViewThreadInvoker) + var convertedAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( + TargetParameterName, + inv.TargetPropertyPath, + "__converted", + ConvertedSubscriptionBodyIndent); + _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(ReturnPrefix).Append(BindingErrors).Append(".Subscribe("), sourceVariable, TargetParameterName, inv.TargetViewThreadInvoker) .AppendLine(", value =>").AppendLine(GeneratedSyntax.StatementBlockOpen) .Append(" if (").Append(RuntimeBindingConverter).Append(".TryConvert<").Append(inv.SourceValueTypeFullName).Append(", ") .Append(inv.TargetPropertyTypeFullName).Append(">(value, ").Append(FormatConversionArguments(inv)).AppendLine(", out var __converted))") diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindingEmitterHelpers.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindingEmitterHelpers.cs index 7b72670f..e4d6dec7 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindingEmitterHelpers.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/BindingEmitterHelpers.cs @@ -30,6 +30,9 @@ internal static class BindingEmitterHelpers /// Opens a delegate parameter, ready for the two type arguments and the parameter name. private const string FuncParameterPrefix = ", global::System.Func<"; + /// The stream carrying values converted for the target. + private const string ConvertedForwardName = "__convertedForward"; + /// Emits a whole binding dispatch file, claiming its call sites through one API's dispatch. /// The detected call sites for this API. /// All detected class binding info. @@ -378,7 +381,7 @@ internal static string FormatExtraMethodParams(BindingInvocationInfo inv, string [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool RequiresRegistryConversion(BindingInvocationInfo inv) => !inv.HasConversion - && !string.Equals(inv.SourcePropertyTypeFullName, inv.TargetPropertyTypeFullName, StringComparison.Ordinal); + && (inv.ForwardConversion is not null || !string.Equals(inv.SourcePropertyTypeFullName, inv.TargetPropertyTypeFullName, StringComparison.Ordinal)); /// Emits a stage that converts observed values to the type the other side declares. /// The string builder to append to. @@ -397,13 +400,7 @@ internal static void EmitRegistryConversion( string resultVar, string fromTypeFullName, string toTypeFullName) => - sb.Append(" var ").Append(resultVar).Append(" = new ").Append(GeneratedTypeNames.MapSignal).Append('<') - .Append(fromTypeFullName).Append(", ").Append(toTypeFullName).AppendLine(">(").Append(" ").Append(sourceVar) - .AppendLine(",").AppendLine(" __value =>").AppendLine(" {").Append(" ") - .Append(toTypeFullName).AppendLine(" __converted;").Append(" ").Append(GeneratedTypeNames.RuntimeBindingConverter) - .Append(".TryConvert<").Append(fromTypeFullName).Append(", ").Append(toTypeFullName) - .AppendLine(">(__value, null, null, out __converted);").AppendLine(" return __converted;") - .AppendLine(" });"); + ConversionEmitter.EmitStage(sb, sourceVar, resultVar, fromTypeFullName, toTypeFullName, null); /// Emits the stage that delivers a write on the owning thread of the object it lands on. /// The string builder to append to. @@ -734,6 +731,11 @@ internal static string EmitSingleStreamStages(StringBuilder sb, BindingDispatchA { currentVar = AppendMapStage(sb, forward, currentVar, inv.HasScheduler); } + else if (inv.SetMethod is null && RequiresRegistryConversion(inv)) + { + ConversionEmitter.EmitStage(sb, currentVar, ConvertedForwardName, inv.SourcePropertyTypeFullName, inv.TargetPropertyTypeFullName, inv.ForwardConversion); + currentVar = ConvertedForwardName; + } if (inv.HasScheduler) { @@ -764,6 +766,13 @@ internal static BindingObservables EmitDualStreamStages(StringBuilder sb, Bindin sourceVar = AppendMapStage(sb, forward, sourceVar, inv.HasScheduler); targetVar = AppendMapStage(sb, reverse, targetVar, inv.HasScheduler); } + else if (RequiresRegistryConversion(inv)) + { + ConversionEmitter.EmitStage(sb, sourceVar, ConvertedForwardName, inv.SourcePropertyTypeFullName, inv.TargetPropertyTypeFullName, inv.ForwardConversion); + ConversionEmitter.EmitStage(sb, targetVar, "__convertedReverse", inv.TargetPropertyTypeFullName, inv.SourcePropertyTypeFullName, inv.ReverseConversion); + sourceVar = ConvertedForwardName; + targetVar = "__convertedReverse"; + } if (inv.HasScheduler) { diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ConversionEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ConversionEmitter.cs new file mode 100644 index 00000000..4834a046 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ConversionEmitter.cs @@ -0,0 +1,169 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.CodeGeneration; + +/// Emits typed conversions, with legacy adaptation confined to a winning custom converter. +internal static class ConversionEmitter +{ + /// The indentation of a generated chooser's statements. + private const string BodyIndent = " "; + + /// Opens a generated custom-converter branch. + private const string ConverterBlockOpen = " {"; + + /// Closes a generated custom-converter branch. + private const string ConverterBlockClose = " }"; + + /// Emits a conversion stage using the default hint and converter selection. + /// The generated file. + /// The source observable variable. + /// The converted observable variable. + /// The declared input type. + /// The declared output type. + /// The selected generated conversion. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EmitStage(StringBuilder builder, string source, string result, string from, string to, ConversionInfo? conversion) => + EmitStage(builder, source, result, from, to, conversion, new("null", "null")); + + /// Emits conversion selection once per binding and typed delivery for each value. + /// The generated file. + /// The source observable variable. + /// The converted observable variable. + /// The declared input type. + /// The declared output type. + /// The selected generated conversion. + /// The caller's hint and converter override. + internal static void EmitStage( + StringBuilder builder, + string source, + string result, + string from, + string to, + ConversionInfo? conversion, + ConversionParameters parameters) + { + if (conversion is not null) + { + AppendSelection(builder, result, from, to, conversion, parameters); + } + + _ = builder.Append(" var ").Append(result).Append(" = "); + if (conversion?.IsIdentity == true) + { + _ = builder.Append(result).Append("Converter == null ? (global::System.IObservable<").Append(to).Append(">)").Append(source).Append(" : "); + } + + _ = builder.Append("global::ReactiveUI.Primitives.LinqExtensions.Choose<").Append(from).Append(", ").Append(to).AppendLine(">(") + .Append(" ").Append(source).AppendLine(",") + .AppendLine(" __value =>") + .AppendLine(" {"); + + if (conversion is null) + { + AppendRuntimeBody(builder, from, to, parameters); + } + else + { + AppendNativeBody(builder, result, from, to, conversion, parameters); + } + + _ = builder.AppendLine(" });"); + } + + /// Selects an explicit converter or a registration that beats the generated score. + /// The generated file. + /// The stage variable prefix. + /// The input type. + /// The output type. + /// The generated candidate. + /// The caller's conversion inputs. + private static void AppendSelection(StringBuilder builder, string result, string from, string to, ConversionInfo conversion, ConversionParameters parameters) => + _ = builder.Append(" global::ReactiveUI.Binding.IBindingTypeConverter ").Append(result).Append("Converter = ").Append(parameters.Override).AppendLine(";") + .Append(" if (").Append(result).AppendLine("Converter == null)") + .AppendLine(" {") + .Append(" ").Append(result).Append("Converter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(") + .Append(from).Append("), typeof(").Append(to).AppendLine("));") + .Append(" if (").Append(result).Append("Converter != null && ").Append(result).Append("Converter.GetAffinityForObjects() <= ") + .Append(conversion.Affinity).AppendLine(")") + .AppendLine(" {") + .Append(" ").Append(result).AppendLine("Converter = null;") + .AppendLine(" }") + .AppendLine(" }"); + + /// Emits the runtime route when no compile-time conversion is available. + /// The generated file. + /// The input type. + /// The output type. + /// The caller's conversion inputs. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendRuntimeBody(StringBuilder builder, string from, string to, ConversionParameters parameters) => + builder.Append(BodyIndent).Append(to).AppendLine(" __converted;") + .Append(" var __success = ").Append(GeneratedTypeNames.RuntimeBindingConverter).Append(".TryConvert<") + .Append(from).Append(", ").Append(to).Append(">(__value, ").Append(parameters.Hint).Append(", ").Append(parameters.Override).AppendLine(", out __converted);") + .AppendLine(" return (__success, __converted);"); + + /// Dispatches a winning custom provider before evaluating the generated conversion. + /// The generated file. + /// The stage variable prefix. + /// The input type. + /// The output type. + /// The generated candidate. + /// The caller's conversion inputs. + private static void AppendNativeBody(StringBuilder builder, string result, string from, string to, ConversionInfo conversion, ConversionParameters parameters) + { + _ = builder.Append(" object __hint = ").Append(parameters.Hint).AppendLine(";") + .Append(" if (").Append(result).AppendLine("Converter != null)") + .AppendLine(" {") + .Append(" if (").Append(result).Append("Converter is global::ReactiveUI.Binding.IBindingTypeConverter<") + .Append(from).Append(", ").Append(to).AppendLine("> __typed)") + .AppendLine(ConverterBlockOpen) + .Append(" ").Append(to).AppendLine(" __converted;") + .AppendLine(" if (__typed.TryConvert(__value, __hint, out __converted))") + .AppendLine(" {") + .AppendLine(" return (true, __converted);") + .AppendLine(" }") + .AppendLine(ConverterBlockClose) + .AppendLine(" else") + .AppendLine(ConverterBlockOpen) + .AppendLine(" object __boxed;") + .Append(" if (").Append(result).AppendLine("Converter.TryConvertTyped(__value, __hint, out __boxed))") + .AppendLine(" {") + .Append(" return (true, (").Append(to).AppendLine(")__boxed);") + .AppendLine(" }") + .AppendLine(ConverterBlockClose); + AppendRejected(builder, to, conversion, parameters); + _ = builder.AppendLine(" }"); + if (conversion.Preparation.Length != 0) + { + _ = builder.Append(BodyIndent).AppendLine(conversion.Preparation); + } + + var rejected = conversion.AssignmentFallback is null ? $"(false, default({to}))" : $"(true, {conversion.AssignmentFallback})"; + _ = builder.Append(" return ").Append(conversion.Condition).Append(" ? (true, ").Append(conversion.Expression) + .Append(") : ").Append(rejected).AppendLine(";"); + } + + /// Preserves assignability fallback for a registered converter that declines a value. + /// The generated file. + /// The output type. + /// The generated conversion. + /// The explicit conversion inputs. + private static void AppendRejected(StringBuilder builder, string to, ConversionInfo conversion, ConversionParameters parameters) + { + if (conversion.AssignmentFallback is not null) + { + _ = builder.Append(" if (").Append(parameters.Override).AppendLine(" == null)") + .AppendLine(ConverterBlockOpen) + .Append(" return (true, ").Append(conversion.AssignmentFallback).AppendLine(");") + .AppendLine(ConverterBlockClose); + } + + _ = builder.Append(" return (false, default(").Append(to).AppendLine("));"); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs index 7bfb9e94..fb2e0978 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs @@ -258,7 +258,7 @@ internal static void GenerateShallowPathObservation( bool isBeforeChange) { var segment = path[0]; - var plugin = ResolveRootPlugin(classInfo, segment); + var plugin = ResolveRootPlugin(classInfo, segment, isBeforeChange); ChainRegistrationEmitter.AppendChoiceOpen( sb, @@ -336,7 +336,7 @@ internal static void GenerateShallowObservableVariable( string varName) { var segment = path[0]; - var plugin = ResolveRootPlugin(classInfo, segment); + var plugin = ResolveRootPlugin(classInfo, segment, isBeforeChange); var mechanismVariable = varName + MechanismVariableSuffix; if (plugin is not null) @@ -397,7 +397,7 @@ internal static void GenerateDeepChainVariable( var obs0Var = $"{varName}_s0"; var rootPlugin = ResolveRootPlugin(classInfo, seg0); - EmitChainRootWithChoice(sb, "obj", seg0, classInfo, rootPlugin, isBeforeChange, obs0Var); + EmitChainRootWithChoice(sb, "obj", seg0, classInfo, rootPlugin, false, obs0Var); EmitDeepChainInnerSegments(sb, path, isBeforeChange, varName); @@ -540,10 +540,8 @@ internal static void GenerateSinglePropertyObservation( string propertyName, bool isBeforeChange) { - var plugin = classInfo is not null - ? ObservationPluginRegistry.GetBestPlugin(classInfo, propertyName) - : null; var segment = inv.PropertyPaths[0][0]; + var plugin = ResolveRootPlugin(classInfo, segment, isBeforeChange); ChainRegistrationEmitter.AppendChoiceOpen( sb, @@ -594,7 +592,7 @@ internal static void GenerateDeepChainObservation( var rootPlugin = ResolveRootPlugin(classInfo, seg0); // First segment: observe root object for first property - EmitChainRootWithChoice(sb, "obj", seg0, classInfo, rootPlugin, isBeforeChange, "__obs0"); + EmitChainRootWithChoice(sb, "obj", seg0, classInfo, rootPlugin, false, "__obs0"); EmitObservationChainInnerSegments(sb, path, isBeforeChange); @@ -622,7 +620,7 @@ internal static void EmitInlineObservation( ClassBindingInfo? classInfo, string variableName) { - var plugin = classInfo is not null ? ObservationPluginRegistry.GetBestPlugin(classInfo) : null; + var plugin = ResolveRootPlugin(classInfo, propertyPath[0]); if (propertyPath.Length == 1) { @@ -657,6 +655,17 @@ internal static void EmitInlineObservation( } } + /// Selects a property's mechanism using its declaring type when the property is inherited. + /// The type named by the call site, or null when unavailable. + /// The observed property and its declaring type. + /// Whether the requested notification precedes the change. + /// The winning observation plugin, or null when no mechanism reaches the property. + internal static IObservationPlugin? ResolveRootPlugin(ClassBindingInfo? classInfo, PropertyPathSegment segment, bool isBeforeChange = false) + { + var owner = segment.DeclaringTypeInfo ?? classInfo; + return owner is null ? null : ObservationPluginRegistry.GetBestPlugin(owner, segment.PropertyName, isBeforeChange); + } + /// Renders a flag as the generated output spells it. /// The flag to render. /// The literal a generated argument carries. @@ -701,7 +710,7 @@ private static void EmitInlinePluginChoice( const string observableOpen = "? (global::System.IObservable<"; _ = sb.Append(layout.DeclarationPrefix).Append(pluginVariable).Append(" = ").Append(ObservationAffinityChecker) - .Append(".FindHigherAffinityPlugin(typeof(").Append(declaringType).Append("), \"").Append(segment.PropertyName) + .Append(".FindHigherAffinityPlugin(").Append(rootVar).Append(".GetType(), \"").Append(segment.PropertyName) .Append("\", ").Append(generatedAffinity).Append(", ").Append(BooleanLiteral(isBeforeChange)).AppendLine(");") .Append(layout.DeclarationPrefix).Append(layout.VariableName).Append(" = ").Append(pluginVariable).AppendLine(" == null") .Append(layout.ContinuationIndent).Append(observableOpen).Append(valueType).Append(">)").AppendLine(layout.MechanismVariable) @@ -781,6 +790,7 @@ private static string ChangingSourceArgumentFor(string rootVar) => /// Picks the observation plugin for the type that declares a chain segment's property. /// The chain segment, which carries how its declaring type notifies. + /// Whether the property is observed before it changes. /// The plugin for that type, or null to fall back to reading the property. /// /// Each link of a chain is declared by its own type and notifies - or does not - on its own terms, so the @@ -789,34 +799,10 @@ private static string ChangingSourceArgumentFor(string rootVar) => /// merely different. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IObservationPlugin? ResolveSegmentPlugin(PropertyPathSegment segment) => + private static IObservationPlugin? ResolveSegmentPlugin(PropertyPathSegment segment, bool isBeforeChange = false) => segment.DeclaringTypeInfo is null ? null - : ObservationPluginRegistry.GetBestPlugin(segment.DeclaringTypeInfo, segment.PropertyName); - - /// Resolves the mechanism for a property observed directly on the type a call site names. - /// The type the call site observes, or null when it was never detected. - /// The property being observed. - /// The mechanism to observe it through, or null when nothing reaches it. - /// - /// The type a call site names advertises the mechanism, but only the type that declares the property knows - /// whether that property takes part in it - and the two differ for an inherited property, which the named - /// type's own member list does not mention. Answering from the named type alone treats every inherited - /// property as participating, which for a dependency object means emitting a companion field that an - /// inherited plain property does not have, and the consumer's build is what discovers it. - /// - private static IObservationPlugin? ResolveRootPlugin(ClassBindingInfo? classInfo, PropertyPathSegment segment) - { - if (classInfo is null) - { - return null; - } - - return ObservedProperties.IsDeclaredByConsumer(classInfo, segment.PropertyName) - || segment.DeclaringTypeInfo is null - ? ObservationPluginRegistry.GetBestPlugin(classInfo, segment.PropertyName) - : ObservationPluginRegistry.GetBestPlugin(segment.DeclaringTypeInfo, segment.PropertyName); - } + : ObservationPluginRegistry.GetBestPlugin(segment.DeclaringTypeInfo, segment.PropertyName, isBeforeChange); /// /// Chains the segments after the root for the standalone observation method, which names its @@ -842,7 +828,8 @@ private static void EmitObservationChainInnerSegments( var lambdaParam = $"__parent{s}"; var segType = seg.PropertyTypeFullName; var segInfo = seg.DeclaringTypeInfo; - var segPlugin = ResolveSegmentPlugin(seg); + var beforeLeaf = isBeforeChange && s == path.Length - 1; + var segPlugin = ResolveSegmentPlugin(seg, beforeLeaf); // Only the leaf suppresses. Inner segments keep pushing the null downstream so the // stage below re-parents onto null and drops its subscription on the detached subtree. @@ -855,14 +842,7 @@ private static void EmitObservationChainInnerSegments( if (segPlugin is not null) { - segPlugin.EmitDeepChainInnerSegment( - sb, - prevVar, - curVar, - lambdaParam, - seg, - isBeforeChange, - nullParentBehavior); + segPlugin.EmitDeepChainInnerSegment(sb, new(prevVar, curVar, lambdaParam), seg, beforeLeaf, nullParentBehavior); } else if (IsINPChanging(segInfo) && isBeforeChange) { @@ -914,7 +894,8 @@ private static void EmitDeepChainInnerSegments( var lambdaParam = $"{varName}_p{s}"; var segType = seg.PropertyTypeFullName; var segInfo = seg.DeclaringTypeInfo; - var segPlugin = ResolveSegmentPlugin(seg); + var beforeLeaf = isBeforeChange && s == path.Length - 1; + var segPlugin = ResolveSegmentPlugin(seg, beforeLeaf); var nullParentBehavior = s == path.Length - 1 ? NullParentObservationBehavior.SuppressEmission @@ -925,14 +906,7 @@ private static void EmitDeepChainInnerSegments( if (segPlugin is not null) { - segPlugin.EmitDeepChainInnerSegment( - sb, - prevObsVar, - curObsVar, - lambdaParam, - seg, - isBeforeChange, - nullParentBehavior); + segPlugin.EmitDeepChainInnerSegment(sb, new(prevObsVar, curObsVar, lambdaParam), seg, beforeLeaf, nullParentBehavior); } else if (IsINPChanging(segInfo) && isBeforeChange) { @@ -991,14 +965,7 @@ private static void EmitInlineDeepChain( if (segPlugin is not null) { - segPlugin.EmitDeepChainInnerSegment( - sb, - prevVar, - curVar, - lambdaParam, - seg, - isBeforeChange: false, - nullParentBehavior: NullParentObservationBehavior.EmitDefault); + segPlugin.EmitDeepChainInnerSegment(sb, new(prevVar, curVar, lambdaParam), seg, isBeforeChange: false, nullParentBehavior: NullParentObservationBehavior.EmitDefault); continue; } diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/OneWayBindCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/OneWayBindCodeGenerator.cs index 04998ae0..7f0c04d3 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/OneWayBindCodeGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/OneWayBindCodeGenerator.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Text; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; using static ReactiveUI.Binding.SourceGenerators.CodeGeneration.GeneratedTypeNames; namespace ReactiveUI.Binding.SourceGenerators.CodeGeneration; @@ -63,11 +64,6 @@ internal static void GenerateOneWayBindMethod( ClassBindingInfo? targetClassInfo, string suffix) { - var viewAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( - "view", - inv.TargetPropertyPath, - "value", - SubscriptionBodyIndent); BindingEmitterHelpers.AppendWorkerMethodHeader(sb, DispatchApi, inv, suffix); // Emit inline observation code instead of delegating to WhenChanged dispatch @@ -83,13 +79,23 @@ internal static void GenerateOneWayBindMethod( var currentVar = BindingEmitterHelpers.EmitSingleStreamStages(sb, DispatchApi, inv); - if (BindingEmitterHelpers.RequiresRegistryConversion(inv)) + currentVar = BindingEmitterHelpers.EmitViewThreadStage(sb, inv, currentVar, "viewThreadObs", "view", inv.TargetViewThreadInvoker); + + if (inv.SetMethod is { } setMethod) { - currentVar = EmitRegistryConversionStage(sb, inv, currentVar); + CollectionSetMethodEmitter.EmitSubscription(sb, new("view", inv.TargetPropertyPath, inv.SourcePropertyTypeFullName, setMethod, inv.TargetExpressionText, true), currentVar); + _ = sb.Append(" return new global::ReactiveUI.Binding.ReactiveBinding<").Append(inv.TargetTypeFullName).Append(", ") + .Append(inv.TargetPropertyTypeFullName).AppendLine(">(view, __setChanges, global::ReactiveUI.Binding.BindingDirection.OneWay,") + .AppendLine(" new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__setSubscription, __setChanges));") + .AppendLine(GeneratedSyntax.MemberBodyClose); + return; } - currentVar = BindingEmitterHelpers.EmitViewThreadStage(sb, inv, currentVar, "viewThreadObs", "view", inv.TargetViewThreadInvoker); - + var viewAssignment = CodeGeneratorHelpers.BuildGuardedAssignment( + "view", + inv.TargetPropertyPath, + "value", + SubscriptionBodyIndent); _ = sb.AppendLine().Append(" var sub = ").Append(BindingErrors).Append(".Subscribe(").Append(currentVar).AppendLine(", value =>") .AppendLine(GeneratedSyntax.StatementBlockOpen).Append(" ").Append(viewAssignment).AppendLine().Append(" }, \"") .Append(CodeGeneratorHelpers.EscapeString(inv.TargetExpressionText)).AppendLine("\");").AppendLine() diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/RuntimeFlavourRewriter.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/RuntimeFlavourRewriter.cs index 7b66c15f..c2a238f9 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/RuntimeFlavourRewriter.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/RuntimeFlavourRewriter.cs @@ -96,7 +96,7 @@ private static string ShiftNamespace( // Signals.MapSignal, which the shared core declares and which therefore keeps its name. var pathEnd = matchWholePath ? ExtendThroughDots(source, segmentEnd) : segmentEnd; - if (Declares(shiftedNamespaceMembers, source, segmentStart, pathEnd - segmentStart) + if (Declares(shiftedNamespaceMembers, source, segmentStart, pathEnd - segmentStart, matchWholePath) || (matchWholePath && DeclaresAnyPrefix(shiftedNamespaceMembers, source, segmentStart, pathEnd))) { _ = builder.Append(source, copiedTo, segmentStart - copiedTo).Append(ReactiveSegment); @@ -114,13 +114,16 @@ private static string ShiftNamespace( /// The generated source. /// The index the name starts at. /// The length of the name. + /// Whether metadata generic arity must match the reference. /// when the name is one the shifted namespace declares. - private static bool Declares(EquatableArray shiftedNamespaceMembers, string source, int start, int length) + private static bool Declares(EquatableArray shiftedNamespaceMembers, string source, int start, int length, bool matchArity) { for (var i = 0; i < shiftedNamespaceMembers.Length; i++) { var member = shiftedNamespaceMembers[i]; - if (member.Length == length && string.CompareOrdinal(member, 0, source, start, length) == 0) + var marker = matchArity ? member.IndexOf('`') : -1; + if ((marker < 0 ? member.Length : marker) == length && string.CompareOrdinal(member, 0, source, start, length) == 0 + && (!matchArity || TypeReferenceArity.Read(source, start + length) == TypeReferenceArity.FromMetadata(member, marker))) { return true; } @@ -177,7 +180,7 @@ private static bool DeclaresAnyPrefix( continue; } - if (Declares(shiftedNamespaceMembers, source, start, cut - start)) + if (Declares(shiftedNamespaceMembers, source, start, cut - start, true)) { return true; } diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/TypeReferenceArity.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/TypeReferenceArity.cs new file mode 100644 index 00000000..b97c47aa --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/TypeReferenceArity.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.CodeGeneration; + +/// Distinguishes generic type references when runtime flavours share a simple type name. +internal static class TypeReferenceArity +{ + /// The numeric base of metadata arity suffixes. + private const int DecimalBase = 10; + + /// Reads a CLR metadata name's generic arity without allocating a substring. + /// The metadata name. + /// The backtick position, or minus one for a non-generic type. + /// The declared arity. + internal static int FromMetadata(string name, int marker) + { + if (marker < 0) + { + return 0; + } + + var arity = 0; + for (var i = marker + 1; i < name.Length; i++) + { + arity = (arity * DecimalBase) + name[i] - '0'; + } + + return arity; + } + + /// Counts the type arguments immediately following a generated type name. + /// The generated C#. + /// The position after the type name. + /// The reference's arity, or zero for a non-generic reference. + internal static int Read(string source, int start) => + start < source.Length && source[start] == '<' ? ReadArguments(source, start) : 0; + + /// Ignores commas within nested generics, tuple elements and array ranks. + /// The generated C#. + /// The opening angle bracket. + /// The number of outer type arguments. + private static int ReadArguments(string source, int start) + { + var depth = 0; + var groups = 0; + var count = 1; + for (var i = start; i < source.Length; i++) + { + switch (source[i]) + { + case '<': + { + depth++; + break; + } + + case '>': + { + depth--; + if (depth == 0) + { + return count; + } + + break; + } + + case '(' or '[': + { + groups++; + break; + } + + case ')' or ']': + { + groups--; + break; + } + + case ',' when depth == 1 && groups == 0: + { + count++; + break; + } + } + } + + return 0; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Constants.cs b/src/ReactiveUI.Binding.SourceGenerators/Constants.cs index bc8d6bfe..4c9e1cb6 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Constants.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Constants.cs @@ -97,9 +97,6 @@ internal static class Constants /// The generated namespace segment used when the compilation has no assembly name. internal const string AnonymousAssemblyNamespaceSegment = "Anonymous"; - /// Class name for the generated module-initializer registration class. - internal const string GeneratedBinderRegistrationClassName = "__GeneratedBinderRegistration"; - /// Method name for after-change property observation (WhenChanged). internal const string WhenChangedMethodName = "WhenChanged"; diff --git a/src/ReactiveUI.Binding.SourceGenerators/Generators/CommandBindingHelperGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Generators/CommandBindingHelperGenerator.cs new file mode 100644 index 00000000..e1014e21 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Generators/CommandBindingHelperGenerator.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins; + +namespace ReactiveUI.Binding.SourceGenerators.Generators; + +/// Emits native command bridges only for selected call-site mechanisms. +internal static class CommandBindingHelperGenerator +{ + /// Reserves enough space for a native selector bridge. + private const int HelperCapacity = 4096; + + /// Shares each selected plugin's bridge across the consumer's command bindings. + /// The output context. + /// The command call sites. + /// The consumer's language and runtime options. + internal static void Generate(in SourceProductionContext context, ImmutableArray invocations, in LanguageFeatures features) + { + HashSet? selected = null; + foreach (var invocation in invocations) + { + if (CommandBindingPluginRegistry.GetBestPlugin(invocation) is not ICommandBindingHelperPlugin helper) + { + continue; + } + + selected ??= []; + _ = selected.Add(helper); + } + + if (selected is null) + { + return; + } + + var sb = PooledBuilder.Rent(HelperCapacity); + CodeGeneratorHelpers.AppendExtensionClassHeader(sb, features); + foreach (var helper in selected) + { + helper.EmitHelper(sb); + } + + CodeGeneratorHelpers.AppendExtensionClassFooter(sb); + CodeGeneratorHelpers.AddGeneratedSource(context, "CommandBindingHelpers.g.cs", PooledBuilder.ToStringAndReturn(sb), features); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Generators/InvocationHelperRequirements.cs b/src/ReactiveUI.Binding.SourceGenerators/Generators/InvocationHelperRequirements.cs new file mode 100644 index 00000000..99ee4761 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Generators/InvocationHelperRequirements.cs @@ -0,0 +1,312 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Generators; + +/// Collects helper requirements from the same property paths used by dispatch emission. +internal static class InvocationHelperRequirements +{ + /// Projects one API's extracted invocations into a cacheable set of helper requirements. + /// The extracted invocation model. + /// The existing extraction pipeline. + /// The helpers used by this API. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static IncrementalValueProvider Select( + IncrementalValuesProvider invocations) + where T : class => + invocations.Collect() + .Select(static (data, _) => Collect(data, ImmutableArray.Empty)); + + /// Adds another API's requirements without re-extracting its call sites. + /// The extracted invocation model. + /// The requirements of the preceding APIs. + /// The existing extraction pipeline. + /// The merged requirements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static IncrementalValueProvider Combine( + IncrementalValueProvider requirements, + IncrementalValuesProvider invocations) + where T : class => + requirements.Combine(Select(invocations)) + .Select(static (data, _) => new Selection( + Merge(data.Left.ObservationKinds, data.Right.ObservationKinds), + Merge(data.Left.ViewThreadInvokers, data.Right.ViewThreadInvokers))); + + /// Visits the observation paths and write routes emitted by one API. + /// The extracted invocation model. + /// The successfully extracted invocations. + /// The consumer's detected type declarations. + /// Distinct helper names in stable order. + private static Selection Collect(ImmutableArray invocations, ImmutableArray classes) + where T : class + { + SortedSet? observationKinds = null; + SortedSet? invokers = null; + for (var i = 0; i < invocations.Length; i++) + { + AddInvocation(invocations[i], classes, ref observationKinds, ref invokers); + } + + return new(ToArray(observationKinds), ToArray(invokers)); + } + + /// Follows each API's observation and write contracts. + /// The extracted invocation. + /// The consumer's detected type declarations. + /// The selected observation helpers. + /// The selected write invokers. + private static void AddInvocation( + object invocation, + ImmutableArray classes, + ref SortedSet? kinds, + ref SortedSet? invokers) + { + switch (invocation) + { + case InvocationInfo observation: + { + AddPaths(observation.SourceTypeFullName, observation.PropertyPaths, classes, observation.IsBeforeChange, ref kinds); + break; + } + + case WhenAnyObservableInvocationInfo observation: + { + AddPaths(observation.SourceTypeFullName, observation.PropertyPaths, classes, false, ref kinds); + break; + } + + case BindingInvocationInfo binding: + { + AddBinding(binding, classes, ref kinds, ref invokers); + break; + } + + case BindCommandInvocationInfo command: + { + AddCommand(command, classes, ref kinds); + AddName(command.ViewThreadInvoker, ref invokers); + break; + } + + case BindInteractionInvocationInfo interaction: + { + AddViewModelPath( + interaction.ViewModelTypeFullName, + interaction.ViewTypeFullName, + interaction.InteractionPropertyPath, + classes, + interaction.ViewClassInfo, + ref kinds); + break; + } + + case BindToInvocationInfo binding: + { + AddName(binding.TargetViewThreadInvoker, ref invokers); + break; + } + + case InvokeCommandInvocationInfo command: + { + AddPath( + command.CommandPropertyPath, + CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, command.TargetTypeFullName, command.CommandPropertyPath), + false, + ref kinds); + break; + } + } + } + + /// Observes the source and, for two-way bindings, the target. + /// The binding invocation. + /// The consumer's detected type declarations. + /// The selected observation helpers. + /// The selected write invokers. + private static void AddBinding( + BindingInvocationInfo invocation, + ImmutableArray classes, + ref SortedSet? kinds, + ref SortedSet? invokers) + { + var sourceInfo = CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, invocation.SourceTypeFullName, invocation.SourcePropertyPath); + var targetInfo = CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, invocation.TargetTypeFullName, invocation.TargetPropertyPath); + if (invocation.MethodName is "Bind" or "OneWayBind") + { + var observation = BindingEmitterHelpers.ResolveViewModelObservation(invocation, sourceInfo, targetInfo); + AddPath(observation.Path, observation.RootClassInfo, false, ref kinds); + } + else + { + AddPath(invocation.SourcePropertyPath, sourceInfo, false, ref kinds); + } + + if (invocation.IsTwoWay) + { + AddPath(invocation.TargetPropertyPath, targetInfo, false, ref kinds); + } + + if (invocation.HasScheduler) + { + return; + } + + AddName(invocation.TargetViewThreadInvoker, ref invokers); + if (invocation.MethodName == "BindTwoWay") + { + AddName(invocation.SourceViewThreadInvoker, ref invokers); + } + } + + /// Follows command and optional parameter replacement through the view model. + /// The command binding invocation. + /// The consumer's detected type declarations. + /// The selected observation helpers. + private static void AddCommand( + BindCommandInvocationInfo invocation, + ImmutableArray classes, + ref SortedSet? kinds) + { + var viewInfo = CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, invocation.ViewTypeFullName, invocation.ControlPropertyPath); + AddPath(invocation.ControlPropertyPath, viewInfo, false, ref kinds); + AddViewModelPath(invocation.ViewModelTypeFullName, invocation.ViewTypeFullName, invocation.CommandPropertyPath, classes, viewInfo, ref kinds); + if (invocation.HasExpressionParameter && invocation.ParameterPropertyPath is { } parameterPath) + { + AddViewModelPath(invocation.ViewModelTypeFullName, invocation.ViewTypeFullName, parameterPath, classes, viewInfo, ref kinds); + } + } + + /// Includes the view's ViewModel property when the binding follows replacements. + /// The view model's concrete type. + /// The view's concrete type. + /// The extracted path rooted at the view model. + /// The consumer's detected type declarations. + /// The view's notification mechanisms. + /// The selected observation helpers. + private static void AddViewModelPath( + string viewModelType, + string viewType, + EquatableArray path, + ImmutableArray classes, + ClassBindingInfo? viewInfo, + ref SortedSet? kinds) + { + var viewModelInfo = CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, viewModelType, path); + var observation = BindingEmitterHelpers.ResolveViewModelObservation(viewModelType, viewType, path, viewModelInfo, viewInfo); + AddPath(observation.Path, observation.RootClassInfo, false, ref kinds); + } + + /// Includes every path of a multi-property observation. + /// The observation's concrete source type. + /// The paths extracted from the observation lambdas. + /// The consumer's detected type declarations. + /// Whether each leaf is observed before changing. + /// The selected observation helpers. + private static void AddPaths( + string sourceType, + EquatableArray> paths, + ImmutableArray classes, + bool isBeforeChange, + ref SortedSet? kinds) + { + for (var i = 0; i < paths.Length; i++) + { + AddPath(paths[i], CodeGeneratorHelpers.ResolveObservedTypeInfo(classes, sourceType, paths[i]), isBeforeChange, ref kinds); + } + } + + /// Uses the root and per-link voting rules shared with observation emission. + /// The observed path. + /// The root's notification mechanisms. + /// Whether the leaf is observed before changing. + /// The selected observation helpers. + private static void AddPath( + EquatableArray path, + ClassBindingInfo? rootInfo, + bool isBeforeChange, + ref SortedSet? kinds) + { + for (var i = 0; i < path.Length; i++) + { + var segment = path[i]; + var beforeChange = isBeforeChange && i == path.Length - 1; + var classInfo = i == 0 ? rootInfo : segment.DeclaringTypeInfo; + var plugin = ObservationCodeGenerator.ResolveRootPlugin(classInfo, segment, beforeChange); + if (plugin?.RequiresHelperClasses == true) + { + AddName(plugin.ObservationKind, ref kinds); + } + } + } + + /// Allocates a set only when an invocation selects a helper. + /// A selected helper name, or null. + /// The distinct names collected so far. + private static void AddName(string? name, ref SortedSet? names) + { + if (name is null) + { + return; + } + + names ??= new(StringComparer.Ordinal); + _ = names.Add(name); + } + + /// Preserves existing arrays when an API contributes no helpers. + /// The accumulated names. + /// The next API's names. + /// Distinct names in stable order. + private static EquatableArray Merge(EquatableArray left, EquatableArray right) + { + if (left.Length == 0) + { + return right; + } + + if (right.Length == 0 || left.Equals(right)) + { + return left; + } + + var names = new SortedSet(StringComparer.Ordinal); + for (var i = 0; i < left.Length; i++) + { + _ = names.Add(left[i]); + } + + for (var i = 0; i < right.Length; i++) + { + _ = names.Add(right[i]); + } + + return ToArray(names); + } + + /// Freezes a selected set for incremental equality checks. + /// The distinct names, or null when no helper was selected. + /// The ordered names, or an empty value. + private static EquatableArray ToArray(SortedSet? names) + { + if (names is null) + { + return default; + } + + var result = new string[names.Count]; + names.CopyTo(result); + return new(result); + } + + /// Separates observation helpers from helpers used to deliver writes. + /// The selected mechanisms requiring helper declarations. + /// The selected invoker class names. + internal readonly record struct Selection(EquatableArray ObservationKinds, EquatableArray ViewThreadInvokers); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Generators/ObservationHelperGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Generators/ObservationHelperGenerator.cs index ce305cc8..1b2284db 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Generators/ObservationHelperGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Generators/ObservationHelperGenerator.cs @@ -16,21 +16,7 @@ namespace ReactiveUI.Binding.SourceGenerators.Generators; /// generated observation code instantiates by name, such as the Apple KVO and WinUI dependency-property /// observables. /// -/// -/// -/// The helpers are declared once for the whole compilation, in a file of their own, because every dispatch -/// file is another part of the same __ReactiveUIGeneratedBindings class: one part declares them and -/// all the others reach them. Letting each file declare the helpers it happens to use would collide as soon -/// as two files used the same one, and letting one dispatch file own them - which is what used to happen - -/// left every other file referencing types that were never declared. -/// -/// -/// Which helpers to declare is decided from the detected types rather than from the call sites, so the -/// declarations are a superset of the references: observation code can only name a helper for a type this -/// pipeline detected, whichever API the call site used. A future binding API therefore cannot reintroduce -/// the undeclared-helper failure by forgetting to register itself here. -/// -/// +/// Helpers are shared across dispatch files and selected from the mechanisms used by each property path. internal static class ObservationHelperGenerator { /// The generated file the helper classes are declared in. @@ -39,11 +25,8 @@ internal static class ObservationHelperGenerator /// Buffer capacity to reserve per helper-requiring observation kind. private const int PerKindBufferCapacity = 4_096; - /// - /// Reduces the per-type observation kinds to the distinct, ordered set of kinds that need helper - /// declarations, so adding another type of an already-seen kind leaves the generated file untouched. - /// - /// The observation kind of every detected type, with repeats. + /// Reduces observation kinds to the distinct, ordered set that needs helper declarations. + /// The selected observation kinds, with repeats. /// The kinds requiring helper declarations, ordered for deterministic output. internal static EquatableArray SelectHelperKinds(ImmutableArray observationKinds) { diff --git a/src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs deleted file mode 100644 index acdc342e..00000000 --- a/src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. -// ReactiveUI and Contributors licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; -using ReactiveUI.Binding.SourceGenerators.Models; - -namespace ReactiveUI.Binding.SourceGenerators.Generators; - -/// -/// Generates the consolidated registration output for all detected observable types. -/// Produces a registration class listing all per-kind binder implementations. -/// -internal static class RegistrationGenerator -{ - /// Generates the consolidated registration output: all per-kind binder classes. - /// The source production context. - /// All detected observable type infos across all notification kinds. - /// The consumer compilation's language-feature and generation-option snapshot. - internal static void Generate(in SourceProductionContext context, ImmutableArray allTypes, in LanguageFeatures features) - { - if (!Helpers.ExtractorValidation.HasItems(allTypes)) - { - return; - } - - // Collect unique observation kinds that were detected - var uniqueKinds = new HashSet(); - for (var i = 0; i < allTypes.Length; i++) - { - _ = uniqueKinds.Add(allTypes[i].ObservationKind); - } - - var sb = CodeGeneration.PooledBuilder.Rent( - CodeGeneration.CodeGeneratorHelpers.PerInvocationBufferCapacity - + (allTypes.Length * CodeGeneration.CodeGeneratorHelpers.FragmentBufferCapacity)); - CodeGeneration.CodeGeneratorHelpers.AppendGeneratedFileMarkers(sb, features.EmitGeneratedCodeMarkers); - if (features.SupportsNullable) - { - _ = sb.AppendLine("#nullable enable"); - } - - _ = sb.AppendLine(""" - - namespace ReactiveUI.Binding.Generated - { - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - """); - - foreach (var kind in uniqueKinds) - { - _ = sb.Append(" // Detected types for kind: ").Append(kind).AppendLine(); - } - - _ = sb.AppendLine(""" - } - } - } - """); - - CodeGeneration.CodeGeneratorHelpers.AddGeneratedSource( - context, - "GeneratedBinderRegistration.g.cs", - CodeGeneration.PooledBuilder.ToStringAndReturn(sb), - features); - } -} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs index adc55163..af16ff6e 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs @@ -47,7 +47,6 @@ internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValueProvider languageFeatures) { - // Reuse Pipeline A's class-with-base-list predicate var viewRegistrations = context.SyntaxProvider .CreateSyntaxProvider( RoslynHelpers.IsClassWithBaseList, diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindToExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindToExtractor.cs index c77db97c..86e7338a 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindToExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindToExtractor.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; +using ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; using ReactiveUI.Binding.SourceGenerators.Plugins.ViewThread; namespace ReactiveUI.Binding.SourceGenerators.Helpers; @@ -74,14 +76,12 @@ internal static class BindToExtractor DetectConversionParameters(methodSymbol, out var hasConversionHint, out var hasConverterOverride); - var filePath = invocation.SyntaxTree.FilePath; - var lineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; - var targetExpressionText = - CodeGeneration.CodeGeneratorHelpers.NormalizeLambdaText(targetPropertyArg.ToString()); + var targetValueType = ConversionPluginRegistry.SelectorType(targetPropertyArg, semanticModel, ct); + var targetExpressionText = CodeGeneration.CodeGeneratorHelpers.NormalizeLambdaText(targetPropertyArg.ToString()); return new( - filePath, - lineNumber, + invocation.SyntaxTree.FilePath, + invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1, sourceValueTypeFullName, targetTypeName, new(targetPropertyPath), @@ -91,7 +91,11 @@ internal static class BindToExtractor hasConverterOverride, targetExpressionText, InterceptableLocationReader.Read(semanticModel, invocation, ct), - ViewThreadPluginRegistry.InvokerFor(targetType, semanticModel.Compilation)); + ViewThreadPluginRegistry.InvokerFor(targetType, semanticModel.Compilation)) + { + Conversion = ConversionPluginRegistry.Select(sourceValueType, targetValueType, semanticModel.Compilation), + SetMethod = SetMethodPluginRegistry.Select(sourceValueType, targetValueType), + }; } /// diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindingExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindingExtractor.cs index 9f29f9b2..4f7d98ee 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindingExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/BindingExtractor.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; +using ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; using ReactiveUI.Binding.SourceGenerators.Plugins.ViewThread; namespace ReactiveUI.Binding.SourceGenerators.Helpers; @@ -65,15 +67,14 @@ internal static class BindingExtractor return null; } - DetectBindingParameters( - methodSymbol, - out var hasConversion, - out var hasScheduler, - out var hasConverterOverride); + DetectBindingParameters(methodSymbol, out var hasConversion, out var hasScheduler, out var hasConverterOverride); + + var sourceValueType = ConversionPluginRegistry.SelectorType(sourcePropertyArg, semanticModel, ct); + var targetValueType = ConversionPluginRegistry.SelectorType(targetPropertyArg, semanticModel, ct); return new( invocation.SyntaxTree.FilePath, - invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1, + invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1, sides.SourceTypeFullName, new(sourcePropertyPath), sides.TargetTypeFullName, @@ -89,7 +90,12 @@ internal static class BindingExtractor hasConverterOverride, InterceptableLocationReader.Read(semanticModel, invocation, ct), sides.SourceViewThreadInvoker, - sides.TargetViewThreadInvoker); + sides.TargetViewThreadInvoker) + { + ForwardConversion = ConversionPluginRegistry.Select(sourceValueType, targetValueType, semanticModel.Compilation), + ReverseConversion = isTwoWay ? ConversionPluginRegistry.Select(targetValueType, sourceValueType, semanticModel.Compilation) : null, + SetMethod = !isTwoWay && !hasConversion ? SetMethodPluginRegistry.Select(sourceValueType, targetValueType) : null, + }; } /// diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/CommandExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/CommandExtractor.cs index 429a60e6..bf0cd75a 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/CommandExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/CommandExtractor.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins; +using ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; using ReactiveUI.Binding.SourceGenerators.Plugins.ViewThread; namespace ReactiveUI.Binding.SourceGenerators.Helpers; @@ -69,12 +71,11 @@ internal static class CommandExtractor // Determine parameter overload (Expression vs IObservable withParameter) var parameterOverload = DetectParameterOverload(methodSymbol, args, semanticModel, ct); - var (resolvedEventName, resolvedEventArgsTypeFullName, capabilities) = - ResolveControlBinding(methodSymbol, args, controlPropertyArg, semanticModel, ct); + var controlBinding = ResolveControlBinding(methodSymbol, args, controlPropertyArg, semanticModel, ct); return new( invocation.SyntaxTree.FilePath, - invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1, + invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1, sides.ViewTypeFullName, sides.ViewModelTypeFullName, new(commandPropertyPath), @@ -86,17 +87,18 @@ internal static class CommandExtractor parameterOverload.ParameterTypeFullName, parameterOverload.ParameterIsReferenceType, parameterOverload.ParameterPropertyPath, - resolvedEventName, - resolvedEventArgsTypeFullName, + controlBinding.EventName, + controlBinding.EventArgsTypeFullName, Constants.BindCommandMethodName, CodeGeneration.CodeGeneratorHelpers.NormalizeLambdaText(commandPropertyArg.ToString()), CodeGeneration.CodeGeneratorHelpers.NormalizeLambdaText(controlPropertyArg.ToString()), parameterOverload.ParameterExpressionText, - capabilities.HasCommand, - capabilities.HasCommandParameter, - capabilities.HasEnabled, + controlBinding.Capabilities.HasCommand, + controlBinding.Capabilities.HasCommandParameter, + controlBinding.Capabilities.HasEnabled, InterceptableLocationReader.Read(semanticModel, invocation, ct), - sides.ViewThreadInvoker); + sides.ViewThreadInvoker) + { HasExplicitEvent = controlBinding.HasExplicitEvent, NativeCommand = controlBinding.NativeCommand }; } /// Searches invocation arguments for a valid withParameter lambda expression. @@ -127,87 +129,6 @@ internal static class CommandExtractor return null; } - /// - /// Checks if a control type has a settable Command property (ICommand) - /// and optionally a settable CommandParameter property. - /// Walks the type hierarchy. - /// - /// The control type symbol to inspect. - /// - /// Set to when the type also has a settable CommandParameter property. - /// - /// - /// if the type or one of its base types has a settable Command property. - /// - internal static bool HasCommandProperties(INamedTypeSymbol controlType, out bool hasCommandParameter) - { - hasCommandParameter = false; - var hasCommand = false; - - var current = (ITypeSymbol?)controlType; - while (current is INamedTypeSymbol namedCurrent) - { - var members = namedCurrent.GetMembers(); - for (var i = 0; i < members.Length; i++) - { - if (members[i] is not IPropertySymbol property) - { - continue; - } - - if (IsSettableICommandProperty(property)) - { - hasCommand = true; - } - - if (IsSettableCommandParameterProperty(property)) - { - hasCommandParameter = true; - } - } - - if (hasCommand && hasCommandParameter) - { - return true; - } - - current = namedCurrent.BaseType; - } - - return hasCommand; - } - - /// Checks if a control type has a settable Enabled property (bool). Walks the type hierarchy. - /// The control type symbol to inspect. - /// - /// if the type or one of its base types has a public settable - /// bool Enabled property. - /// - internal static bool HasEnabledProperty(INamedTypeSymbol controlType) - { - var current = (ITypeSymbol?)controlType; - while (current is INamedTypeSymbol namedCurrent) - { - var members = namedCurrent.GetMembers(); - for (var i = 0; i < members.Length; i++) - { - if (members[i] is IPropertySymbol property - && property.Name == "Enabled" - && !property.IsReadOnly - && !property.IsStatic - && property.DeclaredAccessibility == Accessibility.Public - && property.Type.SpecialType == SpecialType.System_Boolean) - { - return true; - } - } - - current = namedCurrent.BaseType; - } - - return false; - } - /// /// Determines whether an argument matches the "toEvent" parameter by either /// named argument syntax (toEvent: "Click") or positional match. @@ -310,36 +231,19 @@ internal static ControlBinding ResolveControlBinding( CancellationToken ct) { var resolvedEventName = ResolveExplicitEventName(methodSymbol, args, semanticModel, ct); + var explicitEvent = !string.IsNullOrEmpty(resolvedEventName); var controlLeafType = SymbolHelpers.ResolveNamedType(semanticModel, controlPropertyArg, ct); var resolvedEventArgsTypeFullName = ResolveEventArgsTypeFullName(controlLeafType, ref resolvedEventName); - return new(resolvedEventName, resolvedEventArgsTypeFullName, DetectControlCapabilities(controlLeafType)); - } - - /// Determines whether a property is a settable public instance Command property typed as ICommand. - /// The property to inspect. - /// if the property is a settable ICommand-typed Command property. - internal static bool IsSettableICommandProperty(IPropertySymbol property) - { - if (property.Name != "Command" || property.IsReadOnly || property.IsStatic - || property.DeclaredAccessibility != Accessibility.Public) + return new(resolvedEventName, resolvedEventArgsTypeFullName, DetectControlCapabilities(controlLeafType)) { - return false; - } - - var typeName = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return typeName.EndsWith("ICommand", StringComparison.Ordinal); + HasExplicitEvent = explicitEvent, + NativeCommand = explicitEvent ? null : CommandBindingPluginRegistry.InspectControl(controlLeafType), + }; } - /// Determines whether a property is a settable public instance CommandParameter property. - /// The property to inspect. - /// if the property is a settable CommandParameter property. - internal static bool IsSettableCommandParameterProperty(IPropertySymbol property) => - property.Name == "CommandParameter" && !property.IsReadOnly && !property.IsStatic - && property.DeclaredAccessibility == Accessibility.Public; - /// /// Inspects the method's withParameter parameter (if any) to determine whether the /// overload takes an Expression or IObservable parameter, and extracts the @@ -434,8 +338,8 @@ internal static ControlCapabilities DetectControlCapabilities(INamedTypeSymbol? return default; } - var hasCommandProperty = HasCommandProperties(controlLeafType, out var hasCommandParameterProperty); - var hasEnabledProperty = HasEnabledProperty(controlLeafType); + var hasCommandProperty = CommandPropertyBindingPlugin.HasCommandProperties(controlLeafType, out var hasCommandParameterProperty); + var hasEnabledProperty = EventEnabledBindingPlugin.HasEnabledProperty(controlLeafType); return new(hasCommandProperty, hasCommandParameterProperty, hasEnabledProperty); } @@ -455,7 +359,14 @@ internal readonly record struct ControlCapabilities( internal readonly record struct ControlBinding( string? EventName, string? EventArgsTypeFullName, - ControlCapabilities Capabilities); + ControlCapabilities Capabilities) + { + /// Gets a value indicating whether the caller selected an event. + public bool HasExplicitEvent { get; init; } + + /// Gets the verified native command members. + public NativeCommandInfo? NativeCommand { get; init; } + } /// Holds the detected withParameter overload information for a BindCommand invocation. internal sealed class ParameterOverloadInfo diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/ExtractorValidation.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/ExtractorValidation.cs index 4b457e69..adf66ce6 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/ExtractorValidation.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/ExtractorValidation.cs @@ -56,10 +56,7 @@ containingType is not null internal static bool HasMinimumArguments(int argumentCount, int minimumRequired) => argumentCount >= minimumRequired; - /// - /// Checks whether an immutable array of items is non-empty and should be processed. - /// Used by RegistrationGenerator to guard against empty type detection results. - /// + /// Checks whether an immutable array of items is non-empty and should be processed. /// The type of items in the array. /// The immutable array to check. /// if the array has items; otherwise . diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/InteractionExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/InteractionExtractor.cs index 9a9d6822..591e97b3 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/InteractionExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/InteractionExtractor.cs @@ -72,7 +72,7 @@ internal static class InteractionExtractor "view model type display name"); var filePath = invocation.SyntaxTree.FilePath; - var lineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var lineNumber = invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1; var expressionText = CodeGeneration.CodeGeneratorHelpers.NormalizeLambdaText(propertyNameArg.ToString()); return new( diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/InvokeCommandExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/InvokeCommandExtractor.cs index 85d62a13..24114919 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/InvokeCommandExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/InvokeCommandExtractor.cs @@ -69,7 +69,7 @@ internal static class InvokeCommandExtractor ? null : new( invocation.SyntaxTree.FilePath, - invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1, + invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1, sourceValueType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), targetTypeName, new(commandPropertyPath), diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeCommandMembers.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeCommandMembers.cs new file mode 100644 index 00000000..5ea54982 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeCommandMembers.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace ReactiveUI.Binding.SourceGenerators.Helpers; + +/// Shares native command member validation between generation and diagnostics. +internal static class NativeCommandMembers +{ + /// The parameter count of a native event delegate or UIKit target overload. + private const int NativeParameterCount = 2; + + /// Checks a framework hierarchy by its declared CLR name. + /// The concrete type. + /// The framework base type. + /// True when the type belongs to the hierarchy. + internal static bool DerivesFrom(INamedTypeSymbol type, string name) + { + for (var current = type; current is not null; current = current.BaseType) + { + if (NativeTypeIdentity.Matches(current, name)) + { + return true; + } + } + + return false; + } + + /// Finds a member through the base chain while respecting member hiding. + /// The concrete type. + /// The member name. + /// The nearest declaration. + internal static ISymbol? FindMember(INamedTypeSymbol type, string name) + { + for (var current = type; current is not null; current = current.BaseType) + { + var members = current.GetMembers(name); + if (!members.IsEmpty) + { + return members[0]; + } + } + + return null; + } + + /// Checks for a public instance event with the native two-argument void delegate. + /// The concrete owner. + /// The event name. + /// True when generated code can attach its handler. + internal static bool HasEvent(INamedTypeSymbol type, string name) => + FindMember(type, name) is IEventSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, Type: INamedTypeSymbol delegateType } + && delegateType.DelegateInvokeMethod is { ReturnsVoid: true, Parameters.Length: NativeParameterCount } invoke + && invoke.Parameters[0].RefKind == RefKind.None && invoke.Parameters[1].RefKind == RefKind.None; + + /// Checks the public setter and exact type of a native property. + /// The concrete owner. + /// The property name. + /// The native property type. + /// True when the generated assignment is legal. + internal static bool HasWritableProperty(INamedTypeSymbol type, string name, string propertyType) => + FindMember(type, name) is IPropertySymbol { IsStatic: false, SetMethod.DeclaredAccessibility: Accessibility.Public } property + && (propertyType == "bool" ? property.Type.SpecialType == SpecialType.System_Boolean : NativeTypeIdentity.Matches(property.Type, propertyType)); + + /// Checks UIKit's delegate-based target method overload. + /// The concrete control. + /// The native method name. + /// True when the native overload is accessible. + internal static bool HasTargetMethod(INamedTypeSymbol type, string name) + { + for (var current = type; current is not null; current = current.BaseType) + { + foreach (var member in current.GetMembers(name)) + { + if (member is IMethodSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, Parameters.Length: NativeParameterCount } method + && NativeTypeIdentity.Matches(method.Parameters[0].Type, "System.EventHandler") + && NativeTypeIdentity.Matches(method.Parameters[1].Type, "UIKit.UIControlEvent")) + { + return true; + } + } + } + + return false; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeTypeIdentity.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeTypeIdentity.cs new file mode 100644 index 00000000..dd87a4b0 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/NativeTypeIdentity.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace ReactiveUI.Binding.SourceGenerators.Helpers; + +/// Matches native type names without allocating a formatted symbol display. +internal static class NativeTypeIdentity +{ + /// Compares namespace and containing-type segments ordinally. + /// The candidate type. + /// The native type name using dots for nested types. + /// True when the complete type identity matches. + internal static bool Matches(ITypeSymbol? type, string qualifiedName) + { + ISymbol? current = type; + var end = qualifiedName.Length; + while (current is not null && end > 0) + { + var start = qualifiedName.LastIndexOf('.', end - 1) + 1; + var name = current.MetadataName; + if (name.Length != end - start || string.CompareOrdinal(name, 0, qualifiedName, start, name.Length) != 0) + { + return false; + } + + current = current.ContainingSymbol; + end = start - 1; + } + + return end < 0 && current is INamespaceSymbol { IsGlobalNamespace: true }; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/ObservationExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/ObservationExtractor.cs index 04a40313..cc428b29 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/ObservationExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/ObservationExtractor.cs @@ -112,7 +112,7 @@ internal static bool IsSelectorParameterName(string parameterName) => var returnTypeFullName = ComputeReturnTypeFullName(methodSymbol, propertyPaths, hasSelector); var filePath = invocation.SyntaxTree.FilePath; - var lineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var lineNumber = invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1; return new( filePath, diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/SyntaxHelpers.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/SyntaxHelpers.cs index 75cb7caa..9a98136a 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/SyntaxHelpers.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/SyntaxHelpers.cs @@ -62,13 +62,15 @@ internal static class SyntaxHelpers return null; } + var owner = semanticModel.GetTypeInfo(memberAccess.Expression, ct).Type as INamedTypeSymbol ?? propertySymbol.ContainingType; segments.Add(new( propertySymbol.Name, propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - propertySymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + owner.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), propertySymbol.Type.IsReferenceType, - TypeDetectionExtractor.ExtractFromSymbol( - propertySymbol.ContainingType, + TypeDetectionExtractor.ExtractPropertyOwner( + owner, + propertySymbol, semanticModel.Compilation, ct))); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/TypeDetectionExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/TypeDetectionExtractor.cs index 6bf75d23..e81070ee 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/TypeDetectionExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/TypeDetectionExtractor.cs @@ -2,42 +2,79 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; namespace ReactiveUI.Binding.SourceGenerators.Helpers; -/// Pipeline A transform: extracts ClassBindingInfo from class declarations. +/// Extracts notification capabilities from property-owner symbols. internal static class TypeDetectionExtractor { - /// - /// Pipeline A transform: extracts ClassBindingInfo from a class declaration with a base list. - /// Sets boolean flags by walking AllInterfaces + base type chain. - /// - /// The generator syntax context containing the semantic model. - /// Cancellation token. - /// A ClassBindingInfo POCO, or null if the node is not relevant. - /// If the cancellation token is triggered. - internal static ClassBindingInfo? ExtractClassBindingInfo(GeneratorSyntaxContext context, CancellationToken ct) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - - var semanticModel = context.SemanticModel; - var typeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(classDecl, ct)!; - - return ExtractFromSymbol(typeSymbol, semanticModel.Compilation, ct); - } - /// Reads a type's notification mechanisms and observable properties from its symbol. /// The type to inspect, declared in this compilation or referenced from another. /// The compilation the type is resolved against. /// Cancellation token. /// A ClassBindingInfo POCO for the type. /// If the cancellation token is triggered. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ClassBindingInfo ExtractFromSymbol( INamedTypeSymbol typeSymbol, Compilation compilation, + CancellationToken ct) => + CreateTypeInfo(typeSymbol, compilation, ExtractProperties(typeSymbol, ct), ct); + + /// Captures the concrete owner's notification interfaces and one inherited or declared property. + /// The type through which the property is read. + /// The bound property symbol. + /// The consumer compilation. + /// The cancellation token. + /// The owner's capabilities with property-specific native candidates. + internal static ClassBindingInfo ExtractPropertyOwner( + INamedTypeSymbol owner, + IPropertySymbol property, + Compilation compilation, + CancellationToken ct) + { + var propertyInfo = ExtractProperty(owner, property); + var properties = property.Name != "ViewModel" + && PlatformSymbols.FindMember(owner, "ViewModel") is IPropertySymbol { IsStatic: false, GetMethod.DeclaredAccessibility: Accessibility.Public } viewModel + ? new EquatableArray([propertyInfo, ExtractProperty(owner, viewModel)]) + : new EquatableArray([propertyInfo]); + return CreateTypeInfo(owner, compilation, properties, ct); + } + + /// Reads one property's native candidates while its owner symbols are available. + /// The concrete property owner. + /// The selected declaration. + /// Property-specific observation metadata. + internal static ObservablePropertyInfo ExtractProperty(INamedTypeSymbol owner, IPropertySymbol property) + { + var companion = PlatformSymbols.FindMember(owner, $"{property.Name}Property"); + return new( + property.Name, + property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + property.GetMethod?.DeclaredAccessibility == Accessibility.Public, + property.IsIndexer, + companion is IFieldSymbol { IsStatic: true } or IPropertySymbol { IsStatic: true }, + PlatformSymbols.FindEvent(owner, $"{property.Name}Changed") is not null, + SymbolEqualityComparer.Default.Equals(owner, property.ContainingType), + ObservationPluginRegistry.InspectProperty(owner, property), + true); + } + + /// Combines owner capabilities with the property metadata needed by the caller. + /// The concrete owner type. + /// The consumer compilation. + /// The inspected property metadata. + /// The cancellation token. + /// Value-equatable owner information. + internal static ClassBindingInfo CreateTypeInfo( + INamedTypeSymbol typeSymbol, + Compilation compilation, + EquatableArray properties, CancellationToken ct) { var wellKnown = SymbolHelpers.GetWellKnownSymbols(compilation); @@ -54,9 +91,6 @@ internal static ClassBindingInfo ExtractFromSymbol( // Walk base type chain for platform detection var platform = DetectPlatformBaseTypes(typeSymbol, wellKnown, ct); - // Extract properties - var properties = ExtractProperties(typeSymbol, ct); - return new( typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), typeSymbol.MetadataName, @@ -108,7 +142,9 @@ internal static EquatableArray ExtractProperties( property.IsIndexer, isDependencyProperty, hasChangeEvent, - SymbolEqualityComparer.Default.Equals(property.ContainingType, typeSymbol))); + SymbolEqualityComparer.Default.Equals(property.ContainingType, typeSymbol), + ObservationPluginRegistry.InspectProperty(typeSymbol, property), + true)); } return new([.. properties]); @@ -247,7 +283,7 @@ private static PlatformBaseTypeFlags DetectPlatformBaseTypes( var winforms = false; var android = false; - var baseType = typeSymbol.BaseType; + var baseType = typeSymbol; while (baseType is not null) { ct.ThrowIfCancellationRequested(); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Helpers/WhenAnyObservableExtractor.cs b/src/ReactiveUI.Binding.SourceGenerators/Helpers/WhenAnyObservableExtractor.cs index c997db34..5e93fea8 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Helpers/WhenAnyObservableExtractor.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Helpers/WhenAnyObservableExtractor.cs @@ -66,7 +66,7 @@ internal static class WhenAnyObservableExtractor "inner observable types"); var filePath = invocation.SyntaxTree.FilePath; - var lineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var lineNumber = invocation.SyntaxTree.GetLineSpan(invocation.Span, ct).StartLinePosition.Line + 1; return new( filePath, diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindCommandInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindCommandInvocationGenerator.cs index 7ffa3ef4..495fdbbf 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindCommandInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindCommandInvocationGenerator.cs @@ -2,9 +2,9 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Generators; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Invocations; @@ -15,19 +15,20 @@ internal static class BindCommandInvocationGenerator /// Registers the BindCommand invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, - IncrementalValueProvider languageFeatures) => + IncrementalValueProvider languageFeatures) + { InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "BindCommandDispatch.g.cs", static (invocations, classes, features) => BindCommandCodeGenerator.Generate(invocations, classes, features)); + context.RegisterSourceOutput( + invocations.Collect().Combine(languageFeatures), + static (ctx, data) => CommandBindingHelperGenerator.Generate(ctx, data.Left, data.Right)); + } } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInteractionInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInteractionInvocationGenerator.cs index 5cf66a84..e7b32626 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInteractionInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInteractionInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class BindInteractionInvocationGenerator /// Registers the BindInteraction invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "BindInteractionDispatch.g.cs", static (invocations, classes, features) => BindInteractionCodeGenerator.Generate(invocations, classes, features)); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInvocationGenerator.cs index 89f922a9..f82fc4ef 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class BindInvocationGenerator /// Registers the Bind invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "BindDispatch.g.cs", static (invocations, classes, features) => BindingEmitterHelpers.Generate( diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindOneWayInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindOneWayInvocationGenerator.cs index e26703fb..6e3a12b3 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindOneWayInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindOneWayInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class BindOneWayInvocationGenerator /// Registers the BindOneWay invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "BindOneWayDispatch.g.cs", static (invocations, classes, features) => BindingEmitterHelpers.Generate( diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindTwoWayInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindTwoWayInvocationGenerator.cs index bb277252..0f028231 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindTwoWayInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/BindTwoWayInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class BindTwoWayInvocationGenerator /// Registers the BindTwoWay invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "BindTwoWayDispatch.g.cs", static (invocations, classes, features) => BindingEmitterHelpers.Generate( diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvocationPipeline.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvocationPipeline.cs index 4ec3771e..c745e0b8 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvocationPipeline.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvocationPipeline.cs @@ -11,37 +11,33 @@ namespace ReactiveUI.Binding.SourceGenerators.Invocations; /// Wires one API's detected invocations to the emitter that writes its dispatch file. /// -/// Every API is registered the same way: collect its invocations, pair them with the types the compilation -/// declares and the consumer's language features, and write one file when the emitter produces anything. Only +/// Every API collects its invocations, pairs them with the consumer's language features, +/// and writes one file when the emitter produces anything. Only /// the emitter and the file name differ, so they are the arguments and the pipeline is written once. /// internal static class InvocationPipeline { - /// Registers a pipeline whose emitter needs the types the compilation declares. + /// Registers an emitter that reads observation metadata from its property paths. /// The per-call-site model this API extracts. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. /// The name of the file the emitter's output is added as. /// Produces the file's text, or null when this API claimed no call site. internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures, string hintName, Func, ImmutableArray, LanguageFeatures, string?> emit) { - var combined = invocations.Collect() - .Combine(allClasses.Collect()) - .Combine(languageFeatures); + var combined = invocations.Collect().Combine(languageFeatures); context.RegisterSourceOutput( combined, (ctx, data) => { - var source = emit(data.Left.Left, data.Left.Right, data.Right); + var source = emit(data.Left, ImmutableArray.Empty, data.Right); if (source is null) { return; diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvokeCommandInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvokeCommandInvocationGenerator.cs index b6a0e4d3..c478a895 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvokeCommandInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/InvokeCommandInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class InvokeCommandInvocationGenerator /// Registers the InvokeCommand invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "InvokeCommandDispatch.g.cs", static (invocations, classes, features) => InvokeCommandCodeGenerator.Generate(invocations, classes, features)); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/OneWayBindInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/OneWayBindInvocationGenerator.cs index 9dbcd952..f828fb04 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/OneWayBindInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/OneWayBindInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class OneWayBindInvocationGenerator /// Registers the OneWayBind invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "OneWayBindDispatch.g.cs", static (invocations, classes, features) => BindingEmitterHelpers.Generate( diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyInvocationGenerator.cs index a4cf9fae..bd4cac54 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyInvocationGenerator.cs @@ -19,18 +19,15 @@ internal static class WhenAnyInvocationGenerator /// Registers the WhenAny invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "WhenAnyDispatch.g.cs", static (invocations, classes, features) => WhenAnyCodeGenerator.Generate(invocations, classes, features)); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyObservableInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyObservableInvocationGenerator.cs index 2f1c04a6..ecfaf179 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyObservableInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyObservableInvocationGenerator.cs @@ -19,18 +19,15 @@ internal static class WhenAnyObservableInvocationGenerator /// Registers the WhenAnyObservable invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "WhenAnyObservableDispatch.g.cs", static (invocations, classes, features) => WhenAnyObservableCodeGenerator.Generate(invocations, classes, features)); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyValueInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyValueInvocationGenerator.cs index 2646ef11..7a4af75c 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyValueInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenAnyValueInvocationGenerator.cs @@ -18,18 +18,15 @@ internal static class WhenAnyValueInvocationGenerator /// Registers the WhenAnyValue/WhenAny invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "WhenAnyValueDispatch.g.cs", static (invocations, classes, features) => ObservationCodeGenerator.Generate(invocations, classes, features, "WhenAnyValue")); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangedInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangedInvocationGenerator.cs index 04bf3997..9c3646cf 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangedInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangedInvocationGenerator.cs @@ -15,18 +15,15 @@ internal static class WhenChangedInvocationGenerator /// Registers the WhenChanged invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "WhenChangedDispatch.g.cs", static (invocations, classes, features) => ObservationCodeGenerator.Generate(invocations, classes, features, "WhenChanged")); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangingInvocationGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangingInvocationGenerator.cs index e18ceec9..d9617eaf 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangingInvocationGenerator.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Invocations/WhenChangingInvocationGenerator.cs @@ -18,18 +18,15 @@ internal static class WhenChangingInvocationGenerator /// Registers the WhenChanging invocation detection pipeline. /// The generator initialization context. /// The detected invocations of this API. - /// The shared type detection pipeline. /// The consumer compilation's C# language-feature snapshot. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Register( in IncrementalGeneratorInitializationContext context, IncrementalValuesProvider invocations, - IncrementalValuesProvider allClasses, IncrementalValueProvider languageFeatures) => InvocationPipeline.Register( context, invocations, - allClasses, languageFeatures, "WhenChangingDispatch.g.cs", static (invocations, classes, features) => ObservationCodeGenerator.Generate(invocations, classes, features, "WhenChanging")); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/BindCommandInvocationInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/BindCommandInvocationInfo.cs index 9c3ee742..f1deea38 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/BindCommandInvocationInfo.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/BindCommandInvocationInfo.cs @@ -59,4 +59,11 @@ internal sealed record BindCommandInvocationInfo( bool HasCommandParameterProperty, bool HasEnabledProperty, InterceptorLocation Interceptor = default, - string? ViewThreadInvoker = null); + string? ViewThreadInvoker = null) +{ + /// Gets a value indicating whether the caller explicitly selected an event. + public bool HasExplicitEvent { get; init; } + + /// Gets the native command route verified during extraction. + public NativeCommandInfo? NativeCommand { get; init; } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/BindToInvocationInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/BindToInvocationInfo.cs index fdd2fe8d..9a9bb679 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/BindToInvocationInfo.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/BindToInvocationInfo.cs @@ -29,4 +29,11 @@ internal sealed record BindToInvocationInfo( bool HasConverterOverride, string TargetExpressionText, InterceptorLocation Interceptor = default, - string? TargetViewThreadInvoker = null); + string? TargetViewThreadInvoker = null) +{ + /// Gets the typed mechanism converting stream values to the target. + public ConversionInfo? Conversion { get; init; } + + /// Gets the native mutation of an existing target collection. + public SetMethodInfo? SetMethod { get; init; } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/BindingInvocationInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/BindingInvocationInfo.cs index fa6b410c..7a6ac2ab 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/BindingInvocationInfo.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/BindingInvocationInfo.cs @@ -47,4 +47,14 @@ internal sealed record BindingInvocationInfo( bool HasConverterOverride, InterceptorLocation Interceptor = default, string? SourceViewThreadInvoker = null, - string? TargetViewThreadInvoker = null); + string? TargetViewThreadInvoker = null) +{ + /// Gets the typed mechanism converting source values to the target. + public ConversionInfo? ForwardConversion { get; init; } + + /// Gets the typed mechanism converting target values to the source. + public ConversionInfo? ReverseConversion { get; init; } + + /// Gets the native mutation used by a one-way collection binding. + public SetMethodInfo? SetMethod { get; init; } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionInfo.cs new file mode 100644 index 00000000..9b545e4e --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// A symbol-validated conversion whose input is named __value. +/// The typed expression producing the converted value. +/// The condition under which the conversion succeeds. +/// The score a registered converter must exceed. +internal sealed record ConversionInfo(string Expression, string Condition, int Affinity) +{ + /// Gets typed local declarations required by a parsing operation. + public string Preparation { get; init; } = string.Empty; + + /// Gets the assignable value used when a registered converter declines the conversion. + public string? AssignmentFallback { get; init; } + + /// Gets a value indicating whether the native path can return the source observable directly. + public bool IsIdentity { get; init; } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionParameters.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionParameters.cs new file mode 100644 index 00000000..3e135252 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/ConversionParameters.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// The caller-supplied conversion inputs used by a generated stage. +/// The expression carrying the conversion hint. +/// The expression carrying an explicit converter. +internal readonly record struct ConversionParameters(string Hint, string Override); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandInfo.cs new file mode 100644 index 00000000..0b15a09b --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Stores the native members inspected for a command binding. +/// The native route. +/// The concrete event, when the route uses one. +/// The event argument type. +/// Whether the native owner exposes a writable Enabled property. +/// Whether the target/action route exposes a writable Action property. +internal sealed record NativeCommandInfo(NativeCommandKind Kind, string? EventName, string? EventArgsType, bool HasEnabled, bool HasAction); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandKind.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandKind.cs new file mode 100644 index 00000000..9e47c3a2 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/NativeCommandKind.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Identifies a verified native command route. +internal enum NativeCommandKind +{ + /// Android View.Click with enabled-state synchronization. + AndroidClick = 0, + + /// UIKit UIControl target/action touch handling. + UIKitTouch = 1, + + /// A UIKit control-specific event. + UIKitEvent = 2, + + /// Cocoa Target and Action properties. + AppKitTargetAction = 3, +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/NotificationEventInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/NotificationEventInfo.cs new file mode 100644 index 00000000..1881c99d --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/NotificationEventInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Stores a validated event and its concrete delegate type. +/// The event member name. +/// The fully qualified event delegate type. +internal sealed record NotificationEventInfo(string Name, string HandlerType); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/ObservablePropertyInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/ObservablePropertyInfo.cs index f74cef36..dbda591f 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/ObservablePropertyInfo.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/ObservablePropertyInfo.cs @@ -23,6 +23,8 @@ namespace ReactiveUI.Binding.SourceGenerators.Models; /// own frameworks declare, not what an application adds to a subclass - so an inherited property has to stay /// distinguishable from a declared one even though both are now recorded. /// +/// Eligible platform plugins and their verified notification members. +/// Whether eligibility was checked against compiler symbols. internal sealed record ObservablePropertyInfo( string PropertyName, string PropertyTypeFullName, @@ -30,4 +32,6 @@ internal sealed record ObservablePropertyInfo( bool IsIndexer, bool IsDependencyProperty, bool HasChangeEvent, - bool IsDeclaredByType = true); + bool IsDeclaredByType = true, + EquatableArray PlatformObservations = default, + bool SymbolsInspected = false); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/ObservableTypeInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/ObservableTypeInfo.cs deleted file mode 100644 index 8bbea111..00000000 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/ObservableTypeInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. -// ReactiveUI and Contributors licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace ReactiveUI.Binding.SourceGenerators.Models; - -/// -/// Per-kind filtered value-equatable POCO for the incremental generator pipeline. -/// Produced by per-kind fallback generators filtering from . -/// Contains no ISymbol, SyntaxNode, or Location references. -/// -/// The fully qualified name of the type (global:: prefixed). -/// The metadata name of the type (without namespace or global:: prefix). -/// The observation mechanism kind (e.g., "INPC", "ReactiveObject", "WpfDP"). -/// The priority affinity score for this observation kind. -/// Whether this observation kind supports before-change notifications. -/// The observable properties on this type. -internal sealed record ObservableTypeInfo( - string FullyQualifiedName, - string MetadataName, - string ObservationKind, - int Affinity, - bool SupportsBeforeChanged, - EquatableArray Properties); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/ObservationExpression.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/ObservationExpression.cs new file mode 100644 index 00000000..443e51ea --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/ObservationExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Describes one typed property observation expression. +/// The variable carrying the property owner. +/// The property and its selected mechanism data. +/// The concrete owner type used by the getter. +/// Whether to read before the property changes. +/// Whether equal consecutive values are suppressed. +internal readonly record struct ObservationExpression( + string Source, + PropertyPathSegment Segment, + string SourceType, + bool BeforeChange, + bool Distinct); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/PlatformObservationInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/PlatformObservationInfo.cs new file mode 100644 index 00000000..9de92c3f --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/PlatformObservationInfo.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Stores the native notification members verified in the consumer compilation. +/// The plugin that owns extraction and emission. +/// The score of the native notification mechanism. +/// The concrete event delegates to subscribe. +/// The fully qualified Apple notification constant. +/// The WinUI or Uno dependency-object type. +/// The exported Objective-C getter selector. +internal sealed record PlatformObservationInfo( + string Kind, + int Affinity, + EquatableArray Events, + string? NotificationName, + string? DependencyObjectType, + string? KvoKeyPath); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/PropertyPathSegment.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/PropertyPathSegment.cs index 6a99d915..cdfd0ae0 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Models/PropertyPathSegment.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/PropertyPathSegment.cs @@ -11,14 +11,14 @@ namespace ReactiveUI.Binding.SourceGenerators.Models; /// /// The name of the property for this path segment. /// The fully qualified type of the property. -/// The fully qualified type that declares this property. +/// The concrete type exposing the property, including inherited declarations. /// /// Whether the property's type is a reference type. Used to decide whether the generated /// Expression<Func<…, T>> selector parameter may be annotated nullable (T?) so it /// accepts selectors of nullable reference-typed properties; value-type leaves stay non-nullable. /// /// -/// How the declaring type notifies, or when the segment was built without a symbol to +/// How the concrete owner notifies, or when the segment was built without a symbol to /// read it from. Each link of a chain notifies on its own terms, so the mechanism travels with the segment /// rather than being inferred from the type the chain started at. /// diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodEmission.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodEmission.cs new file mode 100644 index 00000000..945e1f38 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodEmission.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// The typed values needed to emit a native collection write. +/// The target variable. +/// The target collection path. +/// The source value type. +/// The selected native mechanism. +/// The expression used for binding error reporting. +/// Whether the public binding exposes applied collections. +internal readonly record struct SetMethodEmission( + string Root, + EquatableArray Path, + string SourceType, + SetMethodInfo Mechanism, + string Expression, + bool ReportChanges); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodInfo.cs b/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodInfo.cs new file mode 100644 index 00000000..a9528706 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Models/SetMethodInfo.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.SourceGenerators.Models; + +/// Describes a typed collection mutation selected from the consumer's native symbols. +/// The generated mechanism's score. +/// The collection property exposing its layout owner. +internal sealed record SetMethodInfo(int Affinity, string LayoutOwner) +{ + /// Gets whether the source already supplies the native API's array shape. + public bool SourceIsArray { get; init; } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AndroidCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AndroidCommandBindingPlugin.cs new file mode 100644 index 00000000..becc635b --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AndroidCommandBindingPlugin.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Binds Android clicks and enabled state through concrete native members. +internal sealed class AndroidCommandBindingPlugin : IPlatformCommandBindingPlugin +{ + /// The Android View command score. + private const int ClickAffinity = 9; + + /// + public int Affinity => ClickAffinity; + + /// + public bool RequiresCustomBinderFallback => true; + + /// + public NativeCommandInfo? InspectControl(INamedTypeSymbol control) => + PlatformSymbols.DerivesFrom(control, "Android.Views.View") + ? NativeCommandSymbols.Event(control, NativeCommandKind.AndroidClick, "Click") + : null; + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => !inv.HasExplicitEvent && inv.NativeCommand?.Kind == NativeCommandKind.AndroidClick; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + NativeCommandEmitter.EmitEvent(sb, inv, controlAccess, supportsNullable); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandBindingPlugin.cs new file mode 100644 index 00000000..ebaaf9c0 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandBindingPlugin.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Binds Cocoa controls through their concrete Target, Action and optional Enabled properties. +internal sealed class AppKitCommandBindingPlugin : IPlatformCommandBindingPlugin, ICommandBindingHelperPlugin +{ + /// The native Cocoa target/action score. + private const int TargetActionAffinity = 4; + + /// + public int Affinity => TargetActionAffinity; + + /// + public bool RequiresCustomBinderFallback => true; + + /// + public NativeCommandInfo? InspectControl(INamedTypeSymbol control) => !AppKitCommandSymbols.CanBind(control) + ? null + : new( + NativeCommandKind.AppKitTargetAction, + null, + null, + NativeCommandSymbols.HasWritableProperty(control, "Enabled", "bool"), + NativeCommandSymbols.HasWritableProperty(control, nameof(Action), "ObjCRuntime.Selector")); + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => !inv.HasExplicitEvent && inv.NativeCommand?.Kind == NativeCommandKind.AppKitTargetAction; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + AppKitCommandEmitter.EmitBinding(sb, inv, controlAccess); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelper(StringBuilder sb) => AppKitCommandEmitter.EmitHelper(sb); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandEmitter.cs new file mode 100644 index 00000000..4c9fe6a6 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandEmitter.cs @@ -0,0 +1,133 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Emits Cocoa target/action routing with concrete setters and a native selector bridge. +internal static class AppKitCommandEmitter +{ + /// Opens the command's nested subscription body. + private const string SubscriptionBlockOpen = " {"; + + /// Installs and removes the native target while commands and parameters change. + /// The output builder. + /// The verified native binding. + /// The concrete target expression. + internal static void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess) + { + if (CommandParameterEmitter.HasParameter(inv)) + { + CommandParameterEmitter.EmitCapture(sb, inv); + } + + var native = inv.NativeCommand!; + var parameter = CommandParameterEmitter.Read(inv); + _ = sb.AppendLine(" var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable();") + .AppendLine(" var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd =>") + .AppendLine(" {") + .AppendLine(" serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;") + .AppendLine(" if (cmd == null)") + .AppendLine(SubscriptionBlockOpen); + if (native.HasEnabled) + { + _ = sb.Append(" ").Append(controlAccess).AppendLine(".Enabled = false;"); + } + + _ = sb.AppendLine(" return;").AppendLine(" }") + .Append(" var __target = new __AppKitCommandTarget(cmd, () => ").Append(parameter).AppendLine(");"); + if (native.HasAction) + { + _ = sb.AppendLine(" var __selector = new global::ObjCRuntime.Selector(\"theAction:\");") + .Append(" ").Append(controlAccess).AppendLine(".Action = __selector;"); + } + + _ = sb.Append(" ").Append(controlAccess).AppendLine(".Target = __target;"); + AppendEnabled(sb, native, controlAccess, parameter); + AppendDetach(sb, native, controlAccess); + NativeCommandEmitter.AppendReturn(sb, inv); + } + + /// Declares the NSObject bridge required by the native selector contract. + /// The output builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EmitHelper(StringBuilder sb) => + sb.AppendLine(""" + private sealed class __AppKitCommandTarget : global::Foundation.NSObject + { + private readonly global::System.Windows.Input.ICommand _command; + private readonly global::System.Func _parameter; + internal __AppKitCommandTarget(global::System.Windows.Input.ICommand command, global::System.Func parameter) + { + _command = command; + _parameter = parameter; + IsEnabled = command.CanExecute(null); + } + internal bool IsEnabled { get; set; } + [global::Foundation.Export("theAction:")] + public void Execute(global::Foundation.NSObject sender) + { + var parameter = _parameter(); + if (_command.CanExecute(parameter)) + { + _command.Execute(parameter); + } + } + [global::Foundation.Export("validateMenuItem:")] + public bool ValidateMenuItem(global::AppKit.NSMenuItem item) + { + return IsEnabled; + } + } + """); + + /// Synchronizes the native enabled property and menu-validation result. + /// The output builder. + /// The verified native capabilities. + /// The concrete control expression. + /// The current command argument expression. + private static void AppendEnabled(StringBuilder sb, NativeCommandInfo native, string control, string parameter) + { + if (!native.HasEnabled) + { + return; + } + + _ = sb.AppendLine(" global::System.EventHandler __enabled = (__sender, __args) =>") + .AppendLine(SubscriptionBlockOpen) + .Append(" __target.IsEnabled = cmd.CanExecute(").Append(parameter).AppendLine(");") + .Append(" ").Append(control).AppendLine(".Enabled = __target.IsEnabled;") + .AppendLine(" };") + .AppendLine(" __enabled(null, global::System.EventArgs.Empty);") + .AppendLine(" cmd.CanExecuteChanged += __enabled;"); + } + + /// Clears native target/action references and releases the bridge and selector. + /// The output builder. + /// The verified native capabilities. + /// The concrete control expression. + private static void AppendDetach(StringBuilder sb, NativeCommandInfo native, string control) + { + _ = sb.AppendLine(" serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() =>") + .AppendLine(SubscriptionBlockOpen) + .Append(" ").Append(control).AppendLine(".Target = null;"); + if (native.HasAction) + { + _ = sb.Append(" ").Append(control).AppendLine(".Action = null;") + .AppendLine(" __selector.Dispose();"); + } + + if (native.HasEnabled) + { + _ = sb.AppendLine(" cmd.CanExecuteChanged -= __enabled;"); + } + + _ = sb.AppendLine(" __target.Dispose();") + .AppendLine(" });") + .AppendLine(" });"); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandSymbols.cs new file mode 100644 index 00000000..0c01227d --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/AppKitCommandSymbols.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Identifies Cocoa's supported target/action hosts. +internal static class AppKitCommandSymbols +{ + /// Checks the native target property on a supported Cocoa control. + /// The concrete control. + /// True when its target can be assigned directly. + internal static bool CanBind(INamedTypeSymbol control) => + IsTargetHost(control) && NativeCommandMembers.HasWritableProperty(control, "Target", "Foundation.NSObject"); + + /// Recognizes the native target/action hierarchies. + /// The concrete control. + /// True for a supported Cocoa hierarchy. + internal static bool IsTargetHost(INamedTypeSymbol control) => + NativeCommandMembers.DerivesFrom(control, "AppKit.NSControl") + || NativeCommandMembers.DerivesFrom(control, "AppKit.NSCell") + || NativeCommandMembers.DerivesFrom(control, "AppKit.NSMenu") + || NativeCommandMembers.DerivesFrom(control, "AppKit.NSMenuItem") + || NativeCommandMembers.DerivesFrom(control, "AppKit.NSToolbarItem"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandControlEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandControlEmitter.cs new file mode 100644 index 00000000..163e41ec --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandControlEmitter.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Owns a command binding for each concrete control reached by the view's property path. +internal static class CommandControlEmitter +{ + /// Closes the observing worker and opens the worker that captures one control instance. + /// The output builder. + /// The command binding. + /// The view's observation capabilities. + /// The call site's stable identifier. + internal static void EmitRebinding(StringBuilder sb, BindCommandInvocationInfo inv, ClassBindingInfo? viewInfo, string suffix) + { + ObservationCodeGenerator.EmitInlineObservation(sb, "view", inv.ControlPropertyPath, inv.ControlTypeFullName, viewInfo, "__controlChanges"); + _ = BindingEmitterHelpers.AppendViewThreadCall(sb.Append(" var __controls = "), "__controlChanges", "view", inv.ViewThreadInvoker) + .AppendLine(";") + .AppendLine(" var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable();") + .AppendLine(" var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control =>") + .AppendLine(" {") + .AppendLine(" __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;") + .AppendLine(" if (__control != null)") + .AppendLine(" {") + .Append(" __controlBinding.Disposable = __BindCommandCore_").Append(suffix).Append("(__control, commandObs") + .Append(CommandParameterEmitter.HasParameter(inv) ? ", withParameter" : string.Empty).AppendLine(");") + .AppendLine(" }") + .AppendLine(" });") + .AppendLine(" return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding);") + .AppendLine(" }") + .AppendLine() + .Append(" private static global::System.IDisposable __BindCommandCore_").Append(suffix).Append('(') + .Append(inv.ControlTypeFullName).Append(" __control, global::System.IObservable<").Append(inv.CommandTypeFullName).Append("> commandObs"); + if (CommandParameterEmitter.HasParameter(inv)) + { + _ = sb.Append(", global::System.IObservable<").Append(inv.ParameterTypeFullName).Append("> withParameter"); + } + + _ = sb.AppendLine(")").AppendLine(" {"); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandEventBindingEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandEventBindingEmitter.cs index c8e30381..19b50669 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandEventBindingEmitter.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandEventBindingEmitter.cs @@ -4,7 +4,6 @@ using System; using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; @@ -35,7 +34,6 @@ internal static string SenderType(bool supportsNullable) => /// The control access chain expression. /// Whether the target supports nullable reference types (C# 8+). /// Emits the binding when an observable parameter is supplied. - /// Emits the binding when an expression parameter is supplied. /// Emits the binding when no parameter is supplied. internal static void EmitByParameterKind( StringBuilder sb, @@ -43,21 +41,14 @@ internal static void EmitByParameterKind( string controlAccess, bool supportsNullable, Action emitObservableParameter, - Action emitExpressionParameter, Action emitNoParameter) { var eventArgsType = inv.ResolvedEventArgsTypeFullName ?? "global::System.EventArgs"; - if (inv.HasObservableParameter) + if (CommandParameterEmitter.HasParameter(inv)) { emitObservableParameter(sb, inv, controlAccess, eventArgsType, supportsNullable); } - else if (inv is { HasExpressionParameter: true, ParameterPropertyPath: not null }) - { - var paramAccess = - CodeGeneratorHelpers.BuildPropertyAccessChain("viewModel", inv.ParameterPropertyPath.Value); - emitExpressionParameter(sb, inv, controlAccess, eventArgsType, paramAccess, supportsNullable); - } else { emitNoParameter(sb, inv, controlAccess, eventArgsType, supportsNullable); diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandParameterEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandParameterEmitter.cs new file mode 100644 index 00000000..71a528f0 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandParameterEmitter.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Keeps streamed parameters typed until ICommand requests its object-valued argument. +internal static class CommandParameterEmitter +{ + /// Determines whether a parameter stream is available in the generated worker. + /// The command binding. + /// True when the caller supplied a stream or property expression. + internal static bool HasParameter(BindCommandInvocationInfo inv) => + inv.HasObservableParameter || inv is { HasExpressionParameter: true, ParameterPropertyPath: not null }; + + /// Emits typed parameter storage, preserving null until the first value arrives. + /// The output builder. + /// The command binding. + internal static void EmitCapture(StringBuilder sb, BindCommandInvocationInfo inv) + { + var type = inv.ParameterTypeFullName ?? "object"; + _ = sb.Append(" ").Append(type).Append(" __latestParam = default(").Append(type).AppendLine(");") + .AppendLine(""" + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); + """); + } + + /// Returns the expression read at the ICommand object-parameter boundary. + /// The command binding. + /// The parameter reader or null. + internal static string Read(BindCommandInvocationInfo inv) => HasParameter(inv) ? "__ReadParameter()" : "null"; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandPropertyBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandPropertyBindingPlugin.cs index ac27f103..d10a6309 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandPropertyBindingPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/CommandPropertyBindingPlugin.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Text; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; @@ -40,7 +41,7 @@ internal sealed class CommandPropertyBindingPlugin : ICommandBindingPlugin /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanHandle(BindCommandInvocationInfo inv) => - inv.HasCommandProperty && inv.HasCommandParameterProperty; + !inv.HasExplicitEvent && inv.HasCommandProperty && inv.HasCommandParameterProperty; /// public void EmitBinding( @@ -82,6 +83,78 @@ public void EmitBinding( AppendRestoringReturn(sb, controlAccess, "new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial)"); } + /// + /// Checks if a control type has a settable Command property (ICommand) + /// and optionally a settable CommandParameter property. + /// Walks the type hierarchy. + /// + /// The control type symbol to inspect. + /// + /// Set to when the type also has a settable CommandParameter property. + /// + /// + /// if the type or one of its base types has a settable Command property. + /// + internal static bool HasCommandProperties(INamedTypeSymbol controlType, out bool hasCommandParameter) + { + hasCommandParameter = false; + var hasCommand = false; + + var current = (ITypeSymbol?)controlType; + while (current is INamedTypeSymbol namedCurrent) + { + var members = namedCurrent.GetMembers(); + for (var i = 0; i < members.Length; i++) + { + if (members[i] is not IPropertySymbol property) + { + continue; + } + + if (IsSettableICommandProperty(property)) + { + hasCommand = true; + } + + if (IsSettableCommandParameterProperty(property)) + { + hasCommandParameter = true; + } + } + + if (hasCommand && hasCommandParameter) + { + return true; + } + + current = namedCurrent.BaseType; + } + + return hasCommand; + } + + /// Determines whether a property is a settable public instance Command property typed as ICommand. + /// The property to inspect. + /// if the property is a settable ICommand-typed Command property. + internal static bool IsSettableICommandProperty(IPropertySymbol property) + { + if (property.Name != "Command" || property.IsReadOnly || property.IsStatic + || property.DeclaredAccessibility != Accessibility.Public) + { + return false; + } + + var typeName = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return typeName.EndsWith("ICommand", StringComparison.Ordinal); + } + + /// Determines whether a property is a settable public instance CommandParameter property. + /// The property to inspect. + /// if the property is a settable CommandParameter property. + internal static bool IsSettableCommandParameterProperty(IPropertySymbol property) => + property.Name == "CommandParameter" && !property.IsReadOnly && !property.IsStatic + && property.DeclaredAccessibility == Accessibility.Public; + /// Appends the reads that remember what the control carried before the binding touched it. /// The string builder to append to. /// The access chain to the bound control. diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/DefaultEventBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/DefaultEventBindingPlugin.cs index 0ce734e5..6f39cc14 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/DefaultEventBindingPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/DefaultEventBindingPlugin.cs @@ -6,6 +6,7 @@ using System.Text; using ReactiveUI.Binding.SourceGenerators.CodeGeneration; using ReactiveUI.Binding.SourceGenerators.Models; +using static ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.EventCommandBindingEmitter; namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; @@ -19,39 +20,64 @@ namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; /// Platforms covered: Any control with a Click/TouchUpInside/Pressed event /// that does not have Command or Enabled properties. /// -internal sealed class DefaultEventBindingPlugin : EventCommandBindingPlugin +internal sealed class DefaultEventBindingPlugin : ICommandBindingPlugin { /// The affinity score for the default-event binder (lowest priority among command binding plugins). private static readonly int DefaultEventAffinity = BindingAffinity.DefaultEvent; /// - public override int Affinity => DefaultEventAffinity; + public int Affinity => DefaultEventAffinity; /// - public override bool CanHandle(BindCommandInvocationInfo inv) => + public bool RequiresCustomBinderFallback => true; + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => inv.ResolvedEventName is not null; /// - protected override void EmitWithObservableParameter( + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + CommandEventBindingEmitter.EmitByParameterKind( + sb, + inv, + controlAccess, + supportsNullable, + EmitWithObservableParameter, + EmitWithNoParameter); + + /// Emits command execution using the latest streamed parameter. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// Whether nullable annotations are available. + internal static void EmitWithObservableParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, string eventArgsType, bool supportsNullable) { - AppendLatestParameterCapture(sb, inv, supportsNullable); + AppendLatestParameterCapture(sb, inv); AppendCommandMissingExit(sb); AppendHandlerDeclaration(sb, eventArgsType, supportsNullable); - _ = sb.Append(" var param = ").Append(CommandBindingSyntax.ReadLatestParameter(inv)).AppendLine(";"); + _ = sb.Append(" var param = ").Append(CommandParameterEmitter.Read(inv)).AppendLine(";"); AppendHandlerExecution(sb, "param"); AppendHandlerAttachment(sb, inv, controlAccess); AppendParameterisedDisposableReturn(sb); } - /// - protected override void EmitWithExpressionParameter( + /// Emits command execution using the selected parameter property. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// The typed command parameter access. + /// Whether nullable annotations are available. + internal static void EmitWithExpressionParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, @@ -72,8 +98,13 @@ protected override void EmitWithExpressionParameter( _ = sb.AppendLine(CommandBindingSyntax.CommandOnlyDisposableReturn).AppendLine(GeneratedSyntax.MemberBodyClose); } - /// - protected override void EmitWithNoParameter( + /// Emits command execution without a parameter. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// Whether nullable annotations are available. + internal static void EmitWithNoParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingEmitter.cs new file mode 100644 index 00000000..32dc1399 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingEmitter.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Shares command-event subscription fragments across binding mechanisms. +internal static class EventCommandBindingEmitter +{ + /// Appends the subscription that rebinds the control whenever the command property changes. + /// The string builder to append to. + internal static void AppendCommandSubscription(StringBuilder sb) => + _ = sb.AppendLine(CommandBindingSyntax.SerialDisposableDeclaration) + .AppendLine(CommandBindingSyntax.CommandSubscriptionOpen) + .AppendLine(GeneratedSyntax.StatementBlockOpen) + .AppendLine(CommandBindingSyntax.ResetSerialDisposable) + .AppendLine(CommandBindingSyntax.CommandMissingTest) + .AppendLine(CommandBindingSyntax.SubscriptionBlockOpen); + + /// Appends the capture of the latest parameter, and opens the command subscription over it. + /// The string builder to append to. + /// The BindCommand invocation info. + internal static void AppendLatestParameterCapture( + StringBuilder sb, + BindCommandInvocationInfo inv) + { + CommandParameterEmitter.EmitCapture(sb, inv); + AppendCommandSubscription(sb); + } + + /// Appends the declaration of the handler the control's event runs the command from. + /// The string builder to append to. + /// The event args type the control's event carries. + /// Whether the target supports nullable reference types. + internal static void AppendHandlerDeclaration(StringBuilder sb, string eventArgsType, bool supportsNullable) => + _ = sb.AppendLine().Append(CommandBindingSyntax.HandlerDeclarationOpen) + .Append(CommandEventBindingEmitter.SenderType(supportsNullable)).Append(CommandBindingSyntax.HandlerSenderSeparator) + .Append(eventArgsType).AppendLine(" e)").AppendLine(CommandBindingSyntax.SubscriptionBlockOpen); + + /// Appends the guarded run of the command inside the control's event handler. + /// The string builder to append to. + /// The expression the command is asked about and run with. + internal static void AppendHandlerExecution(StringBuilder sb, string argument) => + _ = sb.Append(" if (cmd.CanExecute(").Append(argument).AppendLine("))").AppendLine(CommandBindingSyntax.NestedBlockOpen) + .Append(" cmd.Execute(").Append(argument).AppendLine(");").AppendLine(CommandBindingSyntax.NestedBlockClose) + .AppendLine(CommandBindingSyntax.SubscriptionBlockClose).AppendLine(); + + /// Appends the return of the binding's own subscriptions when a parameter stream was subscribed to. + /// The string builder to append to. + internal static void AppendParameterisedDisposableReturn(StringBuilder sb) => + _ = sb.AppendLine(" return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(") + .AppendLine(" new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial);") + .AppendLine(GeneratedSyntax.MemberBodyClose); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingPlugin.cs deleted file mode 100644 index 53a138c9..00000000 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventCommandBindingPlugin.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. -// ReactiveUI and Contributors licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Runtime.CompilerServices; -using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; -using ReactiveUI.Binding.SourceGenerators.Models; - -namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; - -/// The base for command binding plugins that execute a command from a control's event. -internal closed class EventCommandBindingPlugin : ICommandBindingPlugin -{ - /// - public abstract int Affinity { get; } - - /// - public bool RequiresCustomBinderFallback => true; - - /// - public abstract bool CanHandle(BindCommandInvocationInfo inv); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitBinding( - StringBuilder sb, - BindCommandInvocationInfo inv, - string controlAccess, - bool supportsNullable) => CommandEventBindingEmitter.EmitByParameterKind( - sb, - inv, - controlAccess, - supportsNullable, - EmitWithObservableParameter, - EmitWithExpressionParameter, - EmitWithNoParameter); - - /// Appends the subscription that rebinds the control whenever the command property changes. - /// The string builder to append to. - protected static void AppendCommandSubscription(StringBuilder sb) => - _ = sb.AppendLine(CommandBindingSyntax.SerialDisposableDeclaration) - .AppendLine(CommandBindingSyntax.CommandSubscriptionOpen) - .AppendLine(GeneratedSyntax.StatementBlockOpen) - .AppendLine(CommandBindingSyntax.ResetSerialDisposable) - .AppendLine(CommandBindingSyntax.CommandMissingTest) - .AppendLine(CommandBindingSyntax.SubscriptionBlockOpen); - - /// Appends the capture of the latest parameter, and opens the command subscription over it. - /// The string builder to append to. - /// The BindCommand invocation info. - /// Whether the target supports nullable reference types. - protected static void AppendLatestParameterCapture( - StringBuilder sb, - BindCommandInvocationInfo inv, - bool supportsNullable) - { - // The parameter and command streams emit independently, so the handler reads the latest parameter when it runs. - _ = sb.AppendLine().Append(" ").Append(inv.ParameterTypeFullName) - .Append(supportsNullable && inv.ParameterIsReferenceType ? "?" : string.Empty).AppendLine(" __latestParam = default;") - .AppendLine(" var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(") - .Append(" withParameter, p => ").Append(CommandBindingSyntax.WriteLatestParameter(inv, "p")).AppendLine(");").AppendLine(); - - AppendCommandSubscription(sb); - } - - /// Appends the declaration of the handler the control's event runs the command from. - /// The string builder to append to. - /// The event args type the control's event carries. - /// Whether the target supports nullable reference types. - protected static void AppendHandlerDeclaration(StringBuilder sb, string eventArgsType, bool supportsNullable) => - _ = sb.AppendLine().Append(CommandBindingSyntax.HandlerDeclarationOpen) - .Append(CommandEventBindingEmitter.SenderType(supportsNullable)).Append(CommandBindingSyntax.HandlerSenderSeparator) - .Append(eventArgsType).AppendLine(" e)").AppendLine(CommandBindingSyntax.SubscriptionBlockOpen); - - /// Appends the guarded run of the command inside the control's event handler. - /// The string builder to append to. - /// The expression the command is asked about and run with. - protected static void AppendHandlerExecution(StringBuilder sb, string argument) => - _ = sb.Append(" if (cmd.CanExecute(").Append(argument).AppendLine("))").AppendLine(CommandBindingSyntax.NestedBlockOpen) - .Append(" cmd.Execute(").Append(argument).AppendLine(");").AppendLine(CommandBindingSyntax.NestedBlockClose) - .AppendLine(CommandBindingSyntax.SubscriptionBlockClose).AppendLine(); - - /// Appends the return of the binding's own subscriptions when a parameter stream was subscribed to. - /// The string builder to append to. - protected static void AppendParameterisedDisposableReturn(StringBuilder sb) => - _ = sb.AppendLine(" return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(") - .AppendLine(" new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial);") - .AppendLine(GeneratedSyntax.MemberBodyClose); - - /// Emits the binding when the command parameter arrives on an observable. - /// The string builder. - /// The BindCommand invocation info. - /// The control access chain. - /// The event args type. - /// Whether the target supports nullable reference types. - protected abstract void EmitWithObservableParameter( - StringBuilder sb, - BindCommandInvocationInfo inv, - string controlAccess, - string eventArgsType, - bool supportsNullable); - - /// Emits the binding when the command parameter comes from a property expression. - /// The string builder. - /// The BindCommand invocation info. - /// The control access chain. - /// The event args type. - /// The parameter access chain. - /// Whether the target supports nullable reference types. - protected abstract void EmitWithExpressionParameter( - StringBuilder sb, - BindCommandInvocationInfo inv, - string controlAccess, - string eventArgsType, - string paramAccess, - bool supportsNullable); - - /// Emits the binding when the command takes no parameter. - /// The string builder. - /// The BindCommand invocation info. - /// The control access chain. - /// The event args type. - /// Whether the target supports nullable reference types. - protected abstract void EmitWithNoParameter( - StringBuilder sb, - BindCommandInvocationInfo inv, - string controlAccess, - string eventArgsType, - bool supportsNullable); -} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventEnabledBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventEnabledBindingPlugin.cs index 5c0f50fe..a9f92e6b 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventEnabledBindingPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/EventEnabledBindingPlugin.cs @@ -4,8 +4,10 @@ using System.Runtime.CompilerServices; using System.Text; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.CodeGeneration; using ReactiveUI.Binding.SourceGenerators.Models; +using static ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.EventCommandBindingEmitter; namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; @@ -20,7 +22,7 @@ namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; /// Platforms covered: WinForms Control/ToolStripItem (Click+Enabled), /// Android View (Click+Enabled), Apple UIControl (TouchUpInside+Enabled). /// -internal sealed class EventEnabledBindingPlugin : EventCommandBindingPlugin +internal sealed class EventEnabledBindingPlugin : ICommandBindingPlugin { /// Opens the assignment that puts the control's enabled state in step with the command. private const string CanExecuteEnabledOpen = ".Enabled = cmd.CanExecute("; @@ -29,23 +31,42 @@ internal sealed class EventEnabledBindingPlugin : EventCommandBindingPlugin private static readonly int EventEnabledAffinity = BindingAffinity.EventEnabledControl; /// - public override int Affinity => EventEnabledAffinity; + public int Affinity => EventEnabledAffinity; /// - public override bool CanHandle(BindCommandInvocationInfo inv) => + public bool RequiresCustomBinderFallback => true; + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => inv.ResolvedEventName is not null && inv.HasEnabledProperty; /// - protected override void EmitWithObservableParameter( + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + CommandEventBindingEmitter.EmitByParameterKind( + sb, + inv, + controlAccess, + supportsNullable, + EmitWithObservableParameter, + EmitWithNoParameter); + + /// Emits command execution using the latest streamed parameter. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// Whether nullable annotations are available. + internal static void EmitWithObservableParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, string eventArgsType, bool supportsNullable) { - var latestParameter = CommandBindingSyntax.ReadLatestParameter(inv); + var latestParameter = CommandParameterEmitter.Read(inv); - AppendLatestParameterCapture(sb, inv, supportsNullable); + AppendLatestParameterCapture(sb, inv); AppendCommandMissingExit(sb, controlAccess); _ = sb.Append(" var param = ").Append(latestParameter).AppendLine(";"); @@ -60,8 +81,14 @@ protected override void EmitWithObservableParameter( AppendParameterisedDisposableReturn(sb); } - /// - protected override void EmitWithExpressionParameter( + /// Emits command execution using the selected parameter property. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// The typed command parameter access. + /// Whether nullable annotations are available. + internal static void EmitWithExpressionParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, @@ -83,8 +110,13 @@ protected override void EmitWithExpressionParameter( _ = sb.AppendLine(CommandBindingSyntax.CommandOnlyDisposableReturn).AppendLine(GeneratedSyntax.MemberBodyClose); } - /// - protected override void EmitWithNoParameter( + /// Emits command execution without a parameter. + /// The output builder. + /// The extracted binding call. + /// The control's typed access expression. + /// The framework event argument type. + /// Whether nullable annotations are available. + internal static void EmitWithNoParameter( StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, @@ -102,6 +134,37 @@ protected override void EmitWithNoParameter( _ = sb.AppendLine(CommandBindingSyntax.CommandOnlyDisposableReturn).AppendLine(GeneratedSyntax.MemberBodyClose); } + /// Checks if a control type has a settable Enabled property (bool). Walks the type hierarchy. + /// The control type symbol to inspect. + /// + /// if the type or one of its base types has a public settable + /// bool Enabled property. + /// + internal static bool HasEnabledProperty(INamedTypeSymbol controlType) + { + var current = (ITypeSymbol?)controlType; + while (current is INamedTypeSymbol namedCurrent) + { + var members = namedCurrent.GetMembers(); + for (var i = 0; i < members.Length; i++) + { + if (members[i] is IPropertySymbol property + && property.Name == "Enabled" + && !property.IsReadOnly + && !property.IsStatic + && property.DeclaredAccessibility == Accessibility.Public + && property.Type.SpecialType == SpecialType.System_Boolean) + { + return true; + } + } + + current = namedCurrent.BaseType; + } + + return false; + } + /// Appends the exit taken while the view model has handed over no command, disabling the control. /// The string builder to append to. /// The control access chain. diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandEmitter.cs new file mode 100644 index 00000000..42ba67e5 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandEmitter.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Emits native event and UIKit touch command bindings. +internal static class NativeCommandEmitter +{ + /// Opens the command's nested subscription body. + private const string SubscriptionBlockOpen = " {"; + + /// The shared event implementation, called directly after native selection. + private static readonly EventEnabledBindingPlugin EventBinding = new(); + + /// Uses the verified native event instead of a generic default-event guess. + /// The output builder. + /// The binding with its native event. + /// The concrete control expression. + /// Whether nullable annotations are supported. + internal static void EmitEvent(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) + { + var native = inv.NativeCommand!; + EventBinding.EmitBinding(sb, inv with { ResolvedEventName = native.EventName, ResolvedEventArgsTypeFullName = native.EventArgsType }, controlAccess, supportsNullable); + } + + /// Attaches UIKit's native touch target and keeps enabled state in step with the command. + /// The output builder. + /// The selected binding. + /// The concrete control expression. + internal static void EmitTouch(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess) + { + if (CommandParameterEmitter.HasParameter(inv)) + { + CommandParameterEmitter.EmitCapture(sb, inv); + } + + var parameter = CommandParameterEmitter.Read(inv); + _ = sb.Append(" var __nativeControl = (global::UIKit.UIControl)").Append(controlAccess).AppendLine(";") + .AppendLine(" var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable();") + .AppendLine(" var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd =>") + .AppendLine(" {") + .AppendLine(" serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance;") + .AppendLine(" if (cmd == null)") + .AppendLine(SubscriptionBlockOpen) + .AppendLine(" __nativeControl.Enabled = false;") + .AppendLine(" return;") + .AppendLine(" }") + .AppendLine(" global::System.EventHandler __action = (__sender, __args) =>") + .AppendLine(SubscriptionBlockOpen) + .Append(" var __parameter = (object)").Append(parameter).AppendLine(";") + .AppendLine(" if (cmd.CanExecute(__parameter))") + .AppendLine(" {") + .AppendLine(" cmd.Execute(__parameter);") + .AppendLine(" }") + .AppendLine(" };") + .Append(" global::System.EventHandler __enabled = (__sender, __args) => __nativeControl.Enabled = cmd.CanExecute(") + .Append(parameter).AppendLine(");") + .Append(" __nativeControl.Enabled = cmd.CanExecute(").Append(parameter).AppendLine(");") + .AppendLine(" __nativeControl.AddTarget(__action, global::UIKit.UIControlEvent.TouchUpInside);") + .AppendLine(" cmd.CanExecuteChanged += __enabled;") + .AppendLine(" serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() =>") + .AppendLine(SubscriptionBlockOpen) + .AppendLine(" __nativeControl.RemoveTarget(__action, global::UIKit.UIControlEvent.TouchUpInside);") + .AppendLine(" cmd.CanExecuteChanged -= __enabled;") + .AppendLine(" });") + .AppendLine(" });"); + AppendReturn(sb, inv); + } + + /// Returns the command, native-handler and optional parameter subscriptions. + /// The output builder. + /// The binding whose parameter stream may be present. + internal static void AppendReturn(StringBuilder sb, BindCommandInvocationInfo inv) + { + _ = sb.Append(" return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial"); + if (CommandParameterEmitter.HasParameter(inv)) + { + _ = sb.Append(", __paramSub"); + } + + _ = sb.AppendLine(");").AppendLine(" }"); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandSymbols.cs new file mode 100644 index 00000000..68984019 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/NativeCommandSymbols.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; +using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Provides native command member checks shared by the platform mechanisms. +internal static class NativeCommandSymbols +{ + /// Reads a native event route with a writable enabled flag. + /// The concrete control. + /// The native route. + /// The required event. + /// The verified route, or null. + internal static NativeCommandInfo? Event(INamedTypeSymbol control, NativeCommandKind kind, string eventName) + { + var changeEvent = PlatformSymbols.FindEvent(control, eventName); + var enabled = HasWritableProperty(control, "Enabled", "bool"); + return changeEvent is null || !enabled + ? null + : new(kind, eventName, EventHelpers.FindEventArgsType(control, eventName), true, false); + } + + /// Checks that a native property has a public setter and the required type. + /// The concrete owner. + /// The property name. + /// The required CLR type. + /// True when the property can be assigned directly. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool HasWritableProperty(INamedTypeSymbol control, string name, string typeName) => + NativeCommandMembers.HasWritableProperty(control, name, typeName); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandBindingPlugin.cs new file mode 100644 index 00000000..71a6c3fb --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandBindingPlugin.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Binds UIKit touch actions through the native AddTarget and RemoveTarget APIs. +internal sealed class UIKitCommandBindingPlugin : IPlatformCommandBindingPlugin +{ + /// The native UIControl command score. + private const int TouchAffinity = 9; + + /// + public int Affinity => TouchAffinity; + + /// + public bool RequiresCustomBinderFallback => true; + + /// + public NativeCommandInfo? InspectControl(INamedTypeSymbol control) => + UIKitCommandSymbols.CanBindTouch(control) + ? new(NativeCommandKind.UIKitTouch, null, null, true, false) + : null; + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => !inv.HasExplicitEvent && inv.NativeCommand?.Kind == NativeCommandKind.UIKitTouch; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + NativeCommandEmitter.EmitTouch(sb, inv, controlAccess); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandSymbols.cs new file mode 100644 index 00000000..dd9bd0ea --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitCommandSymbols.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Identifies UIKit's touch and control-specific command routes. +internal static class UIKitCommandSymbols +{ + /// Checks the native touch target methods and enabled property. + /// The concrete control. + /// True when UIKit can attach a typed touch handler. + internal static bool CanBindTouch(INamedTypeSymbol control) => + NativeCommandMembers.DerivesFrom(control, "UIKit.UIControl") + && NativeCommandMembers.HasWritableProperty(control, "Enabled", "bool") + && NativeCommandMembers.HasTargetMethod(control, "AddTarget") + && NativeCommandMembers.HasTargetMethod(control, "RemoveTarget"); + + /// Finds the verified specialized command event. + /// The concrete control. + /// The specialized event name, or null. + internal static string? ControlEvent(INamedTypeSymbol control) + { + string? name = null; + if (NativeCommandMembers.DerivesFrom(control, "UIKit.UIRefreshControl")) + { + name = "ValueChanged"; + } + else if (NativeCommandMembers.DerivesFrom(control, "UIKit.UIBarButtonItem")) + { + name = "Clicked"; + } + + return name is not null && NativeCommandMembers.HasWritableProperty(control, "Enabled", "bool") && NativeCommandMembers.HasEvent(control, name) + ? name + : null; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitControlCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitControlCommandBindingPlugin.cs new file mode 100644 index 00000000..d2ff2800 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBinding/UIKitControlCommandBindingPlugin.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; + +/// Binds UIKit refresh controls and bar-button items through their native events. +internal sealed class UIKitControlCommandBindingPlugin : IPlatformCommandBindingPlugin +{ + /// The specialized UIKit control score. + private const int ControlAffinity = 10; + + /// + public int Affinity => ControlAffinity; + + /// + public bool RequiresCustomBinderFallback => true; + + /// + public NativeCommandInfo? InspectControl(INamedTypeSymbol control) => + UIKitCommandSymbols.ControlEvent(control) is { } name + ? NativeCommandSymbols.Event(control, NativeCommandKind.UIKitEvent, name) + : null; + + /// + public bool CanHandle(BindCommandInvocationInfo inv) => !inv.HasExplicitEvent && inv.NativeCommand?.Kind == NativeCommandKind.UIKitEvent; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitBinding(StringBuilder sb, BindCommandInvocationInfo inv, string controlAccess, bool supportsNullable) => + NativeCommandEmitter.EmitEvent(sb, inv, controlAccess, supportsNullable); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBindingPluginRegistry.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBindingPluginRegistry.cs index 3adeb7bb..df731ae5 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBindingPluginRegistry.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/CommandBindingPluginRegistry.cs @@ -2,6 +2,7 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; using ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding; @@ -16,24 +17,56 @@ internal static class CommandBindingPluginRegistry /// All command binding plugins sorted by affinity descending (highest priority first). private static readonly ICommandBindingPlugin[] Plugins = [ + new UIKitControlCommandBindingPlugin(), + new UIKitCommandBindingPlugin(), + new AndroidCommandBindingPlugin(), new CommandPropertyBindingPlugin(), // Affinity 5 + new AppKitCommandBindingPlugin(), new EventEnabledBindingPlugin(), // Affinity 4 new DefaultEventBindingPlugin() // Affinity 3 ]; + /// Finds the strongest native route while the control's symbols are available. + /// The concrete control, or null when unresolved. + /// The native member data, or null. + internal static NativeCommandInfo? InspectControl(INamedTypeSymbol? control) + { + if (control is null) + { + return null; + } + + NativeCommandInfo? best = null; + var affinity = 0; + foreach (var plugin in Plugins) + { + if (plugin is not IPlatformCommandBindingPlugin native || plugin.Affinity <= affinity || native.InspectControl(control) is not { } candidate) + { + continue; + } + + best = candidate; + affinity = plugin.Affinity; + } + + return best; + } + /// Returns the highest-affinity plugin that can handle the given invocation, or if no plugin matches. /// The BindCommand invocation info. /// The best matching plugin, or null. internal static ICommandBindingPlugin? GetBestPlugin(BindCommandInvocationInfo inv) { + ICommandBindingPlugin? best = null; for (var i = 0; i < Plugins.Length; i++) { - if (Plugins[i].CanHandle(inv)) + var candidate = Plugins[i]; + if (candidate.CanHandle(inv) && (best is null || candidate.Affinity > best.Affinity)) { - return Plugins[i]; + best = candidate; } } - return null; + return best; } } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AndroidConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AndroidConversionPlugin.cs new file mode 100644 index 00000000..80a5ccc2 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AndroidConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts Android visibility to typed boolean bindings. +internal sealed class AndroidConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + VisibilityConversion.Select(source, target, "Android.Views.ViewStates", "Gone", compilation, false, string.Empty); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AppleConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AppleConversionPlugin.cs new file mode 100644 index 00000000..4e616b42 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/AppleConversionPlugin.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits native Foundation date operators with their null-input contract. +internal sealed class AppleConversionPlugin : IConversionPlugin +{ + /// The Foundation date converter's score. + private const int Affinity = 8; + + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var from = UnwrapNullable(source); + var to = UnwrapNullable(target); + var fromNative = from.ToDisplayString() == "Foundation.NSDate"; + var toNative = to.ToDisplayString() == "Foundation.NSDate"; + if (fromNative == toNative) + { + return null; + } + + return fromNative ? FromNative(from, target, compilation) : ToNative(source, to, compilation); + } + + /// Emits the managed-date to native-date direction, including nullable rejection. + /// The managed input type. + /// The native output type. + /// The consumer compilation. + /// The direct conversion, or null when the native operator is unavailable. + internal static ConversionInfo? ToNative(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var managed = UnwrapNullable(source); + if (!IsManagedDate(managed) || !HasOperator(compilation.GetSpecialType(SpecialType.System_DateTime), target, compilation)) + { + return null; + } + + var nullable = !SymbolEqualityComparer.Default.Equals(managed, source); + var value = nullable ? "__value.Value" : "__value"; + var member = managed.Name == "DateTimeOffset" ? ".DateTime" : string.Empty; + return new($"(global::Foundation.NSDate){value}{member}", nullable ? "__value.HasValue" : "true", Affinity); + } + + /// Emits the native-date to managed-date direction with native-null rejection. + /// The native input type. + /// The managed output type. + /// The consumer compilation. + /// The direct conversion, or null when the native operator is unavailable. + internal static ConversionInfo? FromNative(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var managed = UnwrapNullable(target); + if (!IsManagedDate(managed) || !HasOperator(source, compilation.GetSpecialType(SpecialType.System_DateTime), compilation)) + { + return null; + } + + var expression = managed.Name == "DateTimeOffset" + ? "new global::System.DateTimeOffset((global::System.DateTime)__value)" + : "(global::System.DateTime)__value"; + return new($"({target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})({expression})", "__value != null", Affinity); + } + + /// Unwraps nullable value types while retaining the native reference type. + /// The source or destination type. + /// The value type used to select the native operator. + internal static ITypeSymbol UnwrapNullable(ITypeSymbol type) => + type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullable + ? nullable.TypeArguments[0] + : type; + + /// Identifies the managed date types covered by the Foundation converters. + /// The unwrapped value type. + /// True for a supported managed date. + private static bool IsManagedDate(ITypeSymbol type) => type.ToDisplayString() is "System.DateTime" or "System.DateTimeOffset"; + + /// Verifies the concrete native cast against the consumer's symbols. + /// The operator input. + /// The operator output. + /// The consumer compilation. + /// True when the native operator exists. + private static bool HasOperator(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var conversion = ((CSharpCompilation)compilation).ClassifyConversion(source, target); + return conversion.IsUserDefined && conversion.Exists; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/BooleanStringConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/BooleanStringConversionPlugin.cs new file mode 100644 index 00000000..c9e4b2aa --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/BooleanStringConversionPlugin.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits boolean formatting and parsing, including nullable values. +internal sealed class BooleanStringConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + StringConversionExpressions.SelectScalar( + source, + target, + ConversionSymbols.Unwrap(source).SpecialType == SpecialType.System_Boolean, + ConversionSymbols.Unwrap(target).SpecialType == SpecialType.System_Boolean, + string.Empty); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionPluginRegistry.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionPluginRegistry.cs new file mode 100644 index 00000000..158295f2 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionPluginRegistry.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Selects typed framework conversions from the consumer's symbols. +internal static class ConversionPluginRegistry +{ + /// The framework adapters, in deterministic tie order. + private static readonly IConversionPlugin[] Plugins = + [ + new WpfConversionPlugin(), + new WinUIConversionPlugin(), + new UnoConversionPlugin(), + new MauiConversionPlugin(), + new AndroidConversionPlugin(), + new AppleConversionPlugin(), + new NumericStringConversionPlugin(), + new BooleanStringConversionPlugin(), + new GuidStringConversionPlugin(), + new TemporalStringConversionPlugin(), + new NullableValueConversionPlugin(), + new UriConversionPlugin(), + new StringIdentityConversionPlugin(), + new EqualityConversionPlugin(), + new LanguageConversionPlugin(), + ]; + + /// Reads the declared value type of a property selector. + /// The selector expression. + /// The consumer's semantic model. + /// The cancellation token. + /// The selected value type. + internal static ITypeSymbol? SelectorType(ExpressionSyntax expression, SemanticModel model, CancellationToken token) => + expression is LambdaExpressionSyntax { Body: ExpressionSyntax body } ? model.GetTypeInfo(body, token).Type : null; + + /// Compares applicable framework mechanisms and retains the highest affinity. + /// The declared input type. + /// The declared output type. + /// The consumer compilation. + /// The winning direct conversion, or null when the registry must resolve it. + internal static ConversionInfo? Select(ITypeSymbol? source, ITypeSymbol? target, Compilation compilation) + { + if (source is null || target is null) + { + return null; + } + + ConversionInfo? winner = null; + foreach (var plugin in Plugins) + { + var candidate = plugin.Select(source, target, compilation); + if (candidate is not null && (winner is null || candidate.Affinity > winner.Affinity)) + { + winner = candidate; + } + } + + return winner is null ? null : ConversionSymbols.WithAssignment(winner, source, target, compilation); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionSymbols.cs new file mode 100644 index 00000000..f18b964e --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/ConversionSymbols.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Provides type predicates shared by the fixed conversion mechanisms. +internal static class ConversionSymbols +{ + /// Records the typed assignment available when a registered converter declines a value. + /// The selected mechanism. + /// The input type. + /// The output type. + /// The consumer compilation. + /// The mechanism with its assignment contract. + internal static ConversionInfo WithAssignment(ConversionInfo conversion, ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var assignment = ((CSharpCompilation)compilation).ClassifyConversion(source, target); + var nullableLift = !IsNullable(source) && IsNullable(target) && SymbolEqualityComparer.Default.Equals(source, Unwrap(target)); + var assignable = assignment.IsIdentity || nullableLift || (assignment.IsImplicit && (assignment.IsReference || assignment.IsBoxing)); + return conversion with + { + AssignmentFallback = assignable ? $"({target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})__value" : null, + IsIdentity = assignment.IsIdentity, + }; + } + + /// Unwraps nullable value types for mechanism selection. + /// The declared value type. + /// The underlying value type. + internal static ITypeSymbol Unwrap(ITypeSymbol type) => + type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullable + ? nullable.TypeArguments[0] + : type; + + /// Determines whether a value has a nullable wrapper. + /// The declared value type. + /// True for a nullable value type. + internal static bool IsNullable(ITypeSymbol type) => + type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }; + + /// Recognizes the numeric types covered by the standard converters. + /// The unwrapped value type. + /// True for a supported numeric type. + internal static bool IsNumeric(ITypeSymbol type) => type.SpecialType is + SpecialType.System_Byte or SpecialType.System_Int16 or SpecialType.System_Int32 or SpecialType.System_Int64 + or SpecialType.System_Single or SpecialType.System_Double or SpecialType.System_Decimal; + + /// Recognizes a named framework type without treating nullable annotations as another type. + /// The value type. + /// The framework type name. + /// True for the named type in the System namespace. + internal static bool IsSystemType(ITypeSymbol type, string name) => + type.Name == name && type.ContainingNamespace is { Name: "System", ContainingNamespace.IsGlobalNamespace: true }; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/EqualityConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/EqualityConversionPlugin.cs new file mode 100644 index 00000000..1fcf38ee --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/EqualityConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Compares an object-valued source with the caller's comparison hint. +internal sealed class EqualityConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + source.SpecialType == SpecialType.System_Object && target.SpecialType == SpecialType.System_Boolean + ? new("global::System.Object.Equals(__value, __hint)", "true", BindingAffinity.Fallback) + : null; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/GuidStringConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/GuidStringConversionPlugin.cs new file mode 100644 index 00000000..39ed4936 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/GuidStringConversionPlugin.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits standard hyphenated GUID formatting and GUID parsing. +internal sealed class GuidStringConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + StringConversionExpressions.SelectScalar( + source, + target, + ConversionSymbols.IsSystemType(ConversionSymbols.Unwrap(source), nameof(Guid)), + ConversionSymbols.IsSystemType(ConversionSymbols.Unwrap(target), nameof(Guid)), + "\"D\""); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/IConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/IConversionPlugin.cs new file mode 100644 index 00000000..500c8e09 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/IConversionPlugin.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts a native conversion into typed generated code. +internal interface IConversionPlugin +{ + /// Offers a conversion only when the consumer's symbols support its emitted expression. + /// The declared input type. + /// The declared output type. + /// The consumer compilation. + /// The candidate's typed expression and affinity, or null when ineligible. + ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/LanguageConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/LanguageConversionPlugin.cs new file mode 100644 index 00000000..6fff39e0 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/LanguageConversionPlugin.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Offers the compiler's implicit conversion without adding a boxing boundary. +internal sealed class LanguageConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var conversion = ((CSharpCompilation)compilation).ClassifyConversion(source, target); + return conversion.IsImplicit && !conversion.IsBoxing + ? new($"({target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})__value", "true", 1) + : null; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/MauiConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/MauiConversionPlugin.cs new file mode 100644 index 00000000..d1859596 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/MauiConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts Maui visibility to typed boolean bindings. +internal sealed class MauiConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + VisibilityConversion.Select(source, target, "Microsoft.Maui.Visibility", "Collapsed", compilation, true, "ReactiveUI"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NullableValueConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NullableValueConversionPlugin.cs new file mode 100644 index 00000000..864bbe96 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NullableValueConversionPlugin.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits numeric nullable wrapping and unwrapping without boxing. +internal sealed class NullableValueConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var underlying = ConversionSymbols.Unwrap(source); + var sourceNullable = ConversionSymbols.IsNullable(source); + if (!ConversionSymbols.IsNumeric(underlying) + || sourceNullable == ConversionSymbols.IsNullable(target) + || !SymbolEqualityComparer.Default.Equals(underlying, ConversionSymbols.Unwrap(target))) + { + return null; + } + + return new( + sourceNullable ? "__value.Value" : $"({target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})__value", + sourceNullable ? "__value.HasValue" : "true", + BindingAffinity.DefaultInternalTypeConverter); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NumericStringConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NumericStringConversionPlugin.cs new file mode 100644 index 00000000..3f2d098e --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/NumericStringConversionPlugin.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits the standard numeric formatting and parsing operations. +internal sealed class NumericStringConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + var input = ConversionSymbols.Unwrap(source); + if (target.SpecialType == SpecialType.System_String && ConversionSymbols.IsNumeric(input)) + { + return StringConversionExpressions.Format(FormattingExpression(input, ConversionSymbols.IsNullable(source)), ConversionSymbols.IsNullable(source)); + } + + return source.SpecialType == SpecialType.System_String && ConversionSymbols.IsNumeric(ConversionSymbols.Unwrap(target)) + ? StringConversionExpressions.Parse(target) + : null; + } + + /// Preserves width, precision, custom format and the Single converter's invariant default. + /// The unwrapped numeric type. + /// Whether the input has a nullable wrapper. + /// The concrete ToString operation. + internal static string FormattingExpression(ITypeSymbol type, bool nullable) + { + var value = nullable ? "__value.Value" : "__value"; + var prefix = type.SpecialType is SpecialType.System_Single or SpecialType.System_Double or SpecialType.System_Decimal ? "F" : "D"; + var culture = type.SpecialType == SpecialType.System_Single && !nullable ? "global::System.Globalization.CultureInfo.InvariantCulture" : string.Empty; + return $"__hint is int __precision ? {value}.ToString(\"{prefix}\" + __precision.ToString())" + + $" : __hint is string __format ? {value}.ToString(__format) : {value}.ToString({culture})"; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringConversionExpressions.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringConversionExpressions.cs new file mode 100644 index 00000000..c3d80f44 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringConversionExpressions.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Builds typed formatting and parsing expressions with nullable-value semantics. +internal static class StringConversionExpressions +{ + /// Builds the common scalar-to-string and string-to-scalar directions. + /// The declared input. + /// The declared output. + /// Whether this mechanism supports the input scalar. + /// Whether this mechanism supports the output scalar. + /// The arguments to the scalar's ToString method. + /// The direct conversion, or null. + internal static ConversionInfo? SelectScalar(ITypeSymbol source, ITypeSymbol target, bool sourceSupported, bool targetSupported, string formatArguments) + { + if (target.SpecialType == SpecialType.System_String && sourceSupported) + { + var value = ConversionSymbols.IsNullable(source) ? "__value.Value" : "__value"; + return Format($"{value}.ToString({formatArguments})", ConversionSymbols.IsNullable(source)); + } + + return source.SpecialType == SpecialType.System_String && targetSupported ? Parse(target) : null; + } + + /// Preserves a successful null result when formatting a nullable value. + /// The non-null formatting operation. + /// Whether the input is nullable. + /// The formatting candidate. + internal static ConversionInfo Format(string expression, bool nullable) => + new(nullable ? $"__value.HasValue ? ({expression}) : null" : expression, "true", BindingAffinity.DefaultInternalTypeConverter); + + /// Parses directly into the concrete value type, treating an empty nullable input as a successful null. + /// The declared output type. + /// The parsing candidate. + internal static ConversionInfo Parse(ITypeSymbol target) + { + var underlying = ConversionSymbols.Unwrap(target).ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var targetName = target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var nullable = ConversionSymbols.IsNullable(target); + var parse = $"{underlying}.TryParse(__value, out __parsed)"; + var condition = nullable ? $"global::System.String.IsNullOrEmpty(__value) || {parse}" : parse; + var expression = nullable ? $"global::System.String.IsNullOrEmpty(__value) ? default({targetName}) : ({targetName})__parsed" : "__parsed"; + return new(expression, condition, BindingAffinity.DefaultInternalTypeConverter) { Preparation = $"{underlying} __parsed = default({underlying});", }; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringIdentityConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringIdentityConversionPlugin.cs new file mode 100644 index 00000000..b6d3524e --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/StringIdentityConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Offers the standard string converter's identity operation. +internal sealed class StringIdentityConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + source.SpecialType == SpecialType.System_String && target.SpecialType == SpecialType.System_String + ? new("__value", "__value != null", BindingAffinity.DefaultInternalTypeConverter) + : null; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/TemporalStringConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/TemporalStringConversionPlugin.cs new file mode 100644 index 00000000..1a37b7b9 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/TemporalStringConversionPlugin.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits formatting and parsing for the standard date and time values. +internal sealed class TemporalStringConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + StringConversionExpressions.SelectScalar(source, target, Supports(ConversionSymbols.Unwrap(source)), Supports(ConversionSymbols.Unwrap(target)), string.Empty); + + /// Identifies the date and time types covered by the standard converters. + /// The unwrapped value type. + /// True for a supported temporal value. + internal static bool Supports(ITypeSymbol type) => + type.ContainingNamespace is { Name: "System", ContainingNamespace.IsGlobalNamespace: true } + && type.Name is "DateTime" or "DateTimeOffset" or "TimeSpan" or "DateOnly" or "TimeOnly"; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UnoConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UnoConversionPlugin.cs new file mode 100644 index 00000000..2528d7c8 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UnoConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts Uno visibility to typed boolean bindings. +internal sealed class UnoConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + VisibilityConversion.Select(source, target, "Windows.UI.Xaml.Visibility", "Collapsed", compilation, false, "ReactiveUI.Uno"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UriConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UriConversionPlugin.cs new file mode 100644 index 00000000..de4fb700 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/UriConversionPlugin.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Emits URI formatting and relative-or-absolute URI parsing. +internal sealed class UriConversionPlugin : IConversionPlugin +{ + /// + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) + { + if (ConversionSymbols.IsSystemType(source, nameof(Uri)) && target.SpecialType == SpecialType.System_String) + { + return new("__value.ToString()", "__value != null", BindingAffinity.DefaultInternalTypeConverter); + } + + return source.SpecialType == SpecialType.System_String && ConversionSymbols.IsSystemType(target, nameof(Uri)) + ? new("__parsed", "global::System.Uri.TryCreate(__value, global::System.UriKind.RelativeOrAbsolute, out __parsed)", BindingAffinity.DefaultInternalTypeConverter) + { + Preparation = "global::System.Uri __parsed;", + } + : null; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityConversion.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityConversion.cs new file mode 100644 index 00000000..704fc853 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityConversion.cs @@ -0,0 +1,72 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Validates and emits the enum mapping shared by visibility adapters. +internal static class VisibilityConversion +{ + /// Matches the native visibility converter's score. + private const int Affinity = 2; + + /// Builds a mapping only for the exact framework enum and its required members. + /// The input type. + /// The output type. + /// The platform's visibility enum. + /// The enum member representing false. + /// The consumer compilation. + /// Whether the platform supports the hidden flag. + /// The platform's visibility hint namespace. + /// The typed visibility mapping, or null when ineligible. + internal static ConversionInfo? Select( + ITypeSymbol source, + ITypeSymbol target, + string metadataName, + string hidden, + Compilation compilation, + bool supportsHidden, + string hintNamespace) + { + var forward = source.SpecialType == SpecialType.System_Boolean; + var enumType = forward ? target : source; + return (!forward && target.SpecialType != SpecialType.System_Boolean) + || enumType.TypeKind != TypeKind.Enum + || enumType.ToDisplayString() != metadataName + || enumType.GetMembers("Visible").IsEmpty + || enumType.GetMembers(hidden).IsEmpty + ? null + : Create(enumType, forward, hidden, compilation, supportsHidden, hintNamespace); + } + + /// Applies the platform's inversion and hidden-value rules. + /// The verified framework enum. + /// Whether the input is boolean. + /// The ordinary false enum member. + /// The consumer compilation. + /// Whether Hidden is supported. + /// The platform's hint namespace. + /// The typed conversion. + private static ConversionInfo Create(ITypeSymbol enumType, bool forward, string hidden, Compilation compilation, bool supportsHidden, string hintNamespace) + { + var typeName = enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var inverse = VisibilityHintExpressions.Flag(compilation, hintNamespace, "Inverse"); + var visible = forward ? "__value" : $"__value == {typeName}.Visible"; + if (inverse != "false") + { + visible = $"({visible}) != ({inverse})"; + } + + var notVisible = $"{typeName}.{hidden}"; + if (supportsHidden && !enumType.GetMembers("Hidden").IsEmpty) + { + var useHidden = VisibilityHintExpressions.Flag(compilation, hintNamespace, "UseHidden"); + notVisible = $"({useHidden}) ? {typeName}.Hidden : {notVisible}"; + } + + return new(forward ? $"({visible}) ? {typeName}.Visible : ({notVisible})" : visible, "true", Affinity); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityHintExpressions.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityHintExpressions.cs new file mode 100644 index 00000000..6ec1784d --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/VisibilityHintExpressions.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Reads supported visibility flags through the hint enum present in the consumer. +internal static class VisibilityHintExpressions +{ + /// Builds an enum-specific flag test for either runtime flavour. + /// The consumer compilation. + /// The platform's hint namespace, or empty when hints do not apply. + /// The flag member to test. + /// A typed flag test, or false when the hint contract is absent. + internal static string Flag(Compilation compilation, string hintNamespace, string member) + { + if (hintNamespace.Length == 0) + { + return "false"; + } + + var first = ForType(compilation, $"{hintNamespace}.BooleanToVisibilityHint", member, "0"); + var reactiveNamespace = hintNamespace.Replace("ReactiveUI", "ReactiveUI.Reactive"); + var second = ForType(compilation, $"{reactiveNamespace}.BooleanToVisibilityHint", member, "1"); + if (first is null) + { + return second ?? "false"; + } + + return second is null ? first : $"({first}) || ({second})"; + } + + /// Tests a flag only when the exact enum and member are accessible. + /// The consumer compilation. + /// The hint enum metadata name. + /// The flag member. + /// The discriminator for the emitted pattern variable. + /// The flag expression, or null. + private static string? ForType(Compilation compilation, string metadataName, string member, string suffix) + { + var type = compilation.GetTypeByMetadataName(metadataName); + if (type is not { TypeKind: TypeKind.Enum } || type.GetMembers(member).IsEmpty + || !compilation.IsSymbolAccessibleWithin(type, compilation.Assembly)) + { + return null; + } + + var typeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var variable = $"__{member}Hint{suffix}"; + return $"__hint is {typeName} {variable} && ({variable} & {typeName}.{member}) != 0"; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WinUIConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WinUIConversionPlugin.cs new file mode 100644 index 00000000..3936a2de --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WinUIConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts WinUI visibility to typed boolean bindings. +internal sealed class WinUIConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + VisibilityConversion.Select(source, target, "Microsoft.UI.Xaml.Visibility", "Collapsed", compilation, false, "ReactiveUI"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WpfConversionPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WpfConversionPlugin.cs new file mode 100644 index 00000000..80a391e3 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Conversion/WpfConversionPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Conversion; + +/// Adapts Wpf visibility to typed boolean bindings. +internal sealed class WpfConversionPlugin : IConversionPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConversionInfo? Select(ITypeSymbol source, ITypeSymbol target, Compilation compilation) => + VisibilityConversion.Select(source, target, "System.Windows.Visibility", "Collapsed", compilation, true, "ReactiveUI"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/ICommandBindingHelperPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ICommandBindingHelperPlugin.cs new file mode 100644 index 00000000..d0ff9e14 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ICommandBindingHelperPlugin.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins; + +/// Declares a native bridge required by a selected command mechanism. +internal interface ICommandBindingHelperPlugin : ICommandBindingPlugin +{ + /// Emits the bridge once for all call sites selecting this plugin. + /// The output builder. + void EmitHelper(StringBuilder sb); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs index e559ab47..ce9de926 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs @@ -52,6 +52,13 @@ internal interface IObservationPlugin /// bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName); + /// Scores this property for the requested notification timing; zero declines the observation. + /// The concrete owner's capabilities. + /// The observed property. + /// Whether the caller requests before-change notifications. + /// The eligible mechanism's score, or zero. + int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange); + /// /// Emits any helper class definitions needed by this plugin's generated code. /// Called at most once per generated output file, inside the @@ -60,81 +67,8 @@ internal interface IObservationPlugin /// The string builder to append to. void EmitHelperClasses(StringBuilder sb); - /// - /// Emits a shallow (single-segment) observation as an inline expression appended to sb. - /// Used for inline contexts such as a selector .Select() call. - /// - /// The string builder to append to. - /// The root variable name (e.g., "obj"). - /// The property path segment. - /// The fully qualified type name for casting. - /// True for WhenChanging (before-change). - /// Whether to include StartWith for initial value emission. - void EmitShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - bool includeStartWith); - - /// Emits a shallow (single-segment) observation as a local variable declaration. - /// The string builder to append to. - /// The root variable name (e.g., "obj"). - /// The property path segment. - /// The fully qualified type name for casting. - /// True for WhenChanging (before-change). - /// The variable name to assign the observable to. - void EmitShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string varName); - - /// Emits the root segment of a deep chain observation as a local variable declaration. - /// The string builder to append to. - /// The root variable name (e.g., "obj"). - /// The first property path segment. - /// The fully qualified type name for casting the root object. - /// True for WhenChanging (before-change). - /// The variable name for the resulting observable. - void EmitDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string obsVarName); - - /// Emits an inner segment of a deep chain observation using Select/Switch re-subscription. - /// The string builder to append to. - /// The previous segment's observable variable name. - /// The current segment's observable variable name. - /// The lambda parameter name for the parent value. - /// The current property path segment. - /// True for WhenChanging (before-change). - /// The behavior to use while the parent segment is null. - void EmitDeepChainInnerSegment( - StringBuilder sb, - string prevVar, - string curVar, - string lambdaParam, - PropertyPathSegment segment, - bool isBeforeChange, - NullParentObservationBehavior nullParentBehavior); - - /// Emits an inline observation variable for binding generators. Used by BindOneWay/BindTwoWay for direct observation code. - /// The string builder to append to. - /// The root variable name (e.g., "source", "target"). - /// The property path segment. - /// The fully qualified type name for casting. - /// The variable name for the resulting observable. - void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName); + /// Emits a direct typed expression for this mechanism. + /// The output builder. + /// The concrete property and notification timing. + void EmitObservation(StringBuilder sb, in ObservationExpression observation); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformCommandBindingPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformCommandBindingPlugin.cs new file mode 100644 index 00000000..e5fd0195 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformCommandBindingPlugin.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins; + +/// Inspects native command contracts while the consumer's symbols are available. +internal interface IPlatformCommandBindingPlugin : ICommandBindingPlugin +{ + /// Offers the native route only when its required members are available. + /// The concrete control type. + /// The native member data, or null when ineligible. + NativeCommandInfo? InspectControl(INamedTypeSymbol control); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformObservationPlugin.cs new file mode 100644 index 00000000..81814757 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IPlatformObservationPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins; + +/// Owns symbol eligibility and direct emission for one platform notification mechanism. +internal interface IPlatformObservationPlugin : IObservationPlugin +{ + /// Offers a candidate only when the consumer exposes the required native members. + /// The concrete type through which the property is accessed. + /// The property being observed. + /// Value-equatable emission data, or null when this plugin is ineligible. + PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AfterChangeObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AfterChangeObservationPlugin.cs deleted file mode 100644 index 89e03535..00000000 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AfterChangeObservationPlugin.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. -// ReactiveUI and Contributors licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; -using ReactiveUI.Binding.SourceGenerators.Models; - -namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; - -/// The base for an observation mechanism that raises only after a property has changed. -internal closed class AfterChangeObservationPlugin : IObservationPlugin -{ - /// Gets the affinity this mechanism bids with. - public abstract int Affinity { get; } - - /// - public abstract string ObservationKind { get; } - - /// - public bool SupportsBeforeChanged => false; - - /// - public abstract bool RequiresHelperClasses { get; } - - /// Gets a value indicating whether a before-change request observes the after-change stream instead of reading the value once. - protected virtual bool AnswersBeforeChangeWithLiveStream => false; - - /// - public abstract bool IsAMatch(ClassBindingInfo classInfo); - - /// - public abstract bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName); - - /// - public abstract void EmitHelperClasses(StringBuilder sb); - - /// Emits the observation of a property read directly off the object a call site named. - /// The string builder to append to. - /// The variable holding the observed object. - /// The property path segment being observed. - /// The type the observed object is cast to. - /// Whether before-change notifications are being observed. - /// Whether the observation opens with the property's current value. - public void EmitShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - bool includeStartWith) - { - if (isBeforeChange && !AnswersBeforeChangeWithLiveStream) - { - _ = UnchangingObservationEmitter.AppendExpression(sb, rootVar, segment, castTypeName); - return; - } - - AppendShallowObservation(sb, rootVar, segment, castTypeName, includeStartWith); - } - - /// Emits that same observation assigned to a local. - /// The string builder to append to. - /// The variable holding the observed object. - /// The property path segment being observed. - /// The type the observed object is cast to. - /// Whether before-change notifications are being observed. - /// The name of the local to assign. - public void EmitShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string varName) - { - if (isBeforeChange && !AnswersBeforeChangeWithLiveStream) - { - _ = UnchangingObservationEmitter.AppendVariable(sb, rootVar, segment, castTypeName, varName); - return; - } - - AppendShallowObservationVariable(sb, rootVar, segment, castTypeName, varName); - } - - /// Emits the observation the first stage of a deep chain is rooted on. - /// The string builder to append to. - /// The variable holding the observed object. - /// The first property path segment. - /// The type the observed object is cast to. - /// Whether before-change notifications are being observed. - /// The name of the observable local to assign. - public void EmitDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string obsVarName) - { - if (isBeforeChange && !AnswersBeforeChangeWithLiveStream) - { - _ = UnchangingObservationEmitter.AppendTypedVariable(sb, rootVar, segment, castTypeName, obsVarName) - .AppendLine(); - return; - } - - AppendDeepChainRootSegment(sb, rootVar, segment, castTypeName, obsVarName); - } - - /// Emits the observation of one link past the first in a deep chain. - /// The string builder to append to. - /// The variable holding the previous link's observation. - /// The name of the local this link's observation is assigned to. - /// The name the switch lambda gives the parent value. - /// The property path segment being observed. - /// Whether before-change notifications are being observed. - /// What the link observes while its parent is null. - public void EmitDeepChainInnerSegment( - StringBuilder sb, - string prevVar, - string curVar, - string lambdaParam, - PropertyPathSegment segment, - bool isBeforeChange, - NullParentObservationBehavior nullParentBehavior) - { - var segType = segment.PropertyTypeFullName; - var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault - ? $"new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{segType}>(default({segType}))" - : $"global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal<{segType}>.Instance"; - - _ = sb.AppendLine().Append(GeneratedSyntax.InlineLocalDeclaration).Append(curVar).Append(" = ") - .Append(GeneratedTypeNames.OpenChainSwitchMap(segment, segType, prevVar)).AppendLine().Append(" ").Append(lambdaParam) - .Append(" => ").Append(lambdaParam).AppendLine(" != null"); - - if (isBeforeChange && !AnswersBeforeChangeWithLiveStream) - { - _ = sb.Append(" ? (global::System.IObservable<").Append(segType); - AppendUnchangingChainSegment(sb, lambdaParam, segment); - } - else - { - ChainRegistrationEmitter.AppendChoiceOpen(sb, lambdaParam, segment, Affinity, false); - AppendChainSegmentObservation(sb, lambdaParam, segment); - _ = sb.AppendLine(")"); - } - - _ = sb.Append(" : (global::System.IObservable<").Append(segType).Append(">)") - .Append(nullParentObservable).AppendLine(");"); - } - - /// - public abstract void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName); - - /// Appends the after-change observation as a bare expression. - /// The string builder to append to. - /// The variable holding the observed object. - /// The property path segment being observed. - /// The type the observed object is cast to. - /// Whether the observation opens with the property's current value. - protected abstract void AppendShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool includeStartWith); - - /// Appends the after-change observation assigned to a local. - /// The string builder to append to. - /// The variable holding the observed object. - /// The property path segment being observed. - /// The type the observed object is cast to. - /// The name of the local to assign. - protected abstract void AppendShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName); - - /// Appends the after-change observation the first stage of a deep chain is rooted on. - /// The string builder to append to. - /// The variable holding the observed object. - /// The first property path segment. - /// The type the observed object is cast to. - /// The name of the observable local to assign. - protected abstract void AppendDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string obsVarName); - - /// Appends the after-change observation of a chain link whose parent is present. - /// The string builder to append to. - /// The name the switch lambda gives the parent value. - /// The property path segment being observed. - protected abstract void AppendChainSegmentObservation( - StringBuilder sb, - string lambdaParam, - PropertyPathSegment segment); - - /// Appends the read that stands in for a before-change observation of a chain link. - /// The string builder to append to. - /// The name the switch lambda gives the parent value. - /// The property path segment being observed. - private static void AppendUnchangingChainSegment( - StringBuilder sb, - string lambdaParam, - PropertyPathSegment segment) => - _ = sb.AppendLine(">)").Append(" new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<") - .Append(segment.PropertyTypeFullName).Append(">(((").Append(segment.DeclaringTypeFullName).Append(')').Append(lambdaParam).Append(").") - .Append(segment.PropertyName).AppendLine(")"); -} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs index c9cf1572..2e950a36 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs @@ -4,42 +4,16 @@ using System.Runtime.CompilerServices; using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; -/// -/// Observation plugin for Android View types. -/// Affinity: 5 (matches ReactiveUI's AndroidObservableForWidgets). -/// Does NOT support before-change notifications. -/// -/// -/// -/// Android View does NOT implement INotifyPropertyChanged. -/// ReactiveUI's runtime uses a static dispatch table mapping (WidgetType, PropertyName) -/// to widget-specific events (e.g., TextView.TextChanged, -/// CompoundButton.CheckedChange, RatingBar.RatingBarChange). -/// -/// -/// Currently emits ImmediateReturnSignal (returns current value, no ongoing observation) -/// as a safe fallback. This matches ReactiveUI's POCO fallback behavior for unknown -/// widget/property combinations. -/// -/// -/// Future enhancement: Build a compile-time dispatch table matching ReactiveUI's -/// AndroidObservableForWidgets to generate direct widget event subscriptions. -/// Supported mappings would include: TextView.Text → TextChanged, -/// CompoundButton.Checked → CheckedChange, NumberPicker.Value → ValueChanged, etc. -/// -/// -internal sealed class AndroidObservationPlugin : IObservationPlugin +/// Observes Android widget properties through their native typed events. +internal sealed class AndroidObservationPlugin : IPlatformObservationPlugin { - /// The affinity score for the Android View observation plugin (matches ReactiveUI's AndroidObservableForWidgets). - private static readonly int AndroidAffinity = BindingAffinity.Explicit; - /// - public int Affinity => AndroidAffinity; + public int Affinity => BindingAffinity.Explicit; /// public string ObservationKind => "Android"; @@ -48,167 +22,81 @@ internal sealed class AndroidObservationPlugin : IObservationPlugin public bool SupportsBeforeChanged => false; /// - public bool RequiresHelperClasses => false; + public bool RequiresHelperClasses => true; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsAMatch(ClassBindingInfo classInfo) => - classInfo.InheritsAndroidView; - - /// - /// - /// Only the widget properties that raise an event of their own. Everything else on an Android view changes - /// silently, so claiming it would replace a mechanism the type may genuinely carry with one that reports - /// nothing. A property the consumer declared on its own subclass is its own, not the widget's. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => - AndroidWidgetEvents.FindChangeEvent(propertyName) is not null - && !ObservedProperties.IsDeclaredByConsumer(classInfo, propertyName); - - /// - public void EmitHelperClasses(StringBuilder sb) - { - // No helper classes needed. Future: may emit event-based observable. - } + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; /// - public void EmitShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - bool includeStartWith) + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) { - var changeEvent = ChangeEventOrUnchanging(sb, rootVar, segment, castTypeName, isBeforeChange); - if (changeEvent is null) + var widget = WidgetFor(property.Name); + if (widget is null || !PlatformSymbols.DerivesFrom(property.ContainingType, widget)) { - return; + return null; } - _ = EventObservationEmitter.AppendExpression( - sb, - rootVar, - segment, - castTypeName, - changeEvent, - includeStartWith); - } - - /// - public void EmitShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string varName) - { - var changeEvent = AndroidWidgetEvents.FindChangeEvent(segment.PropertyName); - if (isBeforeChange || changeEvent is null) + var first = PlatformSymbols.FindEvent(owner, AndroidWidgetEvents.FindChangeEvent(property.Name)!); + if (first is null) { - _ = UnchangingObservationEmitter.AppendVariable(sb, rootVar, segment, castTypeName, varName); - return; + return null; } - _ = EventObservationEmitter.AppendVariable(sb, rootVar, segment, castTypeName, changeEvent, varName); - } - - /// - public void EmitDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string obsVarName) - { - var changeEvent = AndroidWidgetEvents.FindChangeEvent(segment.PropertyName); - if (isBeforeChange || changeEvent is null) + var events = new EquatableArray([first]); + if (property.Name == "SelectedItem") { - _ = UnchangingObservationEmitter.AppendTypedVariable(sb, rootVar, segment, castTypeName, obsVarName) - .AppendLine(); - return; + var second = PlatformSymbols.FindEvent(owner, "NothingSelected"); + if (second is null) + { + return null; + } + + events = new([first, second]); } - _ = sb.Append(" var ").Append(obsVarName) - .Append(" = (global::System.IObservable<").Append(segment.PropertyTypeFullName).Append(">)"); - _ = EventObservationEmitter - .AppendExpression(sb, rootVar, segment, castTypeName, changeEvent, true) - .AppendLine(";"); + return new(ObservationKind, Affinity, events, null, null, null); } /// - public void EmitDeepChainInnerSegment( - StringBuilder sb, - string prevVar, - string curVar, - string lambdaParam, - PropertyPathSegment segment, - bool isBeforeChange, - NullParentObservationBehavior nullParentBehavior) - { - var segType = segment.PropertyTypeFullName; - var declType = segment.DeclaringTypeFullName; - var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault - ? $"new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{segType}>(default({segType}))" - : $"global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal<{segType}>.Instance"; - - _ = sb.AppendLine().Append(" var ").Append(curVar).Append(" = ") - .Append(GeneratedTypeNames.OpenChainSwitchMap(segment, segType, prevVar)).AppendLine().Append(" ").Append(lambdaParam) - .Append(" => ").Append(lambdaParam).AppendLine(" != null"); - - ChainRegistrationEmitter.AppendChoiceOpen(sb, lambdaParam, segment, Affinity, isBeforeChange); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); - _ = sb.Append(" new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<").Append(segType) - .Append(">(((").Append(declType).Append(')').Append(lambdaParam).Append(").").Append(segment.PropertyName).AppendLine("))") - .Append(" : (global::System.IObservable<").Append(segType).Append(">)").Append(nullParentObservable).AppendLine(");"); - } + /// + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; /// - public void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) - { - var changeEvent = AndroidWidgetEvents.FindChangeEvent(segment.PropertyName); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__AndroidObservable"); - _ = changeEvent is null - ? UnchangingObservationEmitter.AppendVariable(sb, rootVar, segment, castTypeName, varName) - : EventObservationEmitter.AppendVariable(sb, rootVar, segment, castTypeName, changeEvent, varName); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, AppendSubscription); - _ = sb.AppendLine(); - } + /// Emits the concrete native event subscriptions and their removal. + /// The output builder. + /// The observed property. + /// The selected native mechanism. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) => + NativeEventSubscriptionEmitter.Append(sb, info.Events); - /// Resolves the event the property reports on, falling back to the unchanging observation. - /// The string builder to append to. - /// The variable the property is read from. - /// The property being observed. - /// The type the root is cast to. - /// Whether before-change notifications are being observed. - /// The event name, or once the fallback has been appended instead. - /// - /// A widget reports a change once it has happened and has nothing to say before it, so a before-change - /// observation takes the unchanging answer whatever the property is. - /// - private static string? ChangeEventOrUnchanging( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange) + /// Names the widget that defines each observable property contract. + /// The observed property. + /// The native widget type, or null. + private static string? WidgetFor(string propertyName) => propertyName switch { - var changeEvent = AndroidWidgetEvents.FindChangeEvent(segment.PropertyName); - if (!isBeforeChange && changeEvent is not null) - { - return changeEvent; - } - - _ = UnchangingObservationEmitter.AppendExpression(sb, rootVar, segment, castTypeName); - - return null; - } + "Text" => "Android.Widget.TextView", + "Value" => "Android.Widget.NumberPicker", + "Rating" => "Android.Widget.RatingBar", + "Checked" => "Android.Widget.CompoundButton", + "Date" => "Android.Widget.CalendarView", + "CurrentTab" => "Android.Widget.TabHost", + "SelectedItem" => "Android.Widget.AdapterView", + "Hour" or "Minute" or "CurrentHour" or "CurrentMinute" => "Android.Widget.TimePicker", + _ => null, + }; } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppKitObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppKitObservationPlugin.cs new file mode 100644 index 00000000..41153fd9 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppKitObservationPlugin.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Observes AppKit control values through sender-scoped text-change notifications. +internal sealed class AppKitObservationPlugin : IPlatformObservationPlugin +{ + /// The native NSControl property observation score. + private const int ControlAffinity = 20; + + /// + public int Affinity => ControlAffinity; + + /// + public string ObservationKind => "AppKit"; + + /// + public bool SupportsBeforeChanged => false; + + /// + public bool RequiresHelperClasses => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; + + /// + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) + { + if (!Reports(property.Name) || !PlatformSymbols.DerivesFrom(owner, "AppKit.NSControl")) + { + return null; + } + + var notification = AppleNotificationEmitter.ResolveNotification(owner, "TextDidChangeNotification"); + return notification is null ? null : new(ObservationKind, Affinity, default, notification, null, null); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); + + /// + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__AppKitObservable"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, AppleNotificationEmitter.AppendSubscription); + + /// Identifies the NSControl values covered by its native text notification. + /// The property name. + /// True for a supported native value. + internal static bool Reports(string name) => name is + "AlphaValue" or "DoubleValue" or "FloatValue" or "IntValue" or "NintValue" or "ObjectValue" or "StringValue" or "AttributedStringValue"; +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppleNotificationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppleNotificationEmitter.cs new file mode 100644 index 00000000..a971bf4f --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AppleNotificationEmitter.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Resolves and emits sender-scoped Foundation notification subscriptions. +internal static class AppleNotificationEmitter +{ + /// Finds the public notification constant and its native notification center. + /// The concrete property owner. + /// The framework notification constant. + /// The fully qualified constant, or null when the contract is unavailable. + internal static string? ResolveNotification(INamedTypeSymbol owner, string memberName) + { + var member = PlatformSymbols.FindMember(owner, memberName); + return member is { IsStatic: true, DeclaredAccessibility: Accessibility.Public } + && member.ContainingAssembly.GetTypeByMetadataName("Foundation.NSNotificationCenter") is not null + ? $"{member.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{memberName}" + : null; + } + + /// Attaches a sender-filtered notification and releases both its registration and token. + /// The output builder. + /// The property being observed. + /// The selected notification constant. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) => + sb.Append(" var __token = global::Foundation.NSNotificationCenter.DefaultCenter.AddObserver(") + .Append(info.NotificationName).AppendLine(", __notification => __notify(), __source);") + .AppendLine(" return new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() =>") + .AppendLine(" {") + .AppendLine(" global::Foundation.NSNotificationCenter.DefaultCenter.RemoveObserver(__token);") + .AppendLine(" __token.Dispose();") + .AppendLine(" });"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs index e8d752a2..e6c7e806 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs @@ -47,15 +47,21 @@ internal static void AppendChoiceOpen( { var declaringType = segment.DeclaringTypeFullName; var valueType = segment.PropertyTypeFullName; + var registration = $"__registration{sb.Length}"; - _ = sb.Append(opening).Append("global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose<") - .Append(valueType).AppendLine(">(") + _ = sb.Append(opening).Append("(global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(") + .Append(sourceExpression).Append(".GetType(), \"").Append(segment.PropertyName).Append("\", ") + .Append(generatedAffinity).Append(", ").Append(isBeforeChange ? "true" : "false") + .Append(") is global::ReactiveUI.Binding.ICreatesObservableForProperty ").Append(registration).AppendLine() + .Append(argumentIndent).Append("? (global::System.IObservable<").Append(valueType) + .Append(">)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable<").Append(valueType).AppendLine(">(") + .Append(argumentIndent).Append(registration).AppendLine(",") .Append(argumentIndent).Append(sourceExpression).AppendLine(",") .Append(argumentIndent).Append("((global::System.Linq.Expressions.Expression>)(__e => __e.").Append(segment.PropertyName).AppendLine(")).Body,") .Append(argumentIndent).Append('"').Append(segment.PropertyName).AppendLine("\",") - .Append(argumentIndent).Append(isBeforeChange ? "true" : "false").AppendLine(",") - .Append(argumentIndent).Append(generatedAffinity).AppendLine(",") - .Append(argumentIndent).Append("(object __o) => ((").Append(declaringType).Append(")__o).").Append(segment.PropertyName).AppendLine(","); + .Append(argumentIndent).Append("(object __o) => ((").Append(declaringType).Append(")__o).").Append(segment.PropertyName).AppendLine(",") + .Append(argumentIndent).Append(isBeforeChange ? "true" : "false").AppendLine(", false)") + .Append(argumentIndent).Append(": (global::System.IObservable<").Append(valueType).AppendLine(">)"); } } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/DependencyPropertyObservationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/DependencyPropertyObservationEmitter.cs new file mode 100644 index 00000000..4d9bbf7b --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/DependencyPropertyObservationEmitter.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Shares WinUI-compatible dependency-property symbol checks and callback-token emission. +internal static class DependencyPropertyObservationEmitter +{ + /// Offers a native candidate only for a property with a concrete dependency-property member. + /// The concrete property owner. + /// The observed CLR property. + /// The platform mechanism identity. + /// The namespace declaring native dependency objects. + /// The eligible candidate, or null. + internal static PlatformObservationInfo? Inspect(INamedTypeSymbol owner, IPropertySymbol property, string kind, string frameworkNamespace) + { + var dependencyObject = $"{frameworkNamespace}.DependencyObject"; + return PlatformSymbols.DerivesFrom(owner, dependencyObject) + && PlatformSymbols.HasDependencyProperty(owner, property.Name, $"{frameworkNamespace}.DependencyProperty") + ? new(kind, BindingAffinity.WinUiDependencyObject, default, null, $"global::{dependencyObject}", null) + : null; + } + + /// Pairs a native callback token with deterministic unregistration. + /// The output builder. + /// The concrete observed property. + /// The verified native mechanism. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) => + sb.Append(" var __token = __source.RegisterPropertyChangedCallback(") + .Append(segment.DeclaringTypeFullName).Append('.').Append(segment.PropertyName) + .AppendLine("Property, (__sender, __property) => __notify());") + .AppendLine(" return new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() =>") + .Append(" __source.UnregisterPropertyChangedCallback(").Append(segment.DeclaringTypeFullName) + .Append('.').Append(segment.PropertyName).AppendLine("Property, __token));"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs index 3f383335..9efc734e 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs @@ -2,6 +2,8 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Runtime.CompilerServices; +using System.Text; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; @@ -10,15 +12,47 @@ namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; /// Observation plugin for types implementing . /// Supports both after-change and before-change (if the type also implements INotifyPropertyChanging). /// -internal sealed class INPCObservationPlugin : NotifyPropertyObservationPlugin +internal sealed class INPCObservationPlugin : IObservationPlugin { /// - public override int Affinity => BindingAffinity.Explicit; + public int Affinity => BindingAffinity.Explicit; /// - public override string ObservationKind => "INPC"; + public string ObservationKind => "INPC"; /// - public override bool IsAMatch(ClassBindingInfo classInfo) => + public bool SupportsBeforeChanged => true; + + /// + public bool RequiresHelperClasses => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) + { + var supported = isBeforeChange ? classInfo.ImplementsINPChanging : classInfo.ImplementsINPC; + return supported ? Affinity : 0; + } + + /// + public bool IsAMatch(ClassBindingInfo classInfo) => classInfo.ImplementsINPC && !classInfo.ImplementsIReactiveObject; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => true; + + /// + public void EmitHelperClasses(StringBuilder sb) {} + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NotifyPropertyEmitter.EmitShallowObservation( + sb, + observation.Source, + observation.Segment, + observation.SourceType, + observation.BeforeChange, + observation.Distinct); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs index 16939ffc..50712f3f 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; @@ -29,7 +29,7 @@ namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; /// for the subscription lifetime. /// /// -internal sealed class KVOObservationPlugin : IObservationPlugin +internal sealed class KVOObservationPlugin : IPlatformObservationPlugin { /// /// The affinity score for the Apple KVO observation plugin @@ -49,6 +49,11 @@ internal sealed class KVOObservationPlugin : IObservationPlugin /// public bool RequiresHelperClasses => true; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + IsAMatch(classInfo) && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsAMatch(ClassBindingInfo classInfo) => @@ -63,292 +68,70 @@ public bool IsAMatch(ClassBindingInfo classInfo) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => - !ObservedProperties.IsDeclaredByConsumer(classInfo, propertyName); + ObservedProperties.Find(classInfo, propertyName) is { SymbolsInspected: true } + ? PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null + : !ObservedProperties.IsDeclaredByConsumer(classInfo, propertyName); /// - public void EmitHelperClasses(StringBuilder sb) + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) { - EmitObserverClass(sb); - EmitObservableClass(sb); - } + if (!PlatformSymbols.DerivesFrom(owner, "Foundation.NSObject")) + { + return null; + } - /// - public void EmitShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - bool includeStartWith) - { - var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName); - _ = sb.Append("new __KVOObservable<").Append(segment.PropertyTypeFullName).Append(">(").Append("(global::Foundation.NSObject)") - .Append(rootVar).Append(", ").Append('"').Append(keyPath).Append("\", ").Append("(global::Foundation.NSObject __o) => ((") - .Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName).Append(", ").Append(BoolLiteral(includeStartWith)).Append(", ") - .Append(BoolLiteral(isBeforeChange)).Append(')'); - } + var selector = property.GetMethod is { } getter ? ExportedSelector(getter) : null; + selector ??= ExportedSelector(property); + if (selector is null && IsNativeDeclaration(owner, property)) + { + selector = KvoObservationEmitter.ToKvoKeyPath(property.Name, property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + } - /// - public void EmitShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string varName) - { - var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName); - _ = sb.Append(" var ").Append(varName).Append(" = new __KVOObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Foundation.NSObject)").Append(rootVar).AppendLine(",").Append(" \"").Append(keyPath) - .AppendLine("\",").Append(" (global::Foundation.NSObject __o) => ((").Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose) - .Append(segment.PropertyName).AppendLine(",").AppendLine(" true,").Append(" ") - .Append(BoolLiteral(isBeforeChange)).Append(");"); + return selector is null ? null : new(ObservationKind, Affinity, default, null, null, selector); } /// - public void EmitDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string obsVarName) - { - var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName); - _ = sb.Append(" var ").Append(obsVarName).Append(" = (global::System.IObservable<").Append(segment.PropertyTypeFullName) - .Append(">)new __KVOObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Foundation.NSObject)").Append(rootVar).AppendLine(",").Append(" \"").Append(keyPath) - .AppendLine("\",").Append(" (global::Foundation.NSObject __o) => ((").Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose) - .Append(segment.PropertyName).AppendLine(",").AppendLine(" false,").Append(" ") - .Append(BoolLiteral(isBeforeChange)).AppendLine(");"); - } - - /// - public void EmitDeepChainInnerSegment( - StringBuilder sb, - string prevVar, - string curVar, - string lambdaParam, - PropertyPathSegment segment, - bool isBeforeChange, - NullParentObservationBehavior nullParentBehavior) - { - var segType = segment.PropertyTypeFullName; - var declType = segment.DeclaringTypeFullName; - var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName); - var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault - ? $"new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{segType}>(default({segType}))" - : $"global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal<{segType}>.Instance"; - - _ = sb.AppendLine().Append(" var ").Append(curVar).Append(" = ") - .Append(GeneratedTypeNames.OpenChainSwitchMap(segment, segType, prevVar)).AppendLine().Append(" ").Append(lambdaParam) - .Append(" => ").Append(lambdaParam).AppendLine(" != null"); - - ChainRegistrationEmitter.AppendChoiceOpen(sb, lambdaParam, segment, Affinity, isBeforeChange); - - _ = sb.Append(" new __KVOObservable<").Append(segType).AppendLine(">(").Append(" (global::Foundation.NSObject)") - .Append(lambdaParam).AppendLine(",").Append(" \"").Append(keyPath).AppendLine("\",") - .Append(" (global::Foundation.NSObject __o) => ((").Append(declType).Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName) - .AppendLine(",").AppendLine(" false,").Append(" ").Append(BoolLiteral(isBeforeChange)).AppendLine("))") - .Append(" : (global::System.IObservable<").Append(segType).Append(">)").Append(nullParentObservable).AppendLine(");"); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => KvoObservationEmitter.EmitHelperClasses(sb); /// - public void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) - { - var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName); - _ = sb.Append(" var ").Append(varName).Append(" = new __KVOObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Foundation.NSObject)").Append(rootVar).AppendLine(",").Append(" \"").Append(keyPath) - .AppendLine("\",").Append(" (global::Foundation.NSObject __o) => ((").Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose) - .Append(segment.PropertyName).AppendLine(",").AppendLine(" true,").AppendLine(" false);"); - } - - /// Renders a boolean as the lowercase C# literal text (true/false) for emission into generated source. - /// The boolean value. - /// "true" or "false". - private static string BoolLiteral(bool value) => value ? "true" : "false"; - - /// - /// Converts a .NET property name to a KVO key path using the standard naming convention. - /// Boolean properties get an "Is" prefix unless they already start with "Is" - /// (e.g., Enabled"isEnabled", but IsEnabled"isEnabled"). - /// All others: lowercase first character (e.g., Text"text"). - /// - /// The .NET property name. - /// The fully qualified property type (e.g., "bool", "string"). - /// The KVO key path string. - private static string ToKvoKeyPath(string propertyName, string propertyTypeFullName) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + KvoObservationEmitter.Emit(sb, observation.Source, observation.Segment, observation.SourceType, observation.BeforeChange, observation.Distinct); + + /// Recognizes properties declared by the assembly supplying NSObject. + /// The concrete observed type. + /// The selected property declaration. + /// True when the declaration belongs to the native framework. + internal static bool IsNativeDeclaration(INamedTypeSymbol owner, IPropertySymbol property) { - if (propertyTypeFullName == "bool" && !propertyName.StartsWith("Is", StringComparison.Ordinal)) + for (var current = owner; current is not null; current = current.BaseType) { - propertyName = $"Is{propertyName}"; + if (current.ToDisplayString() == "Foundation.NSObject") + { + return SymbolEqualityComparer.Default.Equals(property.ContainingAssembly, current.ContainingAssembly); + } } - return propertyName.Length == 0 ? propertyName : char.ToLowerInvariant(propertyName[0]) + propertyName[1..]; + return false; } - /// Emits the __KVOObserver NSObject subclass that forwards ObserveValue callbacks. - /// The string builder. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitObserverClass(StringBuilder sb) => - sb.AppendLine(""" - - /// - /// NSObject subclass that receives KVO ObserveValue callbacks and forwards - /// them to a delegate. Mirrors ReactiveUI's BlockObserveValueDelegate pattern. - /// - private sealed class __KVOObserver : global::Foundation.NSObject - { - private readonly global::System.Action _callback; - - internal __KVOObserver(global::System.Action callback) - { - _callback = callback; - } - - public override void ObserveValue( - global::Foundation.NSString keyPath, - global::Foundation.NSObject ofObject, - global::Foundation.NSDictionary change, - global::System.IntPtr context) - { - _callback(); - } - } - """); - - /// Emits the __KVOObservable<T> fused observable that wraps KVO add/remove observer calls. - /// The string builder. - private static void EmitObservableClass(StringBuilder sb) + /// Reads a Foundation export selector without loading the platform assembly. + /// The property or getter carrying export metadata. + /// The getter selector, or null. + private static string? ExportedSelector(ISymbol symbol) { - _ = sb.AppendLine(""" - - /// - /// Fused observable for Apple KVO property observation. - /// Uses NSObject.AddObserver / NSObject.RemoveObserver - /// with a compile-time resolved KVO key path. - /// - private sealed class __KVOObservable : global::System.IObservable - { - private readonly global::Foundation.NSObject _source; - private readonly global::Foundation.NSString _keyPath; - private readonly global::System.Func _getter; - private readonly bool _distinctUntilChanged; - private readonly global::Foundation.NSKeyValueObservingOptions _options; - - internal __KVOObservable( - global::Foundation.NSObject source, - string keyPath, - global::System.Func getter, - bool distinctUntilChanged, - bool beforeChange) - { - _source = source; - _keyPath = (global::Foundation.NSString)keyPath; - _getter = getter; - _distinctUntilChanged = distinctUntilChanged; - _options = beforeChange - ? global::Foundation.NSKeyValueObservingOptions.Old - : global::Foundation.NSKeyValueObservingOptions.New; - } - - public global::System.IDisposable Subscribe(global::System.IObserver observer) - { - return new Subscription(this, observer); - } - """); - - EmitSubscriptionClass(sb); - } + foreach (var attribute in symbol.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() == "Foundation.ExportAttribute" + && !attribute.ConstructorArguments.IsEmpty + && attribute.ConstructorArguments[0].Value is string selector + && selector.Length > 0 && selector.IndexOf(':') < 0) + { + return selector; + } + } - /// Emits the nested Subscription type of __KVOObservable<T> and the closing brace of the observable. - /// The string builder. - private static void EmitSubscriptionClass(StringBuilder sb) - { - EmitSubscriptionClassHead(sb); - EmitSubscriptionClassCallbacks(sb); + return null; } - - /// Emits the subscription's fields and constructor, which registers the KVO observer. - /// The string builder to append to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitSubscriptionClassHead(StringBuilder sb) => - sb.AppendLine(""" - - private sealed class Subscription : global::System.IDisposable - { - private readonly __KVOObservable _parent; - private readonly __KVOObserver _kvoObserver; - private readonly global::System.Runtime.InteropServices.GCHandle _handle; - private readonly global::System.Collections.Generic.IEqualityComparer _comparer; - private global::System.IObserver _observer; - private T _lastValue; - private bool _hasValue; - - internal Subscription(__KVOObservable parent, global::System.IObserver observer) - { - _parent = parent; - _observer = observer; - _comparer = global::System.Collections.Generic.EqualityComparer.Default; - - _kvoObserver = new __KVOObserver(OnValueChanged); - _handle = global::System.Runtime.InteropServices.GCHandle.Alloc(_kvoObserver); - - parent._source.AddObserver( - _kvoObserver, - parent._keyPath, - parent._options, - global::System.IntPtr.Zero); - - // Emit initial value - var initial = parent._getter(parent._source); - _lastValue = initial; - _hasValue = true; - observer.OnNext(initial); - } - """); - - /// Emits the subscription's value-changed callback and disposal. - /// The string builder to append to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitSubscriptionClassCallbacks(StringBuilder sb) => - sb.AppendLine(""" - - private void OnValueChanged() - { - var obs = System.Threading.Volatile.Read(ref _observer); - if (obs == null) - { - return; - } - - var value = _parent._getter(_parent._source); - - if (_parent._distinctUntilChanged && _hasValue && _comparer.Equals(value, _lastValue)) - { - return; - } - - _lastValue = value; - _hasValue = true; - obs.OnNext(value); - } - - public void Dispose() - { - var obs = System.Threading.Interlocked.Exchange(ref _observer, null); - if (obs != null) - { - _parent._source.RemoveObserver(_kvoObserver, _parent._keyPath); - _handle.Free(); - } - } - } - } - """); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KvoObservationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KvoObservationEmitter.cs new file mode 100644 index 00000000..1a04d9ea --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KvoObservationEmitter.cs @@ -0,0 +1,236 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Emits Apple KVO subscriptions and their typed value delivery. +internal static class KvoObservationEmitter +{ + /// Declares the native subscription helpers used by this mechanism. + /// The output builder. + internal static void EmitHelperClasses(StringBuilder sb) + { + EmitObserverClass(sb); + EmitObservableClass(sb); + } + + /// Emits a typed native property observation. + /// The output builder. + /// The observed source variable. + /// The observed property. + /// The concrete source type. + /// Whether to observe before the change. + /// Whether equal consecutive values are suppressed. + internal static void Emit( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + bool isBeforeChange, + bool includeStartWith) + { + var keyPath = ResolveKeyPath(segment); + _ = sb.Append("new __KVOObservable<").Append(segment.PropertyTypeFullName).Append(">(").Append("(global::Foundation.NSObject)") + .Append(rootVar).Append(", ").Append('"').Append(keyPath).Append("\", ").Append("(global::Foundation.NSObject __o) => ((") + .Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName).Append(", ").Append(BoolLiteral(includeStartWith)).Append(", ") + .Append(BoolLiteral(isBeforeChange)).Append(')'); + } + + /// + /// Converts a .NET property name to a KVO key path using the standard naming convention. + /// Boolean properties get an "Is" prefix unless they already start with "Is" + /// (e.g., Enabled"isEnabled", but IsEnabled"isEnabled"). + /// All others: lowercase first character (e.g., Text"text"). + /// + /// The .NET property name. + /// The fully qualified property type (e.g., "bool", "string"). + /// The KVO key path string. + internal static string ToKvoKeyPath(string propertyName, string propertyTypeFullName) + { + if (propertyTypeFullName == "bool" && !propertyName.StartsWith("Is", StringComparison.Ordinal)) + { + propertyName = $"Is{propertyName}"; + } + + return propertyName.Length == 0 ? propertyName : char.ToLowerInvariant(propertyName[0]) + propertyName[1..]; + } + + /// Renders a boolean as the lowercase C# literal text (true/false) for emission into generated source. + /// The boolean value. + /// "true" or "false". + private static string BoolLiteral(bool value) => value ? "true" : "false"; + + /// Uses the exported getter selector verified by symbol inspection. + /// The property to observe. + /// The native key path, escaped for a generated string literal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string ResolveKeyPath(PropertyPathSegment segment) => + CodeGeneratorHelpers.EscapeString(PlatformSymbols.Candidate(segment.DeclaringTypeInfo, segment.PropertyName, "KVO")?.KvoKeyPath + ?? ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName)); + + /// Emits the __KVOObserver NSObject subclass that forwards ObserveValue callbacks. + /// The string builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EmitObserverClass(StringBuilder sb) => + sb.AppendLine(""" + + /// + /// NSObject subclass that receives KVO ObserveValue callbacks and forwards + /// them to a delegate. Mirrors ReactiveUI's BlockObserveValueDelegate pattern. + /// + private sealed class __KVOObserver : global::Foundation.NSObject + { + private readonly global::System.Action _callback; + + internal __KVOObserver(global::System.Action callback) + { + _callback = callback; + } + + public override void ObserveValue( + global::Foundation.NSString keyPath, + global::Foundation.NSObject ofObject, + global::Foundation.NSDictionary change, + global::System.IntPtr context) + { + _callback(); + } + } + """); + + /// Emits the __KVOObservable<T> fused observable that wraps KVO add/remove observer calls. + /// The string builder. + private static void EmitObservableClass(StringBuilder sb) + { + _ = sb.AppendLine(""" + + /// + /// Fused observable for Apple KVO property observation. + /// Uses NSObject.AddObserver / NSObject.RemoveObserver + /// with a compile-time resolved KVO key path. + /// + private sealed class __KVOObservable : global::System.IObservable + { + private readonly global::Foundation.NSObject _source; + private readonly global::Foundation.NSString _keyPath; + private readonly global::System.Func _getter; + private readonly bool _distinctUntilChanged; + private readonly global::Foundation.NSKeyValueObservingOptions _options; + + internal __KVOObservable( + global::Foundation.NSObject source, + string keyPath, + global::System.Func getter, + bool distinctUntilChanged, + bool beforeChange) + { + _source = source; + _keyPath = (global::Foundation.NSString)keyPath; + _getter = getter; + _distinctUntilChanged = distinctUntilChanged; + _options = beforeChange + ? global::Foundation.NSKeyValueObservingOptions.Old + : global::Foundation.NSKeyValueObservingOptions.New; + } + + public global::System.IDisposable Subscribe(global::System.IObserver observer) + { + return new Subscription(this, observer); + } + """); + + EmitSubscriptionClass(sb); + } + + /// Emits the nested Subscription type of __KVOObservable<T> and the closing brace of the observable. + /// The string builder. + private static void EmitSubscriptionClass(StringBuilder sb) + { + EmitSubscriptionClassHead(sb); + EmitSubscriptionClassCallbacks(sb); + } + + /// Emits the subscription's fields and constructor, which registers the KVO observer. + /// The string builder to append to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EmitSubscriptionClassHead(StringBuilder sb) => + sb.AppendLine(""" + + private sealed class Subscription : global::System.IDisposable + { + private readonly __KVOObservable _parent; + private readonly __KVOObserver _kvoObserver; + private readonly global::System.Runtime.InteropServices.GCHandle _handle; + private readonly global::System.Collections.Generic.IEqualityComparer _comparer; + private global::System.IObserver _observer; + private T _lastValue; + private bool _hasValue; + + internal Subscription(__KVOObservable parent, global::System.IObserver observer) + { + _parent = parent; + _observer = observer; + _comparer = global::System.Collections.Generic.EqualityComparer.Default; + + _kvoObserver = new __KVOObserver(OnValueChanged); + _handle = global::System.Runtime.InteropServices.GCHandle.Alloc(_kvoObserver); + + parent._source.AddObserver( + _kvoObserver, + parent._keyPath, + parent._options, + global::System.IntPtr.Zero); + + // Emit initial value + var initial = parent._getter(parent._source); + _lastValue = initial; + _hasValue = true; + observer.OnNext(initial); + } + """); + + /// Emits the subscription's value-changed callback and disposal. + /// The string builder to append to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EmitSubscriptionClassCallbacks(StringBuilder sb) => + sb.AppendLine(""" + + private void OnValueChanged() + { + var obs = System.Threading.Volatile.Read(ref _observer); + if (obs == null) + { + return; + } + + var value = _parent._getter(_parent._source); + + if (_parent._distinctUntilChanged && _hasValue && _comparer.Equals(value, _lastValue)) + { + return; + } + + _lastValue = value; + _hasValue = true; + obs.OnNext(value); + } + + public void Dispose() + { + var obs = System.Threading.Interlocked.Exchange(ref _observer, null); + if (obs != null) + { + _parent._source.RemoveObserver(_kvoObserver, _parent._keyPath); + _handle.Free(); + } + } + } + } + """); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeEventSubscriptionEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeEventSubscriptionEmitter.cs new file mode 100644 index 00000000..7b2b9079 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeEventSubscriptionEmitter.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Emits concrete native event delegates and their matching removal. +internal static class NativeEventSubscriptionEmitter +{ + /// Attaches every event required by the selected property contract. + /// The output builder. + /// The verified event names and delegate types. + internal static void Append(StringBuilder sb, EquatableArray events) + { + for (var i = 0; i < events.Length; i++) + { + _ = sb.Append(" ").Append(events[i].HandlerType).Append(" __handler").Append(i) + .AppendLine(" = (__sender, __args) => __notify();") + .Append(" __source.").Append(events[i].Name).Append(" += __handler").Append(i).AppendLine(";"); + } + + _ = sb.AppendLine(" return new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() =>") + .AppendLine(" {"); + for (var i = 0; i < events.Length; i++) + { + _ = sb.Append(" __source.").Append(events[i].Name).Append(" -= __handler").Append(i).AppendLine(";"); + } + + _ = sb.AppendLine(" });"); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservableEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservableEmitter.cs new file mode 100644 index 00000000..ef2bf75a --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservableEmitter.cs @@ -0,0 +1,97 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Emits typed subscription storage shared by native notification mechanisms. +internal static class NativeObservableEmitter +{ + /// Opens a generated member body. + private const string MemberOpen = " {"; + + /// Declares a helper that keeps source and value types concrete. + /// The output builder. + /// The plugin's helper type name. + internal static void EmitHelper(StringBuilder sb, string name) + { + _ = sb.Append(" private sealed class ").Append(name).AppendLine(" : global::System.IObservable") + .AppendLine(" {") + .AppendLine(" private readonly TSource _source;") + .AppendLine(" private readonly global::System.Func _subscribe;") + .AppendLine(" private readonly global::System.Func _getter;") + .AppendLine(" private readonly bool _distinct;") + .Append(" internal ").Append(name).AppendLine("(TSource source,") + .AppendLine(" global::System.Func subscribe,") + .AppendLine(" global::System.Func getter, bool distinct)") + .AppendLine(MemberOpen) + .AppendLine(" _source = source;") + .AppendLine(" _subscribe = subscribe;") + .AppendLine(" _getter = getter;") + .AppendLine(" _distinct = distinct;") + .AppendLine(" }") + .AppendLine(" public global::System.IDisposable Subscribe(global::System.IObserver observer)") + .AppendLine(MemberOpen) + .AppendLine(" if (observer == null) throw new global::System.ArgumentNullException(nameof(observer));") + .AppendLine(" return new Subscription(this, observer);") + .AppendLine(" }") + .AppendLine(" private sealed class Subscription : global::System.IDisposable") + .AppendLine(MemberOpen) + .Append(" private readonly ").Append(name).AppendLine(" _parent;") + .AppendLine(" private readonly global::System.IDisposable _inner;") + .AppendLine(" private global::System.IObserver _observer;") + .AppendLine(" private TValue _lastValue;") + .AppendLine(" private bool _hasValue;") + .Append(" internal Subscription(").Append(name).AppendLine(" parent, global::System.IObserver observer)") + .AppendLine(" {") + .AppendLine(" _parent = parent;") + .AppendLine(" _observer = observer;") + .AppendLine(" _inner = parent._subscribe(parent._source, Publish);") + .AppendLine(" try") + .AppendLine(" {") + .AppendLine(" Publish();") + .AppendLine(" }") + .AppendLine(" catch") + .AppendLine(" {") + .AppendLine(" Dispose();") + .AppendLine(" throw;") + .AppendLine(" }") + .AppendLine(" }"); + AppendDelivery(sb); + } + + /// Emits value delivery and idempotent native detachment. + /// The output builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendDelivery(StringBuilder sb) => + sb.AppendLine(""" + public void Dispose() + { + if (global::System.Threading.Interlocked.Exchange(ref _observer, null) != null) + { + _inner.Dispose(); + } + } + private void Publish() + { + var observer = global::System.Threading.Volatile.Read(ref _observer); + if (observer == null) + { + return; + } + var value = _parent._getter(_parent._source); + if (_parent._distinct && _hasValue && global::System.Collections.Generic.EqualityComparer.Default.Equals(_lastValue, value)) + { + return; + } + _lastValue = value; + _hasValue = true; + observer.OnNext(value); + } + } + } + """); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservationEmitter.cs new file mode 100644 index 00000000..df00ff90 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NativeObservationEmitter.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Emits a typed observation around a platform's native attachment statements. +internal static class NativeObservationEmitter +{ + /// Combines a concrete getter with a selected native subscription. + /// The output builder. + /// The typed source and property. + /// The selected mechanism's identity. + /// The mechanism's static subscription emitter. + internal static void Emit( + StringBuilder sb, + in ObservationExpression observation, + string kind, + Action emitSubscription) + { + var segment = observation.Segment; + var info = PlatformSymbols.Candidate(segment.DeclaringTypeInfo, segment.PropertyName, kind); + if (observation.BeforeChange || info is null) + { + _ = UnchangingObservationEmitter.AppendExpression(sb, observation.Source, segment, observation.SourceType); + return; + } + + _ = sb.Append("new __").Append(kind).Append("Observable<").Append(observation.SourceType).Append(", ") + .Append(segment.PropertyTypeFullName).Append(">((").Append(observation.SourceType).Append(')').Append(observation.Source) + .AppendLine(", (__source, __notify) =>").AppendLine(" {"); + emitSubscription(sb, segment, info); + _ = sb.Append(" }, __source => __source.").Append(segment.PropertyName) + .Append(", ").Append(observation.Distinct ? "true" : "false").Append(')'); + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyEmitter.cs index e4b1f192..aa0108cb 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyEmitter.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyEmitter.cs @@ -66,19 +66,19 @@ internal static void EmitShallowObservation( bool isBeforeChange, bool includeStartWith) { + var read = GeneratedTypeNames.ReadProperty(segment, castTypeName, "__o"); if (isBeforeChange) { _ = sb.Append("new global::ReactiveUI.Binding.Observables.PropertyChangingObservable<").Append(segment.PropertyTypeFullName).Append(">((") .Append("global::System.ComponentModel.INotifyPropertyChanging)").Append(rootVar).Append(", \"").Append(segment.PropertyName) - .Append("\", (").Append("global::System.ComponentModel.INotifyPropertyChanging __o) => ((").Append(castTypeName) - .Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName).Append(')'); + .Append("\", (").Append("global::System.ComponentModel.INotifyPropertyChanging __o) => ") + .Append(read).Append(')'); return; } _ = sb.Append("new global::ReactiveUI.Binding.Observables.PropertyObservable<").Append(segment.PropertyTypeFullName).Append(">(") .Append(rootVar).Append(", \"").Append(segment.PropertyName).Append("\", (") - .Append("global::System.ComponentModel.INotifyPropertyChanged __o) => ((").Append(castTypeName) - .Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName) + .Append("global::System.ComponentModel.INotifyPropertyChanged __o) => ").Append(read) .Append(", ").Append(includeStartWith ? "true" : "false").Append(')'); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyObservationPlugin.cs deleted file mode 100644 index 48974695..00000000 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/NotifyPropertyObservationPlugin.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. -// ReactiveUI and Contributors licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Runtime.CompilerServices; -using System.Text; -using ReactiveUI.Binding.SourceGenerators.Models; - -namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; - -/// The base for observation plugins that watch a type through its property change notifications. -internal closed class NotifyPropertyObservationPlugin : IObservationPlugin -{ - /// - public abstract int Affinity { get; } - - /// - public abstract string ObservationKind { get; } - - /// - public bool SupportsBeforeChanged => true; - - /// - public bool RequiresHelperClasses => false; - - /// - public abstract bool IsAMatch(ClassBindingInfo classInfo); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => true; - - /// - public void EmitHelperClasses(StringBuilder sb) - { - // Nothing to declare - the observables come from the runtime library. - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - bool includeStartWith) => - NotifyPropertyEmitter.EmitShallowObservation(sb, rootVar, segment, castTypeName, isBeforeChange, includeStartWith); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string varName) => - NotifyPropertyEmitter.EmitShallowObservationVariable(sb, rootVar, segment, castTypeName, isBeforeChange, varName); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool isBeforeChange, - string obsVarName) => - NotifyPropertyEmitter.EmitDeepChainRootSegment(sb, rootVar, segment, castTypeName, isBeforeChange, obsVarName); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitDeepChainInnerSegment( - StringBuilder sb, - string prevVar, - string curVar, - string lambdaParam, - PropertyPathSegment segment, - bool isBeforeChange, - NullParentObservationBehavior nullParentBehavior) => - NotifyPropertyEmitter.EmitDeepChainInnerSegment(sb, new(prevVar, curVar, lambdaParam), segment, isBeforeChange, nullParentBehavior, Affinity); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - NotifyPropertyEmitter.EmitInlineObservationVariable(sb, rootVar, segment, castTypeName, varName); -} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ObservationEmissionExtensions.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ObservationEmissionExtensions.cs new file mode 100644 index 00000000..e1f4cc6b --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ObservationEmissionExtensions.cs @@ -0,0 +1,124 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Composes property expressions into locals and typed observation chains. +internal static class ObservationEmissionExtensions +{ + /// Composes the selected mechanism into the surrounding observation. + /// The selected property observation mechanism. + extension(IObservationPlugin plugin) + { + /// Emits one property expression with the requested notification timing. + /// The output builder. + /// The source variable. + /// The observed property. + /// The concrete source type. + /// Whether to observe before the change. + /// Whether equal consecutive values are suppressed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void EmitShallowObservation( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + bool isBeforeChange, + bool includeStartWith) => + plugin.EmitObservation(sb, new(rootVar, segment, castTypeName, isBeforeChange, includeStartWith)); + + /// Assigns a typed observation to a local. + /// The output builder. + /// The source variable. + /// The observed property. + /// The concrete source type. + /// Whether to observe before the change. + /// The resulting observable local. + internal void EmitShallowObservationVariable( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + bool isBeforeChange, + string varName) + { + _ = sb.Append(GeneratedSyntax.BodyLocalDeclaration).Append(varName).Append(" = "); + plugin.EmitShallowObservation(sb, rootVar, segment, castTypeName, isBeforeChange, true); + _ = sb.AppendLine(";"); + } + + /// Emits the root observation that owns a property chain. + /// The output builder. + /// The source variable. + /// The observed property. + /// The concrete source type. + /// Whether to observe before the change. + /// The resulting observable local. + internal void EmitDeepChainRootSegment( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + bool isBeforeChange, + string obsVarName) + { + _ = sb.Append(GeneratedSyntax.BodyLocalDeclaration).Append(obsVarName).Append(" = "); + plugin.EmitShallowObservation(sb, rootVar, segment, castTypeName, isBeforeChange, false); + _ = sb.AppendLine(";"); + } + + /// Emits an after-change observation used by a binding. + /// The output builder. + /// The source variable. + /// The observed property. + /// The concrete source type. + /// The resulting observable local. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void EmitInlineObservationVariable( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + string varName) => + plugin.EmitShallowObservationVariable(sb, rootVar, segment, castTypeName, false, varName); + + /// Re-subscribes a property whenever its parent changes, preserving custom-provider voting. + /// The output builder. + /// The chain's observable locals and parent parameter. + /// The observed property. + /// Whether to observe before the change. + /// The delivery behavior while the parent is null. + internal void EmitDeepChainInnerSegment( + StringBuilder sb, + ChainStageVariables stage, + PropertyPathSegment segment, + bool isBeforeChange, + NullParentObservationBehavior nullParentBehavior) + { + var valueType = segment.PropertyTypeFullName; + _ = sb.AppendLine().Append(GeneratedSyntax.BodyLocalDeclaration).Append(stage.CurrentObservable).Append(" = ") + .Append(GeneratedTypeNames.OpenChainSwitchMap(segment, valueType, stage.PreviousObservable)).AppendLine() + .Append(" ").Append(stage.ParentParameter).Append(" => ").Append(stage.ParentParameter).AppendLine(" != null"); + ChainRegistrationEmitter.AppendChoiceOpen(sb, stage.ParentParameter, segment, plugin.Affinity, isBeforeChange); + plugin.EmitShallowObservation(sb, stage.ParentParameter, segment, segment.DeclaringTypeFullName, isBeforeChange, false); + _ = sb.AppendLine(")").Append(" : (global::System.IObservable<").Append(valueType).Append(">)"); + if (nullParentBehavior == NullParentObservationBehavior.EmitDefault) + { + _ = sb.Append("new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<").Append(valueType) + .Append(">(default(").Append(valueType).Append("))"); + } + else + { + _ = sb.Append("global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal<").Append(valueType).Append(">.Instance"); + } + + _ = sb.AppendLine(");"); + } + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PlatformSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PlatformSymbols.cs new file mode 100644 index 00000000..f31b103e --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PlatformSymbols.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Provides symbol navigation shared by platform plugins. +internal static class PlatformSymbols +{ + /// The sender and event-arguments parameters of a native event delegate. + private const int EventParameterCount = 2; + + /// Checks whether a type has any property eligible for a mechanism. + /// The extracted type data. + /// The mechanism identity. + /// True when at least one property offers that candidate. + internal static bool HasCandidate(ClassBindingInfo info, string kind) + { + for (var i = 0; i < info.Properties.Length; i++) + { + if (Candidate(info, info.Properties[i].PropertyName, kind) is not null) + { + return true; + } + } + + return false; + } + + /// Checks a framework base identity, including the type itself. + /// The consumer type. + /// The framework type name. + /// True when the type derives from the named framework type. + internal static bool DerivesFrom(INamedTypeSymbol type, string metadataName) + { + for (var current = type; current is not null; current = current.BaseType) + { + if (NativeTypeIdentity.Matches(current, metadataName)) + { + return true; + } + } + + return false; + } + + /// Finds a member through the base chain, honoring member hiding. + /// The type exposing the member. + /// The member name. + /// The nearest declaration, or null. + internal static ISymbol? FindMember(INamedTypeSymbol owner, string name) + { + for (var type = owner; type is not null; type = type.BaseType) + { + var members = type.GetMembers(name); + if (!members.IsEmpty) + { + return members[0]; + } + } + + return null; + } + + /// Accepts public instance events with two-argument void delegates. + /// The type exposing the event. + /// The event name. + /// Concrete event metadata, or null. + internal static NotificationEventInfo? FindEvent(INamedTypeSymbol owner, string name) => + FindMember(owner, name) is IEventSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, Type: INamedTypeSymbol delegateType } + && delegateType.DelegateInvokeMethod is { ReturnsVoid: true, Parameters.Length: EventParameterCount } invoke + && invoke.Parameters[0].RefKind == RefKind.None && invoke.Parameters[1].RefKind == RefKind.None + ? new(name, delegateType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + : null; + + /// Verifies the public native member backing a CLR dependency property. + /// The type exposing the member. + /// The observed CLR property name. + /// The framework dependency-property type. + /// True when a static field or readable property has the required type. + internal static bool HasDependencyProperty(INamedTypeSymbol owner, string propertyName, string metadataName) + { + var type = FindMember(owner, $"{propertyName}Property") switch + { + IFieldSymbol { IsStatic: true, DeclaredAccessibility: Accessibility.Public } field => field.Type, + IPropertySymbol { IsStatic: true, GetMethod.DeclaredAccessibility: Accessibility.Public } property => property.Type, + _ => null, + }; + return NativeTypeIdentity.Matches(type, metadataName); + } + + /// Gets one plugin's verified candidate for a property. + /// The type's extracted property data. + /// The property name. + /// The plugin identity. + /// The matching candidate, or null. + internal static PlatformObservationInfo? Candidate(ClassBindingInfo? info, string name, string kind) + { + var property = info is null ? null : ObservedProperties.Find(info, name); + if (property is null) + { + return null; + } + + for (var i = 0; i < property.PlatformObservations.Length; i++) + { + var candidate = property.PlatformObservations[i]; + if (candidate.Kind == kind) + { + return candidate; + } + } + + return null; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PocoObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PocoObservationPlugin.cs new file mode 100644 index 00000000..7d070966 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/PocoObservationPlugin.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Reads a non-notifying property at subscription time and keeps the observation open. +internal sealed class PocoObservationPlugin : IObservationPlugin +{ + /// + public int Affinity => BindingAffinity.Fallback; + + /// + public string ObservationKind => "POCO"; + + /// + public bool SupportsBeforeChanged => true; + + /// + public bool RequiresHelperClasses => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => Affinity; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + sb.Append("new __UnchangingPropertyObservable<").Append(observation.SourceType).Append(", ") + .Append(observation.Segment.PropertyTypeFullName).Append(">(").Append(observation.Source) + .Append(", __source => ").Append(GeneratedTypeNames.ReadProperty(observation.Segment, observation.SourceType, "__source")) + .Append(')'); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => EmitHelper(sb); + + /// Emits a typed deferred getter without an event subscription or completion notification. + /// The output builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EmitHelper(StringBuilder sb) => + sb.AppendLine(""" + private sealed class __UnchangingPropertyObservable : global::System.IObservable + { + private readonly TSource _source; + private readonly global::System.Func _getter; + internal __UnchangingPropertyObservable(TSource source, global::System.Func getter) + { + _source = source; + _getter = getter; + } + public global::System.IDisposable Subscribe(global::System.IObserver observer) + { + if (observer == null) + { + throw new global::System.ArgumentNullException(nameof(observer)); + } + observer.OnNext(_getter(_source)); + return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + } + } + """); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs index 807af136..913f00b3 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs @@ -2,6 +2,8 @@ // ReactiveUI and Contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Runtime.CompilerServices; +using System.Text; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; @@ -11,14 +13,44 @@ namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; /// plain INPC plugin. IReactiveObject implements both notification interfaces, so the emitted /// observation is the same. /// -internal sealed class ReactiveObjectObservationPlugin : NotifyPropertyObservationPlugin +internal sealed class ReactiveObjectObservationPlugin : IObservationPlugin { /// - public override int Affinity => BindingAffinity.ExactType; + public int Affinity => BindingAffinity.ExactType; /// - public override string ObservationKind => "ReactiveObject"; + public string ObservationKind => "ReactiveObject"; /// - public override bool IsAMatch(ClassBindingInfo classInfo) => classInfo.ImplementsIReactiveObject; + public bool SupportsBeforeChanged => true; + + /// + public bool RequiresHelperClasses => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + IsAMatch(classInfo) ? Affinity : 0; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => classInfo.ImplementsIReactiveObject; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => true; + + /// + public void EmitHelperClasses(StringBuilder sb) {} + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NotifyPropertyEmitter.EmitShallowObservation( + sb, + observation.Source, + observation.Segment, + observation.SourceType, + observation.BeforeChange, + observation.Distinct); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitObservationPlugin.cs new file mode 100644 index 00000000..39b9f810 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitObservationPlugin.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Observes UIKit text, selection, date and switch properties through their native notifications. +internal sealed class UIKitObservationPlugin : IPlatformObservationPlugin +{ + /// The native UIKit property-specific score. + private const int PropertyAffinity = 30; + + /// + public int Affinity => PropertyAffinity; + + /// + public string ObservationKind => "UIKit"; + + /// + public bool SupportsBeforeChanged => false; + + /// + public bool RequiresHelperClasses => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; + + /// + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) + { + var notification = NotificationFor(owner, property.Name); + if (notification is not null) + { + return new(ObservationKind, Affinity, default, notification, null, null); + } + + var widget = property.Name switch + { + "Date" => "UIKit.UIDatePicker", + "SelectedSegment" => "UIKit.UISegmentedControl", + "On" => "UIKit.UISwitch", + "SelectedItem" => "UIKit.UITabBar", + "Text" => "UIKit.UISearchBar", + _ => null, + }; + if (widget is null || !PlatformSymbols.DerivesFrom(owner, widget)) + { + return null; + } + + var eventName = property.Name switch + { + "SelectedItem" => "ItemSelected", + "Text" => "TextChanged", + _ => "ValueChanged", + }; + var changeEvent = PlatformSymbols.FindEvent(owner, eventName); + return changeEvent is null ? null : new(ObservationKind, Affinity, new([changeEvent]), null, null, null); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); + + /// + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__UIKitObservable"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, AppendSubscription); + + /// Emits the native notification selected for this property. + /// The output builder. + /// The observed property. + /// The verified native notification. + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) + { + if (info.NotificationName is not null) + { + AppleNotificationEmitter.AppendSubscription(sb, segment, info); + } + else + { + NativeEventSubscriptionEmitter.Append(sb, info.Events); + } + } + + /// Resolves sender-scoped text notifications for UIKit text controls. + /// The property owner. + /// The property name. + /// The notification constant, or null. + internal static string? NotificationFor(INamedTypeSymbol owner, string name) + { + if (name != "Text") + { + return null; + } + + if (PlatformSymbols.DerivesFrom(owner, "UIKit.UITextField")) + { + return AppleNotificationEmitter.ResolveNotification(owner, "TextFieldTextDidChangeNotification"); + } + + return PlatformSymbols.DerivesFrom(owner, "UIKit.UITextView") + ? AppleNotificationEmitter.ResolveNotification(owner, "TextDidChangeNotification") + : null; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitValueObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitValueObservationPlugin.cs new file mode 100644 index 00000000..7aff373a --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UIKitValueObservationPlugin.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Observes the value exposed by UIKit controls through their native value-change event. +internal sealed class UIKitValueObservationPlugin : IPlatformObservationPlugin +{ + /// The native UIControl value observation score. + private const int ValueAffinity = 20; + + /// + public int Affinity => ValueAffinity; + + /// + public string ObservationKind => "UIKitValue"; + + /// + public bool SupportsBeforeChanged => false; + + /// + public bool RequiresHelperClasses => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; + + /// + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) + { + if (property.Name != "Value" || !PlatformSymbols.DerivesFrom(owner, "UIKit.UIControl")) + { + return null; + } + + var changeEvent = PlatformSymbols.FindEvent(owner, "ValueChanged"); + return changeEvent is null ? null : new(ObservationKind, Affinity, new([changeEvent]), null, null, null); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); + + /// + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__UIKitValueObservable"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, AppendSubscription); + + /// Emits the concrete value-change handler. + /// The output builder. + /// The observed property. + /// The verified event delegate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) => + NativeEventSubscriptionEmitter.Append(sb, info.Events); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UnoObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UnoObservationPlugin.cs new file mode 100644 index 00000000..3a05943c --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/UnoObservationPlugin.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Observes dependency properties exposed through Uno's Windows UI namespace. +internal sealed class UnoObservationPlugin : IPlatformObservationPlugin +{ + /// + public string ObservationKind => "UnoDP"; + + /// + public int Affinity => BindingAffinity.WinUiDependencyObject; + + /// + public bool SupportsBeforeChanged => false; + + /// + public bool RequiresHelperClasses => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) => + DependencyPropertyObservationEmitter.Inspect(owner, property, ObservationKind, "Windows.UI.Xaml"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); + + /// + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__UnoDPObservable"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, DependencyPropertyObservationEmitter.AppendSubscription); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs index 0ed32a95..16b2e773 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs @@ -4,115 +4,65 @@ using System.Runtime.CompilerServices; using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; -/// Observes WinForms components through their {PropertyName}Changed events. -internal sealed class WinFormsObservationPlugin : AfterChangeObservationPlugin +/// Observes WinForms component properties through their concrete change events. +internal sealed class WinFormsObservationPlugin : IPlatformObservationPlugin { - /// Opens the lambda that adds or removes the generated event handler. - private const string HandlerLambdaOpen = " __h => (("; - - /// Completes the event name and subscribes the generated handler. - private const string ChangedEventAdd = "Changed += __h,"; - - /// Completes the event name and unsubscribes the generated handler. - private const string ChangedEventRemove = "Changed -= __h,"; - - /// The affinity this plugin bids with. - private static readonly int WinFormsAffinity = BindingAffinity.WinFormsEvent; - /// - public override int Affinity => WinFormsAffinity; + public int Affinity => BindingAffinity.WinFormsEvent; /// - public override string ObservationKind => "WinForms"; + public string ObservationKind => "WinForms"; /// - public override bool RequiresHelperClasses => false; + public bool SupportsBeforeChanged => false; /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool IsAMatch(ClassBindingInfo classInfo) => - classInfo.InheritsWinFormsComponent; + public bool RequiresHelperClasses => true; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => - ObservedProperties.HasChangeEvent(classInfo, propertyName); + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; /// - public override void EmitHelperClasses(StringBuilder sb) + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) { - // No helper classes needed — uses EventObservable from runtime library. + if (!PlatformSymbols.DerivesFrom(owner, "System.ComponentModel.Component")) + { + return null; + } + + var changeEvent = PlatformSymbols.FindEvent(owner, $"{property.Name}Changed"); + return changeEvent is null ? null : new(ObservationKind, Affinity, new([changeEvent]), null, null, null); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - sb.Append(GeneratedSyntax.InlineLocalDeclaration).Append(varName).Append(" = new global::ReactiveUI.Binding.Observables.EventObservable<") - .Append(segment.PropertyTypeFullName).AppendLine(">(").Append(" __h => ((").Append(castTypeName).Append(')').Append(rootVar) - .Append(").").Append(segment.PropertyName).AppendLine("Changed += __h,").Append(" __h => ((").Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine("Changed -= __h,").Append(" () => ((") - .Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",") - .AppendLine(" true);"); + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); /// - protected override void AppendShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool includeStartWith) => - _ = sb.Append("new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).Append(">(") - .Append("__h => ((").Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).Append("Changed += __h, ") - .Append("__h => ((").Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).Append("Changed -= __h, ") - .Append("() => ((").Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).Append(", ") - .Append(includeStartWith ? "true" : "false").Append(')'); + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; /// - protected override void AppendShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - _ = sb.Append(" var ").Append(varName).Append(" = new global::ReactiveUI.Binding.Observables.EventObservable<") - .Append(segment.PropertyTypeFullName).AppendLine(">(").Append(HandlerLambdaOpen).Append(castTypeName).Append(')').Append(rootVar) - .Append(").").Append(segment.PropertyName).AppendLine(ChangedEventAdd).Append(HandlerLambdaOpen).Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(ChangedEventRemove).Append(" () => ((") - .Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",").Append(" true);"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__WinFormsObservable"); /// - protected override void AppendChainSegmentObservation( - StringBuilder sb, - string lambdaParam, - PropertyPathSegment segment) => - _ = sb.Append(" new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" __h => ((").Append(segment.DeclaringTypeFullName).Append(')').Append(lambdaParam).Append(").") - .Append(segment.PropertyName).AppendLine(ChangedEventAdd).Append(" __h => ((").Append(segment.DeclaringTypeFullName) - .Append(')').Append(lambdaParam).Append(").").Append(segment.PropertyName).AppendLine(ChangedEventRemove) - .Append(" () => ((").Append(segment.DeclaringTypeFullName).Append(')').Append(lambdaParam).Append(").") - .Append(segment.PropertyName).AppendLine(",").Append(" false)"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, AppendSubscription); - /// - protected override void AppendDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string obsVarName) => - _ = sb.Append(" var ").Append(obsVarName).Append(" = (global::System.IObservable<").Append(segment.PropertyTypeFullName) - .Append(" new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(HandlerLambdaOpen).Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName) - .AppendLine(ChangedEventAdd).Append(HandlerLambdaOpen).Append(castTypeName).Append(')').Append(rootVar).Append(").") - .Append(segment.PropertyName).AppendLine(ChangedEventRemove).Append(" () => ((").Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",").AppendLine(" false);"); + /// Emits the concrete native event subscriptions and their removal. + /// The output builder. + /// The observed property. + /// The selected native mechanism. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AppendSubscription(StringBuilder sb, PropertyPathSegment segment, PlatformObservationInfo info) => + NativeEventSubscriptionEmitter.Append(sb, info.Events); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs index 8b98ee90..bf6c7c8d 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs @@ -4,222 +4,50 @@ using System.Runtime.CompilerServices; using System.Text; -using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; -/// Observes WinUI dependency properties through RegisterPropertyChangedCallback. -internal sealed class WinUIObservationPlugin : AfterChangeObservationPlugin +/// Observes WinUI dependency properties through native callback tokens. +internal sealed class WinUIObservationPlugin : IPlatformObservationPlugin { - /// Completes the name of the dependency property field a plain property is registered under. - private const string DependencyPropertyFieldSuffix = "Property,"; - - /// The affinity this plugin bids with. - private static readonly int WinUIAffinity = BindingAffinity.WinUiDependencyObject; - /// - public override int Affinity => WinUIAffinity; + public string ObservationKind => "WinUIDP"; /// - public override string ObservationKind => "WinUIDP"; + public int Affinity => BindingAffinity.WinUiDependencyObject; /// - public override bool RequiresHelperClasses => true; + public bool SupportsBeforeChanged => false; /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool IsAMatch(ClassBindingInfo classInfo) => - classInfo.InheritsWinUIDependencyObject; + public bool RequiresHelperClasses => true; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => - ObservedProperties.IsDependencyProperty(classInfo, propertyName); - - /// - public override void EmitHelperClasses(StringBuilder sb) - { - EmitObservableHeader(sb); - EmitSubscriptionClass(sb); - } + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + !isBeforeChange && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - sb.Append(GeneratedSyntax.InlineLocalDeclaration).Append(varName).Append(" = new __WinUIDPObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Microsoft.UI.Xaml.DependencyObject)").Append(rootVar).AppendLine(",").Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).AppendLine("Property,") - .Append(" (global::Microsoft.UI.Xaml.DependencyObject __o) => ((").Append(castTypeName).Append(")__o).") - .Append(segment.PropertyName).AppendLine(",").AppendLine(" true);"); - - /// - protected override void AppendShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool includeStartWith) => - _ = sb.Append("new __WinUIDPObservable<").Append(segment.PropertyTypeFullName).Append(">(") - .Append("(global::Microsoft.UI.Xaml.DependencyObject)").Append(rootVar).Append(", ").Append(castTypeName).Append('.') - .Append(segment.PropertyName).Append("Property, ").Append("(global::Microsoft.UI.Xaml.DependencyObject __o) => ((").Append(castTypeName) - .Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName).Append(", ").Append(includeStartWith ? "true" : "false").Append(')'); + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) => + DependencyPropertyObservationEmitter.Inspect(owner, property, ObservationKind, "Microsoft.UI.Xaml"); /// - protected override void AppendShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - _ = sb.Append(" var ").Append(varName).Append(" = new __WinUIDPObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Microsoft.UI.Xaml.DependencyObject)").Append(rootVar).AppendLine(",").Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).AppendLine(DependencyPropertyFieldSuffix) - .Append(" (global::Microsoft.UI.Xaml.DependencyObject __o) => ((").Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose) - .Append(segment.PropertyName).AppendLine(",").Append(" true);"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAMatch(ClassBindingInfo classInfo) => PlatformSymbols.HasCandidate(classInfo, ObservationKind); /// - protected override void AppendChainSegmentObservation( - StringBuilder sb, - string lambdaParam, - PropertyPathSegment segment) => - _ = sb.Append(" new __WinUIDPObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Microsoft.UI.Xaml.DependencyObject)").Append(lambdaParam).AppendLine(",") - .Append(" ").Append(segment.DeclaringTypeFullName).Append('.').Append(segment.PropertyName) - .AppendLine(DependencyPropertyFieldSuffix) - .Append(" (global::Microsoft.UI.Xaml.DependencyObject __o) => ((").Append(segment.DeclaringTypeFullName) - .Append(GeneratedSyntax.ObserverCastClose).Append(segment.PropertyName).AppendLine(",").Append(" false)"); + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null; /// - protected override void AppendDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string obsVarName) => - _ = sb.Append(" var ").Append(obsVarName).Append(" = (global::System.IObservable<").Append(segment.PropertyTypeFullName) - .Append(" new __WinUIDPObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .Append(" (global::Microsoft.UI.Xaml.DependencyObject)").Append(rootVar).AppendLine(",").Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).AppendLine(DependencyPropertyFieldSuffix) - .Append(" (global::Microsoft.UI.Xaml.DependencyObject __o) => ((").Append(castTypeName).Append(GeneratedSyntax.ObserverCastClose) - .Append(segment.PropertyName).AppendLine(",").AppendLine(" false);"); - - /// Emits the __WinUIDPObservable<T> class header (fields and constructor). - /// The string builder. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitObservableHeader(StringBuilder sb) => - sb.AppendLine(""" - - /// - /// Fused observable for WinUI DependencyProperty observation. - /// Uses RegisterPropertyChangedCallback / UnregisterPropertyChangedCallback - /// for token-based subscription management. - /// - private sealed class __WinUIDPObservable : global::System.IObservable - { - private readonly global::Microsoft.UI.Xaml.DependencyObject _source; - private readonly global::Microsoft.UI.Xaml.DependencyProperty _dp; - private readonly global::System.Func _getter; - private readonly bool _distinctUntilChanged; - - internal __WinUIDPObservable( - global::Microsoft.UI.Xaml.DependencyObject source, - global::Microsoft.UI.Xaml.DependencyProperty dp, - global::System.Func getter, - bool distinctUntilChanged) - { - _source = source; - _dp = dp; - _getter = getter; - _distinctUntilChanged = distinctUntilChanged; - } - """); - - /// Emits the observable's Subscribe method and subscription class, closing the observable. - /// The string builder. - private static void EmitSubscriptionClass(StringBuilder sb) - { - EmitSubscriptionClassHead(sb); - EmitSubscriptionClassCallbacks(sb); - } - - /// Emits the Subscribe method plus the subscription's fields and constructor. - /// The string builder to append to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitSubscriptionClassHead(StringBuilder sb) => - sb.AppendLine(""" - - public global::System.IDisposable Subscribe(global::System.IObserver observer) - { - return new Subscription(this, observer); - } - - private sealed class Subscription : global::System.IDisposable - { - private readonly __WinUIDPObservable _parent; - private readonly long _token; - private readonly global::System.Collections.Generic.IEqualityComparer _comparer; - private global::System.IObserver _observer; - private T _lastValue; - private bool _hasValue; - - internal Subscription(__WinUIDPObservable parent, global::System.IObserver observer) - { - _parent = parent; - _observer = observer; - _comparer = global::System.Collections.Generic.EqualityComparer.Default; - _token = parent._source.RegisterPropertyChangedCallback(parent._dp, OnPropertyChanged); - - // Emit initial value - var initial = parent._getter(parent._source); - _lastValue = initial; - _hasValue = true; - observer.OnNext(initial); - } - """); + public void EmitHelperClasses(StringBuilder sb) => NativeObservableEmitter.EmitHelper(sb, "__WinUIDPObservable"); - /// Emits the subscription's change callback and disposal, closing the observable class. - /// The string builder to append to. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EmitSubscriptionClassCallbacks(StringBuilder sb) => - sb.AppendLine(""" - - private void OnPropertyChanged( - global::Microsoft.UI.Xaml.DependencyObject sender, - global::Microsoft.UI.Xaml.DependencyProperty dp) - { - var obs = System.Threading.Volatile.Read(ref _observer); - if (obs == null) - { - return; - } - - var value = _parent._getter(sender); - - if (_parent._distinctUntilChanged && _hasValue && _comparer.Equals(value, _lastValue)) - { - return; - } - - _lastValue = value; - _hasValue = true; - obs.OnNext(value); - } - - public void Dispose() - { - var obs = System.Threading.Interlocked.Exchange(ref _observer, null); - if (obs != null) - { - _parent._source.UnregisterPropertyChangedCallback(_parent._dp, _token); - } - } - } - } - """); + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + NativeObservationEmitter.Emit(sb, observation, ObservationKind, DependencyPropertyObservationEmitter.AppendSubscription); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationEmitter.cs new file mode 100644 index 00000000..f4381d5a --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationEmitter.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +/// Emits WPF dependency-property observation with a typed getter. +internal static class WpfObservationEmitter +{ + /// Closes the descriptor lookup and subscribes the generated handler. + private const string AddValueChangedCall = ")).AddValueChanged("; + + /// Closes the descriptor lookup and unsubscribes the generated handler. + private const string RemoveValueChangedCall = ")).RemoveValueChanged("; + + /// Emits a typed native property observation. + /// The output builder. + /// The observed source variable. + /// The observed property. + /// The concrete source type. + /// Whether equal consecutive values are suppressed. + internal static void Emit( + StringBuilder sb, + string rootVar, + PropertyPathSegment segment, + string castTypeName, + bool includeStartWith) => + _ = sb.Append("new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).Append(">(") + .Append("__h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(castTypeName).Append('.') + .Append(segment.PropertyName).Append("Property,").Append(" typeof(").Append(castTypeName).Append(AddValueChangedCall).Append(rootVar) + .Append(", __h), ").Append("__h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(castTypeName).Append('.') + .Append(segment.PropertyName).Append("Property,").Append(" typeof(").Append(castTypeName).Append(RemoveValueChangedCall).Append(rootVar) + .Append(", __h), ").Append("() => ((").Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).Append(", ") + .Append(includeStartWith ? "true" : "false").Append(')'); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs index 0d9ec4f0..089f5e8b 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs @@ -4,139 +4,61 @@ using System.Runtime.CompilerServices; using System.Text; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; namespace ReactiveUI.Binding.SourceGenerators.Plugins.Observation; /// Observes WPF dependency properties through DependencyPropertyDescriptor.AddValueChanged. -internal sealed class WpfObservationPlugin : AfterChangeObservationPlugin +internal sealed class WpfObservationPlugin : IPlatformObservationPlugin { - /// Opens the descriptor lookup the handler is added to or removed from. - private const string DescriptorLookupOpen = " __h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty("; - - /// Closes the descriptor lookup and subscribes the generated handler. - private const string AddValueChangedCall = ")).AddValueChanged("; - - /// Closes the descriptor lookup and unsubscribes the generated handler. - private const string RemoveValueChangedCall = ")).RemoveValueChanged("; - - /// Passes the generated handler and closes the add or remove lambda. - private const string HandlerArgumentClose = ", __h),"; - - /// Completes the dependency property field name and opens its owner type. - private const string DependencyPropertyOwnerOpen = "Property, typeof("; - /// The affinity this plugin bids with. private static readonly int WpfAffinity = BindingAffinity.WpfDependencyObject; /// - public override int Affinity => WpfAffinity; + public int Affinity => WpfAffinity; /// - public override string ObservationKind => "WpfDP"; + public string ObservationKind => "WpfDP"; /// - public override bool RequiresHelperClasses => false; + public bool RequiresHelperClasses => false; /// - protected override bool AnswersBeforeChangeWithLiveStream => true; + public bool SupportsBeforeChanged => false; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool IsAMatch(ClassBindingInfo classInfo) => - classInfo.InheritsWpfDependencyObject; + public int GetAffinityForProperty(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange) => + IsAMatch(classInfo) && CanObserveProperty(classInfo, propertyName) ? Affinity : 0; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => - ObservedProperties.IsDependencyProperty(classInfo, propertyName); - - /// - public override void EmitHelperClasses(StringBuilder sb) - { - // No helper classes needed — uses EventObservable from runtime library. - } + public bool IsAMatch(ClassBindingInfo classInfo) => + classInfo.InheritsWpfDependencyObject; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void EmitInlineObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - sb.Append(" var ").Append(varName).Append(" = new global::ReactiveUI.Binding.Observables.EventObservable<") - .Append(segment.PropertyTypeFullName).AppendLine(">(") - .AppendLine(" __h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append("Property, typeof(").Append(castTypeName) - .Append(")).AddValueChanged(").Append(rootVar).AppendLine(", __h),") - .AppendLine(" __h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append("Property, typeof(").Append(castTypeName) - .Append(")).RemoveValueChanged(").Append(rootVar).AppendLine(", __h),").Append(" () => ((").Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",").AppendLine(" true);"); - - /// - protected override void AppendShallowObservation( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - bool includeStartWith) => - _ = sb.Append("new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).Append(">(") - .Append("__h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(castTypeName).Append('.') - .Append(segment.PropertyName).Append("Property,").Append(" typeof(").Append(castTypeName).Append(AddValueChangedCall).Append(rootVar) - .Append(", __h), ").Append("__h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(").Append(castTypeName).Append('.') - .Append(segment.PropertyName).Append("Property,").Append(" typeof(").Append(castTypeName).Append(RemoveValueChangedCall).Append(rootVar) - .Append(", __h), ").Append("() => ((").Append(castTypeName).Append(')').Append(rootVar).Append(").").Append(segment.PropertyName).Append(", ") - .Append(includeStartWith ? "true" : "false").Append(')'); + public bool CanObserveProperty(ClassBindingInfo classInfo, string propertyName) => + ObservedProperties.Find(classInfo, propertyName) is { SymbolsInspected: true } + ? PlatformSymbols.Candidate(classInfo, propertyName, ObservationKind) is not null + : ObservedProperties.IsDependencyProperty(classInfo, propertyName); /// - protected override void AppendShallowObservationVariable( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string varName) => - _ = sb.Append(" var ").Append(varName).Append(" = new global::ReactiveUI.Binding.Observables.EventObservable<") - .Append(segment.PropertyTypeFullName).AppendLine(">(") - .AppendLine(DescriptorLookupOpen).Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append(DependencyPropertyOwnerOpen).Append(castTypeName).Append(AddValueChangedCall) - .Append(rootVar).AppendLine(HandlerArgumentClose) - .AppendLine(DescriptorLookupOpen).Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append(DependencyPropertyOwnerOpen).Append(castTypeName) - .Append(RemoveValueChangedCall).Append(rootVar).AppendLine(HandlerArgumentClose).Append(" () => ((").Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",").Append(" true);"); + public PlatformObservationInfo? InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) => + PlatformSymbols.DerivesFrom(owner, "System.Windows.DependencyObject") + && PlatformSymbols.HasDependencyProperty(owner, property.Name, "System.Windows.DependencyProperty") + ? new(ObservationKind, Affinity, default, null, "global::System.Windows.DependencyObject", null) + : null; /// - protected override void AppendChainSegmentObservation( - StringBuilder sb, - string lambdaParam, - PropertyPathSegment segment) => - _ = sb.Append(" new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .AppendLine(" __h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(") - .Append(" ").Append(segment.DeclaringTypeFullName).Append('.').Append(segment.PropertyName) - .Append(DependencyPropertyOwnerOpen).Append(segment.DeclaringTypeFullName).Append(AddValueChangedCall).Append(lambdaParam) - .AppendLine(HandlerArgumentClose) - .AppendLine(" __h => global::System.ComponentModel.DependencyPropertyDescriptor.FromProperty(") - .Append(" ").Append(segment.DeclaringTypeFullName).Append('.').Append(segment.PropertyName) - .Append(DependencyPropertyOwnerOpen).Append(segment.DeclaringTypeFullName).Append(RemoveValueChangedCall).Append(lambdaParam) - .AppendLine(HandlerArgumentClose).Append(" () => ((").Append(segment.DeclaringTypeFullName).Append(')') - .Append(lambdaParam).Append(").").Append(segment.PropertyName).AppendLine(",").Append(" false)"); + public void EmitHelperClasses(StringBuilder sb) + { + // No helper classes needed — uses EventObservable from runtime library. + } /// - protected override void AppendDeepChainRootSegment( - StringBuilder sb, - string rootVar, - PropertyPathSegment segment, - string castTypeName, - string obsVarName) => - _ = sb.Append(" var ").Append(obsVarName).Append(" = (global::System.IObservable<").Append(segment.PropertyTypeFullName) - .Append(" new global::ReactiveUI.Binding.Observables.EventObservable<").Append(segment.PropertyTypeFullName).AppendLine(">(") - .AppendLine(DescriptorLookupOpen).Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append(DependencyPropertyOwnerOpen).Append(castTypeName).Append(AddValueChangedCall) - .Append(rootVar).AppendLine(HandlerArgumentClose) - .AppendLine(DescriptorLookupOpen).Append(" ") - .Append(castTypeName).Append('.').Append(segment.PropertyName).Append(DependencyPropertyOwnerOpen).Append(castTypeName) - .Append(RemoveValueChangedCall).Append(rootVar).AppendLine(HandlerArgumentClose).Append(" () => ((").Append(castTypeName).Append(')') - .Append(rootVar).Append(").").Append(segment.PropertyName).AppendLine(",").AppendLine(" false);"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EmitObservation(StringBuilder sb, in ObservationExpression observation) => + WpfObservationEmitter.Emit(sb, observation.Source, observation.Segment, observation.SourceType, observation.Distinct); } diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservationPluginRegistry.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservationPluginRegistry.cs index 95195e81..d3f6a959 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservationPluginRegistry.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservationPluginRegistry.cs @@ -3,31 +3,30 @@ // See the LICENSE file in the project root for full license information. using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; using ReactiveUI.Binding.SourceGenerators.Models; using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; namespace ReactiveUI.Binding.SourceGenerators.Plugins; -/// -/// Static registry of observation plugins, sorted by affinity descending. -/// Returns the highest-affinity plugin that matches a given type's . -/// Affinity values match ReactiveUI's runtime GetAffinityForObject values exactly. -/// +/// Selects the strongest eligible observation mechanism, preserving declaration order for ties. internal static class ObservationPluginRegistry { - /// - /// All plugins sorted by affinity descending (highest priority first). - /// When multiple plugins match the same type, the first match wins. - /// + /// The supported mechanisms in deterministic tie order. private static readonly IObservationPlugin[] Plugins = [ new KVOObservationPlugin(), // Affinity 15 - Apple NSObject KVO + new UIKitObservationPlugin(), + new UIKitValueObservationPlugin(), + new AppKitObservationPlugin(), new ReactiveObjectObservationPlugin(), // Affinity 10 - IReactiveObject new WinFormsObservationPlugin(), // Affinity 8 - WinForms Component new WinUIObservationPlugin(), // Affinity 6 - WinUI DependencyObject + new UnoObservationPlugin(), new INPCObservationPlugin(), // Affinity 5 - INotifyPropertyChanged new AndroidObservationPlugin(), // Affinity 5 - Android View - new WpfObservationPlugin() // Affinity 4 - WPF DependencyObject + new WpfObservationPlugin(), // Affinity 4 - WPF DependencyObject + new PocoObservationPlugin() ]; /// Gets the total number of registered plugins. @@ -38,37 +37,65 @@ internal static class ObservationPluginRegistry /// The best matching plugin, or if no plugin matches. internal static IObservationPlugin? GetBestPlugin(ClassBindingInfo classInfo) { + IObservationPlugin? best = null; for (var i = 0; i < Plugins.Length; i++) { - if (Plugins[i].IsAMatch(classInfo)) + var candidate = Plugins[i]; + if (candidate.IsAMatch(classInfo) && (best is null || candidate.Affinity > best.Affinity)) { - return Plugins[i]; + best = candidate; } } - return null; + return best; } /// Gets the highest-affinity plugin whose mechanism reaches one particular property. /// The type-level binding info. /// The property being observed. + /// Whether the mechanism must report before the property changes. /// The best matching plugin, or if none reaches that property. /// /// A mechanism that outranks another on the type can still be the wrong one for a given property - a /// component that also raises PropertyChanged declares properties with no change event - so a plugin that /// cannot reach the property is passed over for the next, exactly as a zero affinity would be at runtime. /// - internal static IObservationPlugin? GetBestPlugin(ClassBindingInfo classInfo, string propertyName) + internal static IObservationPlugin? GetBestPlugin(ClassBindingInfo classInfo, string propertyName, bool isBeforeChange = false) { + IObservationPlugin? best = null; + var bestScore = 0; for (var i = 0; i < Plugins.Length; i++) { - if (Plugins[i].IsAMatch(classInfo) && Plugins[i].CanObserveProperty(classInfo, propertyName)) + var candidate = Plugins[i]; + var score = candidate.GetAffinityForProperty(classInfo, propertyName, isBeforeChange); + if (score <= bestScore) { - return Plugins[i]; + continue; } + + best = candidate; + bestScore = score; } - return null; + return best; + } + + /// Collects eligible platform candidates while property symbols are available. + /// The concrete type exposing the property. + /// The property to inspect. + /// Value-equatable candidates for subsequent affinity voting. + internal static EquatableArray InspectProperty(INamedTypeSymbol owner, IPropertySymbol property) + { + var candidates = new List(); + for (var i = 0; i < Plugins.Length; i++) + { + if (Plugins[i] is IPlatformObservationPlugin platform && platform.InspectProperty(owner, property) is { } candidate) + { + candidates.Add(candidate); + } + } + + return new([.. candidates]); } /// Gets a plugin by its observation kind identifier. diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservedProperties.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservedProperties.cs index 02b3f927..3ac8145a 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservedProperties.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservedProperties.cs @@ -57,7 +57,7 @@ internal static bool IsDeclaredByConsumer(ClassBindingInfo classInfo, string pro /// The declaring type's binding info. /// The property name to find. /// The property info, or null when the type does not declare it. - private static ObservablePropertyInfo? Find(ClassBindingInfo classInfo, string propertyName) + internal static ObservablePropertyInfo? Find(ClassBindingInfo classInfo, string propertyName) { var properties = classInfo.Properties; for (var i = 0; i < properties.Length; i++) diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/CollectionSetMethodEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/CollectionSetMethodEmitter.cs new file mode 100644 index 00000000..2434053f --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/CollectionSetMethodEmitter.cs @@ -0,0 +1,102 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Emits typed collection mutation and isolates winning custom set-method providers. +internal static class CollectionSetMethodEmitter +{ + /// Opens a block within the write callback. + private const string WriteBlockOpen = " {"; + + /// Closes a block within the write callback. + private const string WriteBlockClose = " }"; + + /// Subscribes after scheduling so collection operations run on the owning thread. + /// The output builder. + /// The concrete collection write. + /// The scheduled input stream. + internal static void EmitSubscription(StringBuilder sb, in SetMethodEmission write, string source) + { + var targetType = write.Path[write.Path.Length - 1].PropertyTypeFullName; + AppendSelection(sb, write, targetType); + if (write.ReportChanges) + { + _ = sb.Append(" var __setChanges = new global::ReactiveUI.Primitives.Signals.Signal<").Append(targetType).AppendLine(">();"); + } + + _ = sb.Append(" var __setSubscription = ").Append(GeneratedTypeNames.BindingErrors).Append(".Subscribe(").Append(source).AppendLine(", value =>") + .AppendLine(" {"); + AppendTarget(sb, write); + _ = sb.AppendLine(" if (__setConverter != null)") + .AppendLine(WriteBlockOpen) + .Append(" var __result = (").Append(targetType).AppendLine(")__setConverter.PerformSet(__collection, value, null);"); + if (write.ReportChanges) + { + _ = sb.AppendLine(" __setChanges.OnNext(__result);"); + } + + _ = sb.AppendLine(" return;").AppendLine(WriteBlockClose); + AppendMutation(sb, write.Mechanism); + if (write.ReportChanges) + { + _ = sb.AppendLine(" __setChanges.OnNext(__collection);"); + } + + _ = sb.Append(" }, \"").Append(CodeGeneratorHelpers.EscapeString(write.Expression)).AppendLine("\");"); + } + + /// Resolves the custom winner once, retaining native ties. + /// The output builder. + /// The concrete write. + /// The collection type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendSelection(StringBuilder sb, in SetMethodEmission write, string targetType) => + sb.Append(" var __setConverter = global::ReactiveUI.Binding.BindingConverters.Current.SetMethodConverters.TryGetConverter(typeof(") + .Append(write.SourceType).Append("), typeof(").Append(targetType).AppendLine("));") + .Append(" if (__setConverter != null && __setConverter.GetAffinityForObjects(typeof(") + .Append(write.SourceType).Append("), typeof(").Append(targetType).Append(")) <= ").Append(write.Mechanism.Affinity).AppendLine(")") + .AppendLine(" {").AppendLine(" __setConverter = null;").AppendLine(" }"); + + /// Reads each path link once and skips a temporarily missing collection owner. + /// The output builder. + /// The target path. + private static void AppendTarget(StringBuilder sb, in SetMethodEmission write) + { + var parent = write.Root; + for (var i = 0; i < write.Path.Length; i++) + { + var local = i == write.Path.Length - 1 ? "__collection" : $"__setParent{i.ToString(CultureInfo.InvariantCulture)}"; + _ = sb.Append(" var ").Append(local).Append(" = ").Append(CodeGeneratorHelpers.AppendSegmentRead(parent, write.Path[i])).AppendLine(";") + .Append(" if (").Append(local).AppendLine(" == null)") + .AppendLine(WriteBlockOpen).AppendLine(" return;").AppendLine(WriteBlockClose); + parent = local; + } + } + + /// Balances layout suspension even when enumeration or a native collection call fails. + /// The output builder. + /// The selected collection mechanism. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendMutation(StringBuilder sb, SetMethodInfo mechanism) => + sb.Append(" var __layoutOwner = __collection.").Append(mechanism.LayoutOwner).AppendLine(";") + .AppendLine(" __layoutOwner.SuspendLayout();") + .AppendLine(" try") + .AppendLine(WriteBlockOpen) + .AppendLine(" __collection.Clear();") + .Append(" __collection.AddRange(") + .Append(mechanism.SourceIsArray ? "value" : "global::System.Linq.Enumerable.ToArray(value)") + .AppendLine(");") + .AppendLine(WriteBlockClose) + .AppendLine(" finally") + .AppendLine(WriteBlockOpen) + .AppendLine(" __layoutOwner.ResumeLayout();") + .AppendLine(WriteBlockClose); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/ISetMethodPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/ISetMethodPlugin.cs new file mode 100644 index 00000000..61533559 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/ISetMethodPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Offers a typed native setter for a source and destination pair. +internal interface ISetMethodPlugin +{ + /// Inspects the consumer's collection and value contracts. + /// The incoming value type. + /// The property's collection type. + /// The scored mechanism, or null when ineligible. + SetMethodInfo? Select(ITypeSymbol source, ITypeSymbol target); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/PanelSetMethodPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/PanelSetMethodPlugin.cs new file mode 100644 index 00000000..cbd98a48 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/PanelSetMethodPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Populates the control collection owned by a WinForms panel. +internal sealed class PanelSetMethodPlugin : ISetMethodPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SetMethodInfo? Select(ITypeSymbol source, ITypeSymbol target) => + WinFormsCollectionSymbols.Select(source, target, "System.Windows.Forms.Control.ControlCollection", "Owner"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/SetMethodPluginRegistry.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/SetMethodPluginRegistry.cs new file mode 100644 index 00000000..83640e77 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/SetMethodPluginRegistry.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Selects the strongest eligible typed setter, preserving declaration order on ties. +internal static class SetMethodPluginRegistry +{ + /// The known native setter mechanisms. + private static readonly ISetMethodPlugin[] Plugins = [new PanelSetMethodPlugin(), new TableLayoutSetMethodPlugin()]; + + /// Compares the native candidates for the actual source and destination types. + /// The incoming value type. + /// The collection type. + /// The highest-affinity eligible mechanism. + internal static SetMethodInfo? Select(ITypeSymbol? source, ITypeSymbol? target) + { + if (source is null || target is null) + { + return null; + } + + SetMethodInfo? winner = null; + foreach (var plugin in Plugins) + { + var candidate = plugin.Select(source, target); + if (candidate is not null && (winner is null || candidate.Affinity > winner.Affinity)) + { + winner = candidate; + } + } + + return winner; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/TableLayoutSetMethodPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/TableLayoutSetMethodPlugin.cs new file mode 100644 index 00000000..e64fa017 --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/TableLayoutSetMethodPlugin.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Models; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Populates a WinForms table's control collection under its container's layout suspension. +internal sealed class TableLayoutSetMethodPlugin : ISetMethodPlugin +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SetMethodInfo? Select(ITypeSymbol source, ITypeSymbol target) => + WinFormsCollectionSymbols.Select(source, target, "System.Windows.Forms.TableLayoutControlCollection", "Container"); +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/WinFormsCollectionSymbols.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/WinFormsCollectionSymbols.cs new file mode 100644 index 00000000..8c5322ab --- /dev/null +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/SetMethod/WinFormsCollectionSymbols.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; +using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +namespace ReactiveUI.Binding.SourceGenerators.Plugins.SetMethod; + +/// Validates WinForms collection mutation members from the consumer's symbols. +internal static class WinFormsCollectionSymbols +{ + /// The native collection setter's affinity. + private const int CollectionAffinity = 10; + + /// Checks the element, collection and layout contracts before offering direct mutation. + /// The incoming enumerable type. + /// The destination collection type. + /// The exact native collection type. + /// The property providing layout suspension. + /// The verified native setter, or null. + internal static SetMethodInfo? Select(ITypeSymbol source, ITypeSymbol target, string collectionName, string layoutOwner) + { + if (target is not INamedTypeSymbol collection || !NativeTypeIdentity.Matches(target, collectionName) || !HasCollectionContract(collection, layoutOwner)) + { + return null; + } + + foreach (var contract in source.AllInterfaces) + { + if (contract.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T + && contract.TypeArguments[0] is INamedTypeSymbol { BaseType: { } elementBase } + && PlatformSymbols.DerivesFrom(elementBase, "System.Windows.Forms.Control")) + { + return new(CollectionAffinity, layoutOwner) { SourceIsArray = source is IArrayTypeSymbol { IsSZArray: true } }; + } + } + + return null; + } + + /// Checks the public collection and layout operations used by generated code. + /// The native collection type. + /// The property exposing its layout owner. + /// True when direct mutation is supported. + internal static bool HasCollectionContract(INamedTypeSymbol collection, string layoutOwner) => + PlatformSymbols.FindMember(collection, layoutOwner) is IPropertySymbol { IsStatic: false, GetMethod.DeclaredAccessibility: Accessibility.Public, Type: INamedTypeSymbol owner } + && HasMethod(owner, "SuspendLayout") && HasMethod(owner, "ResumeLayout") + && HasMethod(collection, "Clear") && HasAddRange(collection); + + /// Checks for an accessible parameterless native operation. + /// The declaring hierarchy. + /// The operation name. + /// True when the method can be called directly. + private static bool HasMethod(INamedTypeSymbol type, string name) + { + for (var current = type; current is not null; current = current.BaseType) + { + foreach (var member in current.GetMembers(name)) + { + if (member is IMethodSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, Parameters.Length: 0 }) + { + return true; + } + } + } + + return false; + } + + /// Checks that AddRange accepts a concrete control array. + /// The collection hierarchy. + /// True when the native array overload exists. + private static bool HasAddRange(INamedTypeSymbol type) + { + for (var current = type; current is not null; current = current.BaseType) + { + foreach (var member in current.GetMembers("AddRange")) + { + if (member is IMethodSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, Parameters.Length: 1 } method + && method.Parameters[0].Type is IArrayTypeSymbol array && NativeTypeIdentity.Matches(array.ElementType, "System.Windows.Forms.Control")) + { + return true; + } + } + } + + return false; + } +} diff --git a/src/ReactiveUI.Binding.SourceGenerators/RoslynHelpers.cs b/src/ReactiveUI.Binding.SourceGenerators/RoslynHelpers.cs index 3638a465..24faf461 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/RoslynHelpers.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/RoslynHelpers.cs @@ -14,7 +14,7 @@ namespace ReactiveUI.Binding.SourceGenerators; /// internal static class RoslynHelpers { - /// Pipeline A predicate: detects class declarations with a base list (potential INPC, IRO, DP, etc.). + /// Detects class declarations that can participate in view registration. /// The syntax node to check. /// Cancellation token. /// true if the node is a class with a base list; otherwise, false. diff --git a/src/ReactiveUI.Binding/PublicAPI/net10.0/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net10.0/PublicAPI.txt index 5eb6a9b3..b8ed4e51 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net10.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net10.0/PublicAPI.txt @@ -1988,6 +1988,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net11.0/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net11.0/PublicAPI.txt index 5eb6a9b3..b8ed4e51 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net11.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net11.0/PublicAPI.txt @@ -1988,6 +1988,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net462/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net462/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net462/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net462/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net47/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net47/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net47/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net47/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net471/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net471/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net471/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net471/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net472/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net472/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net472/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net472/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net48/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net48/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net48/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net48/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net481/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net481/PublicAPI.txt index 10ce6f57..fe5ce36b 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net481/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net481/PublicAPI.txt @@ -1711,6 +1711,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net8.0/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net8.0/PublicAPI.txt index 5eb6a9b3..b8ed4e51 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net8.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net8.0/PublicAPI.txt @@ -1988,6 +1988,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/ReactiveUI.Binding/PublicAPI/net9.0/PublicAPI.txt b/src/ReactiveUI.Binding/PublicAPI/net9.0/PublicAPI.txt index 5eb6a9b3..b8ed4e51 100644 --- a/src/ReactiveUI.Binding/PublicAPI/net9.0/PublicAPI.txt +++ b/src/ReactiveUI.Binding/PublicAPI/net9.0/PublicAPI.txt @@ -1988,6 +1988,7 @@ namespace ReactiveUI.Binding.Observables public sealed class AppliedChangeObservable : System.IObservable { public AppliedChangeObservable() { } + public bool HasObservers { get; } public void OnNext(ReactiveUI.Binding.BindingChange value) { } public System.IDisposable Subscribe(System.IObserver observer) { } } diff --git a/src/benchmarks/README.md b/src/benchmarks/README.md index 1e20abbb..920852af 100644 --- a/src/benchmarks/README.md +++ b/src/benchmarks/README.md @@ -72,6 +72,7 @@ cost of creating the subscription is measured separately, by the `First...` case | `BindBenchmark` | `Bind` | `Standard`, `Bidirectional`, `WithObservedChanges` | | `OneWayBindBenchmark` | `OneWayBind` | `Standard`, `FirstBinding` | | `BindToBenchmark` | `BindTo` | `Standard`, `FirstBinding` | +| `TypedAdapterBenchmark` | `BindTo` conversions | `NullableValues`, `FormattedValues` | | `InvokeCommandBenchmark` | `InvokeCommand` | `Standard`, `FirstInvocation` | | `UnsafeFallbackBenchmark` | the `Unsafe` overloads | `WhenChangedUnsafe`, `WhenChangedUnsafe deep chain`, `WhenAnyValueUnsafe`, `BindOneWayUnsafe`, `BindUnsafe` | | `RxUiDynamicChainBaseline` | ReactiveUI's dynamic chain | `SingleChain`, `TwoChains`, `DeepChain`, `FirstObservation` | @@ -83,22 +84,10 @@ have a counterpart here. Read each against `ReactiveUIObservationBenchmark` and table. A chain named at run time is walked by reflection; the same chain written as a lambda is resolved at compile time. -Each class declares a job per runtime: .NET 8, 10 and 11, and NativeAOT 10 and 11 where the code can run +Each class declares a job per runtime: .NET 10 and 11, and NativeAOT 10 and 11 where the code can run ahead of time. A class that walks a chain by reflection declares no NativeAOT job, because it cannot run there. -The .NET Framework 4.6.2 job is opt-in. Set `BenchNetFx` to add it, on Windows only, since no other host can -launch it: - -```sh -dotnet run -c Release -f net10.0 --property:BenchNetFx=true -- --filter '*' -``` - -That leg and the EventPipe profiler are mutually exclusive. The profiler refuses any job below .NET Core 3.0 -and its validator stops the whole run rather than the single job, so `BenchNetFx` drops it. A default run -keeps the profiler and the allocation traces; a `BenchNetFx` run trades them for the older runtime, and -`MemoryDiagnoser` still reports the allocation column. - ## What the generation benchmark covers `GenerationBenchmarks.Generate` runs a cold pass: syntax scan, extraction and emission. Two parameters @@ -114,17 +103,31 @@ its incremental caches, which measures the cache rather than the pass a consumer The corpus is built once per parameter set, because loading a framework's worth of metadata references costs far more than the pass under measurement. -The benchmark runs under `MemoryDiagnoser` and the `GcVerbose` EventPipe profiler. The allocation column is -the A/B number, and the trace names the frame that allocated. +The `GcVerbose` EventPipe profiler records allocations. Analysis uses the measured workload windows and their +operation counts to report sampled bytes per generation and the allocation sites. NativeAOT timing runs remain +separate where EventPipe capture is unavailable. + +## Focused adapter measurements + +`AdapterGenerationBenchmarks` covers native observation, Android/UIKit/AppKit commands, WinForms collection +bindings and typed conversions. Each corpus checks the exact number of binding methods and the intended +native operations before collecting timings. + +`TypedAdapterBenchmark` measures nullable and formatting delivery after creating the bindings. +`NativeAdapterBenchmark` measures observation, command delivery and collection writes against native-shaped +contracts. These fixtures exclude platform rendering costs. Both benchmarks count actual deliveries and reject +runs that drop updates or leave layout suspended. Timings include this counting overhead. + +Run only these cases with `--filter '*AdapterGenerationBenchmarks*'` in the generator benchmark project, or +`--filter '*TypedAdapterBenchmark*' '*NativeAdapterBenchmark*'` in the runtime benchmark project. Use `--artifacts` to put reports and +traces under `~/.cache`. ## What is not measured -`BindCommand` and `BindInteraction` have no benchmark. Both need something registered before they do any -work: `BindCommand` needs an `ICreatesCommandBinding` that reaches the control, and `BindInteraction` needs a -handler. A benchmark would be measuring that fixture rather than the library, so the number would not mean -what it appeared to. +`BindCommand` generation is measured by the adapter corpus. Execution inside native UI frameworks and +application-specific `BindInteraction` handlers is not timed by this suite. -Everything else in the observation and binding surface is covered, including the `Unsafe` overloads. +The runtime suite covers property observation and binding delivery, including the `Unsafe` overloads. `UnsafeFallbackBenchmark` declares no NativeAOT job. Those overloads carry `RequiresUnreferencedCode` because they walk the path at run time, so an ahead-of-time publish cannot be relied on to keep the members they diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUI.Binding.Benchmarks.ReactiveUI.csproj b/src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUI.Binding.Benchmarks.ReactiveUI.csproj index c1f7c07f..7a23bf28 100644 --- a/src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUI.Binding.Benchmarks.ReactiveUI.csproj +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUI.Binding.Benchmarks.ReactiveUI.csproj @@ -1,7 +1,7 @@ - net8.0;net10.0;net11.0;net462 + net10.0;net11.0 false diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/NumberPicker.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/NumberPicker.cs new file mode 100644 index 00000000..f7c2551b --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/NumberPicker.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Android.Widget; + +/// Provides a typed native value event with no fixture allocation per update. +public sealed class NumberPicker : Views.View +{ + /// Occurs after the numeric value changes. + public event EventHandler? ValueChanged; + + /// Gets or sets the observed value. + public int Value + { + get => field; + set + { + field = value; + ValueChanged?.Invoke(this, EventArgs.Empty); + } + } = -1; +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/View.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/View.cs new file mode 100644 index 00000000..015206be --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/Android/View.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace Android.Views; + +/// Provides the Android click contract for measuring generated command wiring. +public class View +{ + /// Occurs when the benchmark requests a click. + public event EventHandler? Click; + + /// Gets or sets the command's enabled state. + public bool Enabled { get; set; } + + /// Raises the native event without allocating event arguments. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RaiseClick() => Click?.Invoke(this, EventArgs.Empty); +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterCommand.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterCommand.cs new file mode 100644 index 00000000..e4a5d1c6 --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterCommand.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Windows.Input; + +namespace ReactiveUI.Binding.Benchmarks.Mocks; + +/// Counts command executions across long benchmark runs. +public sealed class NativeAdapterCommand : ICommand +{ + /// + public event EventHandler? CanExecuteChanged + { + add { } + remove { } + } + + /// Gets the number of delivered command invocations. + public long Executions { get; private set; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(object? parameter) => true; + + /// + public void Execute(object? parameter) => Executions++; +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterView.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterView.cs new file mode 100644 index 00000000..c11fb5de --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/NativeAdapterView.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.Benchmarks.Mocks; + +/// Exposes native control contracts to the generated bindings. +public sealed class NativeAdapterView : IViewFor +{ + /// Initializes a new instance of the class. + public NativeAdapterView() => Controls = new(new()); + + /// + public BenchmarkViewModel? ViewModel { get; set; } + + /// Gets the native click target. + public Android.Views.View Button { get; } = new(); + + /// Gets the collection receiving typed control arrays. + public System.Windows.Forms.Control.ControlCollection Controls { get; } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (BenchmarkViewModel?)value; + } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/TypedAdapterTarget.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/TypedAdapterTarget.cs new file mode 100644 index 00000000..492f036d --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/TypedAdapterTarget.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Binding.Benchmarks.Mocks; + +/// Receives values from the generated nullable and numeric-string converters. +public sealed class TypedAdapterTarget +{ + /// Gets or sets the nullable numeric destination. + public int? Number + { + get => field; + set + { + field = value; + NumberWrites++; + } + } + + /// Gets or sets the formatted numeric destination. + public string Text + { + get => field; + set + { + field = value; + TextWrites++; + } + } = string.Empty; + + /// Gets the number of numeric writes actually delivered. + public long NumberWrites { get; private set; } + + /// Gets the number of formatted writes actually delivered. + public long TextWrites { get; private set; } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Button.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Button.cs new file mode 100644 index 00000000..a509a3df --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Button.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace System.Windows.Forms; + +/// A concrete child control used by the collection binding. +public sealed class Button : Control +{ + /// Gets or sets the child identifier. + public int Id { get; set; } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Control.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Control.cs new file mode 100644 index 00000000..4ac1f023 --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/Mocks/WinForms/Control.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Windows.Forms; + +/// Provides layout and collection contracts without platform rendering costs. +public class Control +{ + /// Gets the number of completed layout batches. + public long Layouts { get; private set; } + + /// Gets the number of outstanding layout suspensions. + public int LayoutDepth { get; private set; } + + /// Starts a layout batch. + public void SuspendLayout() => LayoutDepth++; + + /// Completes a layout batch. + public void ResumeLayout() + { + LayoutDepth--; + Layouts++; + } + + /// Stores controls in a reusable collection buffer. + public sealed class ControlCollection + { + /// Capacity reserved for the benchmark's fixed-size control inputs. + private const int BufferCapacity = 8; + + /// Storage allocated before measurement. + private readonly List _items = [with(BufferCapacity)]; + + /// Initializes a new instance of the class. + /// The layout owner. + public ControlCollection(Control owner) => Owner = owner; + + /// Gets the layout owner. + public Control Owner { get; } + + /// Gets the number of stored controls. + public int Count => _items.Count; + + /// Empties the collection while retaining its capacity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() => _items.Clear(); + + /// Adds the supplied controls. + /// The incoming controls. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddRange(Control[] controls) => _items.AddRange(controls); + } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/NativeAdapterBenchmark.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/NativeAdapterBenchmark.cs new file mode 100644 index 00000000..180f5a0c --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/NativeAdapterBenchmark.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using BenchmarkDotNet.Attributes; +using ReactiveUI.Binding.Benchmarks.Configs; +using ReactiveUI.Binding.Benchmarks.Mocks; + +namespace ReactiveUI.Binding.Benchmarks; + +/// Measures generated native wiring separately from platform rendering and interop costs. +[Config(typeof(NativeAotBenchmarkConfig))] +public class NativeAdapterBenchmark : IObserver +{ + /// The number of native updates in each invocation. + private const int Updates = 1_000; + + /// The native value source. + private readonly Android.Widget.NumberPicker _picker = new(); + + /// The native binding target. + private readonly NativeAdapterView _view = new(); + + /// The command executed by native clicks. + private readonly NativeAdapterCommand _command = new(); + + /// The collection input stream. + private readonly BenchmarkSource _source = new(); + + /// Input controls allocated before measurement. + private readonly System.Windows.Forms.Button[] _controls = [new(), new()]; + + /// The active observation. + private IDisposable _observation = null!; + + /// The active command binding. + private IDisposable _commandBinding = null!; + + /// The active collection binding. + private IDisposable _collectionBinding = null!; + + /// The number of completed observation batches. + private long _observationBatches; + + /// The number of completed command batches. + private long _commandBatches; + + /// The number of completed collection batches. + private long _collectionBatches; + + /// The number of actual observation deliveries. + private long _observed; + + /// The latest observed value. + private int _last; + + /// Creates the native wiring before timing delivery. + [GlobalSetup] + public void Setup() + { + _view.ViewModel = new() { Run = _command }; + _observation = _picker.WhenChanged(picker => picker.Value).Subscribe(this); + _observed = 0; + _commandBinding = _view.BindCommand(_view.ViewModel, model => model.Run, view => view.Button); + _collectionBinding = _source.BindTo(_view, view => view.Controls); + } + + /// Checks every native operation was delivered and releases the bindings. + /// The measured work was not delivered. + [GlobalCleanup] + public void Cleanup() + { + _observation.Dispose(); + _commandBinding.Dispose(); + _collectionBinding.Dispose(); + if (_observed != _observationBatches * Updates || _command.Executions != _commandBatches * Updates + || _view.Controls.Owner.Layouts != _collectionBatches * Updates || _view.Controls.Owner.LayoutDepth != 0 + || (_observationBatches != 0 && _last != Updates - 1) + || (_collectionBatches != 0 && _view.Controls.Count != _controls.Length)) + { + throw new InvalidOperationException("Native adapter delivery counts do not match requested work."); + } + } + + /// Delivers value-type property notifications through the native event adapter. + /// The final observed value. + [Benchmark(OperationsPerInvoke = Updates)] + public int ObserveValue() + { + _observationBatches++; + for (var i = 0; i < Updates; i++) + { + _picker.Value = i; + } + + return _last; + } + + /// Executes commands through the native click adapter. + /// The number of actual command executions. + [Benchmark(OperationsPerInvoke = Updates)] + public long ExecuteCommand() + { + _commandBatches++; + for (var i = 0; i < Updates; i++) + { + _view.Button.RaiseClick(); + } + + return _command.Executions; + } + + /// Populates the existing native collection from a concrete control array. + /// The final collection size. + [Benchmark(OperationsPerInvoke = Updates)] + public int WriteCollection() + { + _collectionBatches++; + for (var i = 0; i < Updates; i++) + { + _source.Push(_controls); + } + + return _view.Controls.Count; + } + + /// + public void OnNext(int value) + { + _observed++; + _last = value; + } + + /// + public void OnError(Exception error) => throw new InvalidOperationException("Native observation failed.", error); + + /// + public void OnCompleted() => throw new InvalidOperationException("Native observation completed unexpectedly."); +} diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/ReactiveUI.Binding.Benchmarks.csproj b/src/benchmarks/ReactiveUI.Binding.Benchmarks/ReactiveUI.Binding.Benchmarks.csproj index fe90a2e2..50456d64 100644 --- a/src/benchmarks/ReactiveUI.Binding.Benchmarks/ReactiveUI.Binding.Benchmarks.csproj +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/ReactiveUI.Binding.Benchmarks.csproj @@ -1,7 +1,7 @@ - net8.0;net10.0;net11.0;net462 + net10.0;net11.0 true diff --git a/src/benchmarks/ReactiveUI.Binding.Benchmarks/TypedAdapterBenchmark.cs b/src/benchmarks/ReactiveUI.Binding.Benchmarks/TypedAdapterBenchmark.cs new file mode 100644 index 00000000..9196fecf --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Benchmarks/TypedAdapterBenchmark.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using BenchmarkDotNet.Attributes; +using ReactiveUI.Binding.Benchmarks.Configs; +using ReactiveUI.Binding.Benchmarks.Mocks; + +namespace ReactiveUI.Binding.Benchmarks; + +/// Measures value delivery through generated nullable and numeric formatting conversions. +[Config(typeof(NativeAotBenchmarkConfig))] +public class TypedAdapterBenchmark +{ + /// The number of values delivered in each measured batch. + private const int ValueCount = 1_000; + + /// The numeric values delivered to the nullable destination. + private readonly BenchmarkSource _nullableSource = new(); + + /// The numeric values delivered to the string destination. + private readonly BenchmarkSource _formatSource = new(); + + /// The typed destination properties. + private readonly TypedAdapterTarget _target = new(); + + /// The active nullable conversion binding. + private IDisposable _nullableBinding = null!; + + /// The active formatting conversion binding. + private IDisposable _formatBinding = null!; + + /// The number of numeric batches requested by the benchmark runner. + private long _nullableBatches; + + /// The number of formatting batches requested by the benchmark runner. + private long _formatBatches; + + /// Creates the bindings before measuring per-change delivery. + [GlobalSetup] + public void Setup() + { + _nullableBinding = _nullableSource.BindTo(_target, target => target.Number); + _formatBinding = _formatSource.BindTo(_target, target => target.Text); + } + + /// Releases both subscriptions. + /// A binding dropped writes or delivered the wrong value. + [GlobalCleanup] + public void Cleanup() + { + _nullableBinding.Dispose(); + _formatBinding.Dispose(); + if (_target.NumberWrites != _nullableBatches * ValueCount || _target.TextWrites != _formatBatches * ValueCount + || (_nullableBatches != 0 && _target.Number != ValueCount - 1) + || (_formatBatches != 0 && _target.Text != (ValueCount - 1).ToString(System.Globalization.CultureInfo.CurrentCulture))) + { + throw new InvalidOperationException("The benchmark did not deliver every requested value."); + } + } + + /// Delivers numeric values through the generated nullable lift. + /// The final delivered number. + [Benchmark(OperationsPerInvoke = ValueCount)] + public int NullableValues() + { + _nullableBatches++; + for (var i = 0; i < ValueCount; i++) + { + _nullableSource.Push(i); + } + + return _target.Number.GetValueOrDefault(); + } + + /// Formats numbers with the typed generated converter. + /// The final formatted value. + [Benchmark(OperationsPerInvoke = ValueCount)] + public string FormattedValues() + { + _formatBatches++; + for (var i = 0; i < ValueCount; i++) + { + _formatSource.Push(i); + } + + return _target.Text; + } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413.csproj b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413.csproj index 9fc2e56a..b1a94d80 100644 --- a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413.csproj +++ b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413/ReactiveUI.Binding.Generator.Benchmarks.Roslyn413.csproj @@ -3,14 +3,15 @@ - net8.0;net10.0;net11.0 + net10.0;net11.0 ReactiveUI.Binding.Generator.Benchmarks.Roslyn413 ReactiveUI.Binding.Generator.Benchmarks - + + diff --git a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/AdapterGenerationBenchmarks.cs b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/AdapterGenerationBenchmarks.cs new file mode 100644 index 00000000..b1ac2e9a --- /dev/null +++ b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/AdapterGenerationBenchmarks.cs @@ -0,0 +1,196 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using ReactiveUI.Binding.Benchmarks.Configs; +using ReactiveUI.Binding.Generator.Benchmarks.Support; + +namespace ReactiveUI.Binding.Generator.Benchmarks; + +/// Measures generation for native observations, commands, collection setters and typed conversions. +[Config(typeof(ProfilerConfig))] +public class AdapterGenerationBenchmarks +{ + /// The number of native command bindings in the command corpus. + private const int CommandWorkers = 3; + + /// The number of typed bindings in the conversion corpus. + private const int ConversionWorkers = 4; + + /// The number of binding workers in each other corpus. + private const int PairedWorkers = 2; + + /// The prepared consumer compilation. + private Compilation _compilation = null!; + + /// The dispatch file required by the selected corpus. + private string _dispatchHint = string.Empty; + + /// The validated dispatch size for the fixed consumer input. + private int _dispatchCharacters; + + /// Gets or sets the adapter family to measure. + [Params("Observation", "Commands", "Collections", "Conversions")] + public string Family { get; set; } = "Observation"; + + /// Prepares the consumer and verifies that every generated file compiles. + /// The corpus fails compilation. + [GlobalSetup] + public void Setup() + { + _compilation = CreateCompilation(); + var driver = GeneratorHarness.CreateDriver(false).RunGeneratorsAndUpdateCompilation(_compilation, out var output, out _); + ValidateCompilation(output); + _dispatchHint = Family switch + { + "Observation" => "WhenChangedDispatch.g.cs", + "Commands" => "BindCommandDispatch.g.cs", + _ => "BindToDispatch.g.cs", + }; + ValidateWorkers(driver.GetRunResult()); + _dispatchCharacters = CountDispatchCharacters(driver.GetRunResult()); + } + + /// Runs selection and emission with a fresh generator driver. + /// The dispatch size, to keep the generated work observable. + /// The output differs from the validated corpus. + [Benchmark] + public int Generate() + { + var characters = CountDispatchCharacters(GeneratorHarness.CreateDriver(false).RunGenerators(_compilation).GetRunResult()); + return characters == _dispatchCharacters + ? characters + : throw new InvalidOperationException("A generation pass changed the validated dispatch output."); + } + + /// Rejects output that cannot compile before collecting timings. + /// The generated consumer compilation. + /// The output contains compiler errors. + private static void ValidateCompilation(Compilation output) + { + var errors = new List(); + foreach (var diagnostic in output.GetDiagnostics()) + { + if (diagnostic.Severity == DiagnosticSeverity.Error) + { + errors.Add(diagnostic.ToString()); + } + } + + if (errors.Count != 0) + { + throw new InvalidOperationException(string.Join(Environment.NewLine, errors)); + } + } + + /// Builds the consumer with the same referenced runtime assemblies its output needs. + /// The compilation passed to the generator. + private CSharpCompilation CreateCompilation() + { + var compilation = GeneratorHarness.BuildCompilation(false); + var references = new List(compilation.References); + var paths = new HashSet(StringComparer.Ordinal); + foreach (var reference in references) + { + _ = paths.Add(reference.Display); + } + + foreach (var assembly in new[] + { + typeof(ReactiveUI.Primitives.LinqExtensions).Assembly, + typeof(ReactiveUI.Primitives.SubscribeExtensions).Assembly, + typeof(ReactiveUI.Primitives.Disposables.EmptyDisposable).Assembly, + typeof(ReactiveUI.Primitives.Advanced.ImmediateReturnSignal).Assembly, + typeof(Splat.AppLocator).Assembly, + }) + { + if (paths.Add(assembly.Location)) + { + references.Add(MetadataReference.CreateFromFile(assembly.Location)); + } + } + + return compilation.RemoveAllSyntaxTrees().WithReferences(references) + .AddSyntaxTrees(CSharpSyntaxTree.ParseText(AdapterBenchmarkCorpus.Source(Family), GeneratorHarness.ParseOptions(false))); + } + + /// Counts API workers so unrelated view dispatch cannot satisfy a missing binding. + /// The generated consumer. + /// The selected corpus did not produce every binding worker. + private void ValidateWorkers(GeneratorDriverRunResult result) + { + var expected = Family switch { "Commands" => CommandWorkers, "Conversions" => ConversionWorkers, _ => PairedWorkers }; + var prefix = Family switch { "Observation" => "__WhenChanged_", "Commands" => "__BindCommand_", _ => "__BindTo_" }; + var workers = new HashSet(StringComparer.Ordinal); + foreach (var generated in result.Results[0].GeneratedSources) + { + if (generated.HintName != _dispatchHint) + { + continue; + } + + ValidateMechanisms(generated.SourceText.ToString()); + var root = CSharpSyntaxTree.ParseText(generated.SourceText).GetRoot(); + foreach (var node in root.DescendantNodes()) + { + if (node is MethodDeclarationSyntax method && method.Identifier.ValueText.StartsWith(prefix, StringComparison.Ordinal)) + { + _ = workers.Add(method.Identifier.ValueText); + } + } + } + + if (workers.Count != expected) + { + throw new InvalidOperationException($"{Family}: expected {expected} binding workers, found {workers.Count}."); + } + } + + /// Requires the native operations the corpus is intended to measure. + /// The validated API dispatch. + /// A native mechanism was replaced by a fallback. + private void ValidateMechanisms(string source) + { + string[] required = Family switch + { + "Observation" => ["__UIKitValueObservable", "__UIKitObservable", "AddObserver"], + "Commands" => ["AddTarget", "__AppKitCommandTarget", ".Click +="], + "Collections" => ["SuspendLayout", "AddRange", ".Owner", ".Container"], + _ => [".ToString(", ".Visible", "__value.HasValue", "global::Foundation.NSDate"], + }; + foreach (var operation in required) + { + if (source.IndexOf(operation, StringComparison.Ordinal) < 0) + { + throw new InvalidOperationException($"{Family}: missing generated operation {operation}."); + } + } + } + + /// Rejects a benchmark corpus that fails to select any binding call sites. + /// The generator output. + /// The number of emitted dispatch characters. + /// No dispatch was emitted. + private int CountDispatchCharacters(GeneratorDriverRunResult result) + { + if (!result.Diagnostics.IsEmpty || result.Results[0].Exception is not null) + { + throw new InvalidOperationException("The generator reported diagnostics or an exception."); + } + + var count = 0; + foreach (var generated in result.Results[0].GeneratedSources) + { + if (generated.HintName == _dispatchHint) + { + count += generated.SourceText.Length; + } + } + + return count != 0 ? count : throw new InvalidOperationException("The adapter corpus produced no binding dispatch."); + } +} diff --git a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/Program.cs b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/Program.cs index dd10aeab..f42c0c6e 100644 --- a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/Program.cs +++ b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/Program.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for full license information. using BenchmarkDotNet.Running; +using ReactiveUI.Binding.Benchmarks.Configs; namespace ReactiveUI.Binding.Generator.Benchmarks; @@ -12,6 +13,9 @@ internal static class Program /// Runs the benchmark selected by the command line. /// The command-line arguments passed to the switcher. [STAThread] - internal static void Main(string[] args) => - _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + internal static void Main(string[] args) + { + var count = BenchmarkRunValidation.CountVerified(BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args)); + BenchmarkRunValidation.RequireMeasurements(count, args); + } } diff --git a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/ReactiveUI.Binding.Generator.Benchmarks.csproj b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/ReactiveUI.Binding.Generator.Benchmarks.csproj index b6736990..1f1007d5 100644 --- a/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/ReactiveUI.Binding.Generator.Benchmarks.csproj +++ b/src/benchmarks/ReactiveUI.Binding.Generator.Benchmarks/ReactiveUI.Binding.Generator.Benchmarks.csproj @@ -1,12 +1,13 @@ - net8.0;net10.0;net11.0 + net10.0;net11.0 - + + - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable diff --git a/src/tests/ReactiveUI.Binding.Analyzer.Tests/BindingInvocationAnalyzerTests.NativeCommands.cs b/src/tests/ReactiveUI.Binding.Analyzer.Tests/BindingInvocationAnalyzerTests.NativeCommands.cs new file mode 100644 index 00000000..3a81ce8b --- /dev/null +++ b/src/tests/ReactiveUI.Binding.Analyzer.Tests/BindingInvocationAnalyzerTests.NativeCommands.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Binding.Analyzer.Analyzers; +using ReactiveUI.Binding.Analyzer.Tests.Helpers; + +namespace ReactiveUI.Binding.Analyzer.Tests; + +/// Checks native command contracts against the bindable-event diagnostic. +public partial class BindingInvocationAnalyzerTests +{ + /// The framework command members available in the test compilation. + private const string NativeCommandFramework = """ + namespace Foundation { public class NSObject {} } + namespace ObjCRuntime { public class Selector {} } + namespace AppKit + { + public class NSControl : Foundation.NSObject { public Foundation.NSObject Target { get; set; } } + public class NSCell : NSControl {} + public class NSMenu : NSControl {} + public class NSMenuItem : NSControl {} + public class NSToolbarItem : NSControl {} + } + namespace UIKit + { + public enum UIControlEvent { TouchUpInside } + public class UIControl + { + public bool Enabled { get; set; } + public void AddTarget(System.EventHandler handler, UIControlEvent kind) {} + public void RemoveTarget(System.EventHandler handler, UIControlEvent kind) {} + } + public class UIRefreshControl : UIControl { public event System.EventHandler ValueChanged; } + public class UIBarButtonItem { public bool Enabled { get; set; } public event System.EventHandler Clicked; } + } + public class ClickBase { public event System.EventHandler Click; } + public class DerivedButton : ClickBase {} + """; + + /// Supported native command mechanisms do not require a generic default event. + /// The native control being bound. + /// A task representing the asynchronous test. + [Test] + [Arguments("AppKit.NSControl")] + [Arguments("AppKit.NSCell")] + [Arguments("AppKit.NSMenu")] + [Arguments("AppKit.NSMenuItem")] + [Arguments("AppKit.NSToolbarItem")] + [Arguments("UIKit.UIControl")] + [Arguments("UIKit.UIRefreshControl")] + [Arguments("UIKit.UIBarButtonItem")] + [Arguments("DerivedButton")] + public async Task RXUIBIND007_NativeCommand_NoDiagnostic(string controlType) + { + var source = InteractionCommandPreamble + NativeCommandFramework + $$""" + public class NativeViewModel { public ICommand Save { get; set; } } + public class NativeView : ReactiveUI.Binding.IViewFor + { + public object ViewModel { get; set; } + public {{controlType}} Button { get; } = new {{controlType}}(); + } + public class NativeUsage + { + public void Bind(NativeView view, NativeViewModel model) + { + ReactiveUI.Binding.ReactiveUIBindingExtensions.BindCommand(view, model, x => x.Save, x => x.Button); + } + } + """; + var diagnostics = await AnalyzerTestHelper.GetDiagnosticsAsync(source); + await Assert.That(diagnostics.Any(static diagnostic => diagnostic.Id == NoBindableEventDiagnosticId)).IsFalse(); + } +} diff --git a/src/tests/ReactiveUI.Binding.Analyzer.Tests/ReactiveUI.Binding.Analyzer.Tests.csproj b/src/tests/ReactiveUI.Binding.Analyzer.Tests/ReactiveUI.Binding.Analyzer.Tests.csproj index 4ff5d180..277f4d07 100644 --- a/src/tests/ReactiveUI.Binding.Analyzer.Tests/ReactiveUI.Binding.Analyzer.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.Analyzer.Tests/ReactiveUI.Binding.Analyzer.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/ReactiveUI.Binding.GeneratedCode.TestModels.csproj b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/ReactiveUI.Binding.GeneratedCode.TestModels.csproj index f0673969..b7d26377 100644 --- a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/ReactiveUI.Binding.GeneratedCode.TestModels.csproj +++ b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/ReactiveUI.Binding.GeneratedCode.TestModels.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) enable false true diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/ReactiveUI.Binding.GeneratedCode.Tests.csproj b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/ReactiveUI.Binding.GeneratedCode.Tests.csproj index 116ebe5b..0f767348 100644 --- a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/ReactiveUI.Binding.GeneratedCode.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/ReactiveUI.Binding.GeneratedCode.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413.csproj b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413.csproj index 7b6931ad..ae705fe7 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413.csproj +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413/ReactiveUI.Binding.SourceGenerators.Tests.Roslyn413.csproj @@ -3,7 +3,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable @@ -27,7 +27,7 @@ diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleConversionParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleConversionParityTests.cs new file mode 100644 index 00000000..06aac0ff --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleConversionParityTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes every Foundation date conversion, including nullable failure contracts. +public class AppleConversionParityTests +{ + /// Every registered date direction preserves the native converter's value and null behavior. + /// The input type. + /// The output type. + /// The input expression. + /// The expected assignment behavior. + /// A task representing the asynchronous test. + [Test] + [Arguments("DateTime", "Foundation.NSDate", "date", "target.Value.Value == date")] + [Arguments("DateTime?", "Foundation.NSDate", "date", "target.Value.Value == date")] + [Arguments("DateTime?", "Foundation.NSDate", "null", "target.Writes == 0")] + [Arguments("DateTimeOffset", "Foundation.NSDate", "new DateTimeOffset(date)", "target.Value.Value == date")] + [Arguments("DateTimeOffset?", "Foundation.NSDate", "new DateTimeOffset(date)", "target.Value.Value == date")] + [Arguments("DateTimeOffset?", "Foundation.NSDate", "null", "target.Writes == 0")] + [Arguments("Foundation.NSDate", "DateTime", "new Foundation.NSDate(date)", "target.Value == date")] + [Arguments("Foundation.NSDate", "DateTime?", "new Foundation.NSDate(date)", "target.Value == date")] + [Arguments("Foundation.NSDate", "DateTimeOffset", "new Foundation.NSDate(date)", "target.Value == new DateTimeOffset(date)")] + [Arguments("Foundation.NSDate", "DateTimeOffset?", "new Foundation.NSDate(date)", "target.Value == new DateTimeOffset(date)")] + [Arguments("Foundation.NSDate", "DateTime", "null", "target.Writes == 0")] + [Arguments("Foundation.NSDate", "DateTime?", "null", "target.Writes == 0")] + [Arguments("Foundation.NSDate", "DateTimeOffset", "null", "target.Writes == 0")] + [Arguments("Foundation.NSDate", "DateTimeOffset?", "null", "target.Writes == 0")] + public async Task BindTo_PreservesNativeDateConversion(string from, string to, string input, string expected) + { + var result = TestHelper.RunGenerator(Scenario(from, to, input, expected), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceContains("BindToDispatch.g.cs", "GetAffinityForObjects() <= 8"); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a typed binding whose target counts successful assignments. + /// The input type. + /// The output type. + /// The input expression. + /// The expected result. + /// The executable consumer. + private static string Scenario(string from, string to, string input, string expected) => $$""" + using System; + using ReactiveUI.Binding; + namespace Foundation + { + public class NSDate + { + public DateTime Value { get; } + public NSDate(DateTime value) { Value = value; } + public static explicit operator NSDate(DateTime value) => new NSDate(value); + public static explicit operator DateTime(NSDate value) => value.Value; + } + } + public class Target + { + private {{to}} _value; + public int Writes { get; private set; } + public {{to}} Value { get { return _value; } set { _value = value; Writes++; } } + } + public static class Usage + { + public static bool Run() + { + var date = new DateTime(2026, 1, 2, 3, 4, 5, DateTimeKind.Utc); + IObservable<{{from}}> source = new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{{from}}>({{input}}); + var target = new Target(); + using (source.BindTo(target, x => x.Value)) + { + return {{expected}}; + } + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleObservationParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleObservationParityTests.cs new file mode 100644 index 00000000..77fb2c1e --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/AppleObservationParityTests.cs @@ -0,0 +1,185 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Checks the native Apple property mappings and scores against ReactiveUI. +public class AppleObservationParityTests +{ + /// The native contracts consumed by generated Apple observations. + private const string Framework = """ + using System; + using System.Collections.Generic; + using ReactiveUI.Binding; + namespace Foundation + { + public class NSObject : IDisposable { public void Dispose() {} } + public class NSNotification : NSObject {} + public class NSNotificationCenter + { + public static NSNotificationCenter DefaultCenter { get; } = new NSNotificationCenter(); + private readonly List _entries = new List(); + public int Count => _entries.Count; + public NSObject AddObserver(string name, Action callback, NSObject sender) + { + var entry = new Entry { Name = name, Callback = callback, Sender = sender }; + _entries.Add(entry); + return entry; + } + public void RemoveObserver(NSObject observer) => _entries.Remove((Entry)observer); + public void Post(string name, NSObject sender) + { + foreach (var entry in _entries.ToArray()) + if (entry.Name == name && ReferenceEquals(entry.Sender, sender)) entry.Callback(new NSNotification()); + } + private sealed class Entry : NSObject + { + public string Name; + public Action Callback; + public NSObject Sender; + } + } + } + namespace UIKit + { + public class UIControl : Foundation.NSObject + { + public event EventHandler ValueChanged; + public int Value { get; set; } + public void EmitChange() => ValueChanged?.Invoke(this, EventArgs.Empty); + public bool HasHandlers => ValueChanged != null; + } + public class UITextField : UIControl + { + public static string TextFieldTextDidChangeNotification => "FieldText"; + public int Text { get; set; } + } + public class UITextView : Foundation.NSObject + { + public static string TextDidChangeNotification => "ViewText"; + public int Text { get; set; } + } + public class UIDatePicker : UIControl { public int Date { get; set; } } + public class UISegmentedControl : UIControl { public int SelectedSegment { get; set; } } + public class UISwitch : UIControl { public int On { get; set; } } + public class UITabBar : Foundation.NSObject + { + public event EventHandler ItemSelected; + public int SelectedItem { get; set; } + public void EmitChange() => ItemSelected?.Invoke(this, EventArgs.Empty); + public bool HasHandlers => ItemSelected != null; + } + public class UISearchBar : Foundation.NSObject + { + public event EventHandler TextChanged; + public int Text { get; set; } + public void EmitChange() => TextChanged?.Invoke(this, EventArgs.Empty); + public bool HasHandlers => TextChanged != null; + } + } + namespace AppKit + { + public class NSControl : Foundation.NSObject + { + public static string TextDidChangeNotification => "ControlText"; + public int AlphaValue { get; set; } + public int DoubleValue { get; set; } + public int FloatValue { get; set; } + public int IntValue { get; set; } + public int NintValue { get; set; } + public int ObjectValue { get; set; } + public int StringValue { get; set; } + public int AttributedStringValue { get; set; } + } + } + """; + + /// Each native property emits its own subscription and the score a custom provider must beat. + /// The framework control type. + /// The observed property. + /// The native provider's score. + /// A task representing the asynchronous test. + [Test] + [Arguments("UIKit.UIControl", "Value", 20)] + [Arguments("UIKit.UITextField", "Text", 30)] + [Arguments("UIKit.UITextView", "Text", 30)] + [Arguments("UIKit.UIDatePicker", "Date", 30)] + [Arguments("UIKit.UISegmentedControl", "SelectedSegment", 30)] + [Arguments("UIKit.UISwitch", "On", 30)] + [Arguments("UIKit.UITabBar", "SelectedItem", 30)] + [Arguments("UIKit.UISearchBar", "Text", 30)] + [Arguments("AppKit.NSControl", "AlphaValue", 20)] + [Arguments("AppKit.NSControl", "DoubleValue", 20)] + [Arguments("AppKit.NSControl", "FloatValue", 20)] + [Arguments("AppKit.NSControl", "IntValue", 20)] + [Arguments("AppKit.NSControl", "NintValue", 20)] + [Arguments("AppKit.NSControl", "ObjectValue", 20)] + [Arguments("AppKit.NSControl", "StringValue", 20)] + [Arguments("AppKit.NSControl", "AttributedStringValue", 20)] + public async Task NativeProperty_ObservesAndDetachesWithCorrectAffinity(string type, string property, int affinity) + { + var result = TestHelper.RunGenerator(Scenario(type, property), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceContains("WhenChangedDispatch.g.cs", $"\"{property}\", {affinity}, false"); + await result.HasGeneratedSource("ObservationHelpers.g.cs"); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Static | BindingFlags.Public)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a native subscription that checks filtering, delivery and deterministic detachment. + /// The framework control type. + /// The observed property. + /// The executable consumer source. + private static string Scenario(string type, string property) + { + var notification = type switch + { + "UIKit.UITextField" => "TextFieldTextDidChangeNotification", + "UIKit.UITextView" or "AppKit.NSControl" => "TextDidChangeNotification", + _ => null, + }; + var raise = notification is null ? "value.EmitChange();" : $"Foundation.NSNotificationCenter.DefaultCenter.Post({type}.{notification}, value);"; + var detached = notification is null ? "!value.HasHandlers" : "Foundation.NSNotificationCenter.DefaultCenter.Count == 0"; + return Framework + $$""" + + public static class Usage + { + public static bool Run() + { + var value = new {{type}}(); + var values = new List(); + var subscription = value.WhenChanged(x => x.{{property}}).Subscribe(new Observer(values)); + value.{{property}} = 1; + {{raise}} + {{raise}} + subscription.Dispose(); + subscription.Dispose(); + value.{{property}} = 2; + {{raise}} + return values.Count == 2 && values[0] == 0 && values[1] == 1 && {{detached}}; + } + private sealed class Observer : IObserver + { + private readonly List _values; + public Observer(List values) { _values = values; } + public void OnNext(int value) => _values.Add(value); + public void OnError(Exception error) { throw error; } + public void OnCompleted() {} + } + } + """; + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#BindCommandDispatch.g.verified.cs index 89345241..0d7a9f34 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFF69B2C59DF80(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFF69B2C59DF80(global::SharedScenarios.BindCommand.BasicNoParam.MyButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -81,7 +104,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -106,9 +129,9 @@ void __Handler(object? sender, global::System.EventArgs e) } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.BasicNoParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#BindCommandDispatch.g.verified.cs index 665984bc..7e3fab47 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#BindCommandDispatch.g.verified.cs @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -60,12 +56,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.BasicNoParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFF69B2C59DF80(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFF69B2C59DF80(global::SharedScenarios.BindCommand.BasicNoParam.MyButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -74,7 +97,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -99,9 +122,9 @@ void __Handler(object? sender, global::System.EventArgs e) } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#BindCommandDispatch.g.verified.cs index c46044f6..477abb69 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#BindCommandDispatch.g.verified.cs @@ -45,12 +45,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -62,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); - var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "CurrentItem", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, - true); - var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel), "CurrentItem", 5, false); + var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "CurrentItem", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, true); + var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "CurrentItem", 5, false); var withParameter = withParameterRegistration == null ? (global::System.IObservable)withParameterMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -78,12 +70,39 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, false, true); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFEA737737D210(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFEA737737D210(global::SharedScenarios.BindCommand.ExpressionParam.MyButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -92,14 +111,43 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { @@ -111,18 +159,19 @@ internal static partial class __ReactiveUIGeneratedBindings void __Handler(object? sender, global::System.EventArgs e) { - var param = viewModel.CurrentItem; + var param = __ReadParameter(); if (cmd.CanExecute(param)) { cmd.Execute(param); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); - return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( + new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ExpressionParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#BindCommandDispatch.g.verified.cs index cb1af652..f28a4bc6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#BindCommandDispatch.g.verified.cs @@ -44,12 +44,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ObservableParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -61,12 +57,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00000F3AD26C97AB(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00000F3AD26C97AB(global::SharedScenarios.BindCommand.ObservableParam.MyButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -75,18 +98,43 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - - string? __latestParam = default; - var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( - withParameter, p => global::System.Threading.Volatile.Write(ref __latestParam, p)); - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { @@ -98,16 +146,16 @@ internal static partial class __ReactiveUIGeneratedBindings void __Handler(object? sender, global::System.EventArgs e) { - var param = global::System.Threading.Volatile.Read(ref __latestParam); + var param = __ReadParameter(); if (cmd.CanExecute(param)) { cmd.Execute(param); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CFPFallback_ObservableParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#BindCommandDispatch.g.verified.cs index 060b190d..1f2de0f1 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandProperty.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.CommandProperty.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandProperty.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,6 +63,33 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandProperty.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.CommandProperty.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00000A6C4A2CDDAA(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00000A6C4A2CDDAA(global::SharedScenarios.BindCommand.CommandProperty.WpfLikeButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(5, false)) @@ -81,7 +104,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -89,21 +112,21 @@ internal static partial class __ReactiveUIGeneratedBindings } - var __originalCommand = view.SaveButton.Command; - var __originalParameter = view.SaveButton.CommandParameter; + var __originalCommand = __control.Command; + var __originalParameter = __control.CommandParameter; var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; - view.SaveButton.Command = cmd; + __control.Command = cmd; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial), new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.CommandParameter = __originalParameter; - view.SaveButton.Command = __originalCommand; + __control.CommandParameter = __originalParameter; + __control.Command = __originalCommand; })); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandProperty#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#BindCommandDispatch.g.verified.cs index 0c83fddf..19ff8f73 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#BindCommandDispatch.g.verified.cs @@ -52,12 +52,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -69,12 +65,8 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); - var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "CurrentItem", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel)__o).CurrentItem, - true); - var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel), "CurrentItem", 5, false); + var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "CurrentItem", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel)__o).CurrentItem, true); + var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "CurrentItem", 5, false); var withParameter = withParameterRegistration == null ? (global::System.IObservable)withParameterMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -85,6 +77,33 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyViewModel)__o).CurrentItem, false, true); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.CommandPropertyExprParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00000CE640706F77(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00000CE640706F77(global::SharedScenarios.BindCommand.CommandPropertyExprParam.WpfLikeButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(5, false)) @@ -99,7 +118,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -107,23 +126,23 @@ internal static partial class __ReactiveUIGeneratedBindings } - var __originalCommand = view.SaveButton.Command; - var __originalParameter = view.SaveButton.CommandParameter; + var __originalCommand = __control.Command; + var __originalParameter = __control.CommandParameter; var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; serial.Disposable = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( - withParameter, __p => view.SaveButton.CommandParameter = __p); - view.SaveButton.Command = cmd; + withParameter, __p => __control.CommandParameter = __p); + __control.Command = cmd; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial), new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.CommandParameter = __originalParameter; - view.SaveButton.Command = __originalCommand; + __control.CommandParameter = __originalParameter; + __control.Command = __originalCommand; })); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyExprParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#BindCommandDispatch.g.verified.cs index 17759fe5..868a0dd4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#BindCommandDispatch.g.verified.cs @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyObsParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -68,6 +64,33 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CommandPropertyObsParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.CommandPropertyObsParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_000025BD0B2B014E(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_000025BD0B2B014E(global::SharedScenarios.BindCommand.CommandPropertyObsParam.WpfLikeButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(5, false)) @@ -82,7 +105,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -90,8 +113,8 @@ internal static partial class __ReactiveUIGeneratedBindings } - var __originalCommand = view.SaveButton.Command; - var __originalParameter = view.SaveButton.CommandParameter; + var __originalCommand = __control.Command; + var __originalParameter = __control.CommandParameter; string? __latestParam = default; var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( withParameter, p => global::System.Threading.Volatile.Write(ref __latestParam, p)); @@ -101,15 +124,15 @@ internal static partial class __ReactiveUIGeneratedBindings { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; var param = global::System.Threading.Volatile.Read(ref __latestParam); - view.SaveButton.CommandParameter = param; - view.SaveButton.Command = cmd; + __control.CommandParameter = param; + __control.Command = cmd; if (cmd != null) { serial.Disposable = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( withParameter, p => { global::System.Threading.Volatile.Write(ref __latestParam, p); - view.SaveButton.CommandParameter = p; + __control.CommandParameter = p; }); } }); @@ -118,8 +141,8 @@ internal static partial class __ReactiveUIGeneratedBindings new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial), new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.CommandParameter = __originalParameter; - view.SaveButton.Command = __originalCommand; + __control.CommandParameter = __originalParameter; + __control.Command = __originalCommand; })); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CommandPropertyObsParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#BindCommandDispatch.g.verified.cs index 97127d36..3d451638 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CustomEvent.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.CustomEvent.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CustomEvent.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,6 +63,33 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.CustomEvent.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.CustomEvent.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_000010E3246570D9(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_000010E3246570D9(global::SharedScenarios.BindCommand.CustomEvent.MyButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(3, true)) @@ -80,8 +103,8 @@ internal static partial class __ReactiveUIGeneratedBindings { __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; - __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __serial.Disposable = __customBinder.BindCommandToObject( + __cmd, __control, __paramObs, "MouseUp") ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -106,9 +129,9 @@ void __Handler(object? sender, global::System.EventArgs e) } } - view.SaveButton.MouseUp += __Handler; + __control.MouseUp += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.MouseUp -= __Handler); + __control.MouseUp -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.CustomEvent#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs index b1b319bb..41900242 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var ____commandChanges_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.MyViewModel)__o).Child, - false); - var ____commandChanges_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.DeepCommandPath.MyViewModel), "Child", 5, false); + var ____commandChanges_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.MyViewModel)__o).Child, false); + var ____commandChanges_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Child", 5, false); var ____commandChanges_s0 = ____commandChanges_s0Registration == null ? (global::System.IObservable)____commandChanges_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -66,29 +62,54 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.MyViewModel)__o).Child, false, true); - var ____commandChanges_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(____commandChanges_s0, - __p1 => __p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var ____commandChanges_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(____commandChanges_s0, + __p1 => __p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "SaveCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4707 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4707, __p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveCommand)).Body, "SaveCommand", - false, - 5, (object __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.ChildViewModel)__o).SaveCommand, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__p1, - "SaveCommand", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.ChildViewModel)__o).SaveCommand, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__p1, "SaveCommand", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.ChildViewModel)__o).SaveCommand, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::System.Windows.Input.ICommand))); var __commandChanges = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(____commandChanges_s1); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.DeepCommandPath.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFEA7C178C3571(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFEA7C178C3571(global::SharedScenarios.BindCommand.DeepCommandPath.MyButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -97,7 +118,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -122,9 +143,9 @@ void __Handler(object? sender, global::System.EventArgs e) } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#BindCommandDispatch.g.verified.cs index fc26c100..59475579 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabled.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.EventEnabled.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabled.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabled.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.EventEnabled.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_0000372503979BB9(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_0000372503979BB9(global::SharedScenarios.BindCommand.EventEnabled.WinFormsLikeButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(4, true)) + .HasHigherAffinityPlugin(4, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -81,7 +104,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -95,13 +118,13 @@ internal static partial class __ReactiveUIGeneratedBindings serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; if (cmd == null) { - view.SaveButton.Enabled = false; + __control.Enabled = false; return; } - view.SaveButton.Enabled = cmd.CanExecute(null); + __control.Enabled = cmd.CanExecute(null); global::System.EventHandler __canExecHandler = (s, e) => - view.SaveButton.Enabled = cmd.CanExecute(null); + __control.Enabled = cmd.CanExecute(null); cmd.CanExecuteChanged += __canExecHandler; void __Handler(object? sender, global::System.EventArgs e) @@ -112,10 +135,10 @@ void __Handler(object? sender, global::System.EventArgs e) } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.Click -= __Handler; + __control.Click -= __Handler; cmd.CanExecuteChanged -= __canExecHandler; }); }); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabled#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#BindCommandDispatch.g.verified.cs index 2329d491..db611be0 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#BindCommandDispatch.g.verified.cs @@ -52,12 +52,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -69,12 +65,8 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); - var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "CurrentItem", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel)__o).CurrentItem, - true); - var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel), "CurrentItem", 5, false); + var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "CurrentItem", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel)__o).CurrentItem, true); + var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "CurrentItem", 5, false); var withParameter = withParameterRegistration == null ? (global::System.IObservable)withParameterMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -85,12 +77,39 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyViewModel)__o).CurrentItem, false, true); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.EventEnabledExprParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00001A92FB3E1A78(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00001A92FB3E1A78(global::SharedScenarios.BindCommand.EventEnabledExprParam.WinFormsLikeButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(4, true)) + .HasHigherAffinityPlugin(4, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -99,46 +118,77 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; if (cmd == null) { - view.SaveButton.Enabled = false; + __control.Enabled = false; return; } - view.SaveButton.Enabled = cmd.CanExecute(viewModel.CurrentItem); + var param = __ReadParameter(); + __control.Enabled = cmd.CanExecute(param); global::System.EventHandler __canExecHandler = (s, e) => - view.SaveButton.Enabled = cmd.CanExecute(viewModel.CurrentItem); + __control.Enabled = cmd.CanExecute(__ReadParameter()); cmd.CanExecuteChanged += __canExecHandler; void __Handler(object? sender, global::System.EventArgs e) { - var param = viewModel.CurrentItem; - if (cmd.CanExecute(param)) + var p = __ReadParameter(); + if (cmd.CanExecute(p)) { - cmd.Execute(param); + cmd.Execute(p); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.Click -= __Handler; + __control.Click -= __Handler; cmd.CanExecuteChanged -= __canExecHandler; }); }); - return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( + new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledExprParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#BindCommandDispatch.g.verified.cs index 91ebeaad..c8fa03c4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#BindCommandDispatch.g.verified.cs @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledObsParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -68,12 +64,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.EventEnabledObsParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.EventEnabledObsParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00001723A43D0363(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00001723A43D0363(global::SharedScenarios.BindCommand.EventEnabledObsParam.WinFormsLikeButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(4, true)) + .HasHigherAffinityPlugin(4, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -82,47 +105,72 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - - string? __latestParam = default; - var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( - withParameter, p => global::System.Threading.Volatile.Write(ref __latestParam, p)); - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; if (cmd == null) { - view.SaveButton.Enabled = false; + __control.Enabled = false; return; } - var param = global::System.Threading.Volatile.Read(ref __latestParam); - view.SaveButton.Enabled = cmd.CanExecute(param); + var param = __ReadParameter(); + __control.Enabled = cmd.CanExecute(param); global::System.EventHandler __canExecHandler = (s, e) => - view.SaveButton.Enabled = cmd.CanExecute(global::System.Threading.Volatile.Read(ref __latestParam)); + __control.Enabled = cmd.CanExecute(__ReadParameter()); cmd.CanExecuteChanged += __canExecHandler; void __Handler(object? sender, global::System.EventArgs e) { - var p = global::System.Threading.Volatile.Read(ref __latestParam); + var p = __ReadParameter(); if (cmd.CanExecute(p)) { cmd.Execute(p); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.Click -= __Handler; + __control.Click -= __Handler; cmd.CanExecuteChanged -= __canExecHandler; }); }); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.EventEnabledObsParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#BindCommandDispatch.g.verified.cs index d44088a5..5ca494ed 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#BindCommandDispatch.g.verified.cs @@ -52,12 +52,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -69,12 +65,8 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); - var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "CurrentItem", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, - true); - var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel), "CurrentItem", 5, false); + var withParameterMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "CurrentItem", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, true); + var withParameterRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "CurrentItem", 5, false); var withParameter = withParameterRegistration == null ? (global::System.IObservable)withParameterMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -85,12 +77,39 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyViewModel)__o).CurrentItem, false, true); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.ExpressionParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFEA737737D210(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFEA737737D210(global::SharedScenarios.BindCommand.ExpressionParam.MyButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -99,14 +118,43 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { @@ -118,18 +166,19 @@ internal static partial class __ReactiveUIGeneratedBindings void __Handler(object? sender, global::System.EventArgs e) { - var param = viewModel.CurrentItem; + var param = __ReadParameter(); if (cmd.CanExecute(param)) { cmd.Execute(param); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); - return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( + new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ExpressionParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#BindCommandDispatch.g.verified.cs index 783c4aea..9102632b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.NoEvent.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.NoEvent.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.NoEvent.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,6 +63,33 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "Label", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.NoEvent.MyView)__o).Label, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "Label", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Label)).Body, + "Label", + (object __o) => ((global::SharedScenarios.BindCommand.NoEvent.MyView)__o).Label, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_000006F07B8E70EE(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_000006F07B8E70EE(global::SharedScenarios.BindCommand.NoEvent.PlainControl __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(-1, false)) @@ -81,7 +104,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.Label, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.NoEvent#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#BindCommandDispatch.g.verified.cs index 8b9f7ff9..11da46a0 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#BindCommandDispatch.g.verified.cs @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindCommand.ObservableParam.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -68,12 +64,39 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::SharedScenarios.BindCommand.ObservableParam.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_00000F3AD26C97AB(__control, commandObs, withParameter); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_00000F3AD26C97AB(global::SharedScenarios.BindCommand.ObservableParam.MyButton __control, global::System.IObservable commandObs, global::System.IObservable withParameter) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker - .HasHigherAffinityPlugin(3, true)) + .HasHigherAffinityPlugin(3, false)) { var __customBinder = global::ReactiveUI.Binding.CommandBinding.CommandBinderService - .GetBinder(true); + .GetBinder(false); if (__customBinder != null) { var __serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); @@ -82,18 +105,43 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = new global::ReactiveUI.Primitives.Signals.MapSignal(withParameter, __p => __p); __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); } } - - string? __latestParam = default; - var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe( - withParameter, p => global::System.Threading.Volatile.Write(ref __latestParam, p)); - + string __latestParam = default(string); + var __parameterGate = new object(); + var __hasParameter = false; + var __argumentCached = false; + object __argument = null; + object __ReadParameter() + { + lock (__parameterGate) + { + if (!__hasParameter) + { + return null; + } + if (!__argumentCached) + { + __argument = __latestParam; + __argumentCached = true; + } + return __argument; + } + } + var __paramSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(withParameter, __parameter => + { + lock (__parameterGate) + { + __latestParam = __parameter; + __hasParameter = true; + __argumentCached = false; + } + }); var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { @@ -105,16 +153,16 @@ internal static partial class __ReactiveUIGeneratedBindings void __Handler(object? sender, global::System.EventArgs e) { - var param = global::System.Threading.Volatile.Read(ref __latestParam); + var param = __ReadParameter(); if (cmd.CanExecute(param)) { cmd.Execute(param); } } - view.SaveButton.Click += __Handler; + __control.Click += __Handler; serial.Disposable = new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => - view.SaveButton.Click -= __Handler); + __control.Click -= __Handler); }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, __paramSub), serial); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.ObservableParam#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#BindDispatch.g.verified.cs index 9f15e956..011a38a9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -56,12 +56,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).FirstName, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel), "FirstName", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).FirstName, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "FirstName", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -72,12 +68,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "FirstNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).FirstNameText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.TwoSameTypeBindings.MyView), "FirstNameText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "FirstNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).FirstNameText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "FirstNameText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -88,37 +80,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).FirstNameText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(string))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.FirstNameText, value)) { return; } view.FirstNameText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (string)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.FirstName, value)) { return; } viewModel.FirstName = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.FirstName / x => x.FirstNameText"); return new global::ReactiveUI.Binding.ReactiveBinding( @@ -147,12 +225,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).LastName, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel), "LastName", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).LastName, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "LastName", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -163,12 +237,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyViewModel)__o).LastName, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "LastNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).LastNameText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.TwoSameTypeBindings.MyView), "LastNameText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "LastNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).LastNameText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "LastNameText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -179,37 +249,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.TwoSameTypeBindings.MyView)__o).LastNameText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(string))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.LastNameText, value)) { return; } view.LastNameText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (string)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.LastName, value)) { return; } viewModel.LastName = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.LastName / x => x.LastNameText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#BindDispatch.g.verified.cs index d08989f7..9f6d85ad 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Name, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.MultipleBindings.MyViewModel), "Name", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Name, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Name, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).NameText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.MultipleBindings.MyView), "NameText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).NameText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "NameText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -83,37 +75,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).NameText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(string))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.NameText, value)) { return; } view.NameText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (string)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Name, value)) { return; } viewModel.Name = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Name / x => x.NameText"); return new global::ReactiveUI.Binding.ReactiveBinding( @@ -165,12 +243,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Age, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.MultipleBindings.MyViewModel), "Age", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Age, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Age", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -181,12 +255,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyViewModel)__o).Age, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "AgeText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).AgeText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.MultipleBindings.MyView), "AgeText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "AgeText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).AgeText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "AgeText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -197,37 +267,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.MultipleBindings.MyView)__o).AgeText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 1) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(int))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(int), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (int)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.AgeText, value)) { return; } view.AgeText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (int)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Age, value)) { return; } viewModel.Age = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Age / x => x.AgeText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.MB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#BindDispatch.g.verified.cs index 73746cc6..66f731d6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyStringToString.MyView), "NameText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "NameText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -83,37 +75,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(string))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.NameText, value)) { return; } view.NameText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (string)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Name, value)) { return; } viewModel.Name = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Name / x => x.NameText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#BindDispatch.g.verified.cs index 9f576d24..f71c2f21 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyStringToString.MyView), "NameText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "NameText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -83,37 +75,123 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyStringToString.MyView)__o).NameText, false, true); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)vmObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + vmObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)viewObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + viewObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedForward, v => new global::System.ValueTuple(true, v, default(string))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(__convertedReverse, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.NameText, value)) { return; } view.NameText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (string)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Name, value)) { return; } viewModel.Name = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Name / x => x.NameText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#BindDispatch.g.verified.cs index cc7f4cfc..cd7c1e49 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -53,12 +53,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConverters.MyViewModel)__o).Count, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyWithConverters.MyViewModel), "Count", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConverters.MyViewModel)__o).Count, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Count", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -69,12 +65,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConverters.MyViewModel)__o).Count, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "CountText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConverters.MyView)__o).CountText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyWithConverters.MyView), "CountText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "CountText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConverters.MyView)__o).CountText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "CountText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -88,36 +80,42 @@ internal static partial class __ReactiveUIGeneratedBindings var vmBind = new global::ReactiveUI.Primitives.Signals.MapSignal(vmObs, viewModelToViewConverter); var viewBind = new global::ReactiveUI.Primitives.Signals.MapSignal(viewObs, viewToViewModelConverter); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmBind, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewBind, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(vmBind, v => new global::System.ValueTuple(true, v, default(int))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(viewBind, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var __routed = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__sides, view); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__routed, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.CountText, value)) { return; } view.CountText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (int)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Count, value)) { return; } viewModel.Count = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Count / x => x.CountText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#BindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#BindDispatch.g.verified.cs index 20c60e1a..14899163 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#BindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#BindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindDispatch.g.cs +//HintName: BindDispatch.g.cs // #pragma warning disable #nullable enable @@ -54,12 +54,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, - true); - var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyViewModel), "Count", 5, false); + var vmObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, true); + var vmObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Count", 5, false); var vmObs = vmObsRegistration == null ? (global::System.IObservable)vmObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -70,12 +66,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, false, true); - var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - view, - "CountText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyView)__o).CountText, - true); - var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyView), "CountText", 5, false); + var viewObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "CountText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.Bind.SinglePropertyWithConvertersAndScheduler.MyView)__o).CountText, true); + var viewObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "CountText", 5, false); var viewObs = viewObsRegistration == null ? (global::System.IObservable)viewObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -91,35 +83,41 @@ internal static partial class __ReactiveUIGeneratedBindings var vmBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(__vmSelected, scheduler); var viewBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(__viewSelected, scheduler); - var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(vmBind, v => new global::ReactiveUI.Binding.BindingChange(v, true)); - var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal(viewBind, v => new global::ReactiveUI.Binding.BindingChange(v, false)); - var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal(__vmTagged, __viewTagged); + var __vmTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(vmBind, v => new global::System.ValueTuple(true, v, default(int))); + var __viewTagged = new global::ReactiveUI.Primitives.Signals.MapSignal>(viewBind, v => new global::System.ValueTuple(false, default(string), v)); + var __sides = new global::ReactiveUI.Primitives.Advanced.MergeSignal>(__vmTagged, __viewTagged); var changed = new global::ReactiveUI.Binding.Observables.AppliedChangeObservable(); var disposable = global::ReactiveUI.Binding.BindingErrors.Subscribe(__sides, __change => { - if (__change.FromViewModel) + if (__change.Item1) { - var value = (string)__change.Value; + var value = __change.Item2; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(view.CountText, value)) { return; } view.CountText = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, true)); + } } else { - var value = (int)__change.Value; + var value = __change.Item3; if (global::System.Collections.Generic.EqualityComparer.Default.Equals(viewModel.Count, value)) { return; } viewModel.Count = value; + if (changed.HasObservers) + { + changed.OnNext(new global::ReactiveUI.Binding.BindingChange(value, false)); + } } - - changed.OnNext(__change); }, "x => x.Count / x => x.CountText"); return new global::ReactiveUI.Binding.ReactiveBinding( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#BindInteractionDispatch.g.verified.cs index 879ea12c..d6f25edb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - viewModel, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel)__o).Confirm, - true); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel), "Confirm", 5, false); + var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(viewModel, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel)__o).Confirm, true); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 5, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs index 1ff1141d..fe3b8361 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var __interactionObs_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, - false); - var __interactionObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel), "Child", 5, false); + var __interactionObs_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, false); + var __interactionObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Child", 5, false); var __interactionObs_s0 = __interactionObs_s0Registration == null ? (global::System.IObservable)__interactionObs_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -59,20 +55,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, false, true); - var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, - __p1 => __p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, + __p1 => __p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4180 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration4180, __p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Confirm)).Body, "Confirm", - false, - 5, (object __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__p1, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__p1, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, false)) : (global::System.IObservable>)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal>(default(global::ReactiveUI.Binding.Interaction))); var interactionObs = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__interactionObs_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#BindInteractionDispatch.g.verified.cs index 05e2be96..a32a2fdd 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,8 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.UnchangingPropertyObservable>(viewModel.Confirm); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.NonINPCViewModel.MyViewModel), "Confirm", 0, false); + var interactionObsMechanism = new __UnchangingPropertyObservable>(viewModel, __source => ((global::SharedScenarios.BindInteraction.NonINPCViewModel.MyViewModel)__source).Confirm); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 1, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#ObservationHelpers.g.verified.cs new file mode 100644 index 00000000..469e2d39 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_NonINPCViewModel#ObservationHelpers.g.verified.cs @@ -0,0 +1,31 @@ +//HintName: ObservationHelpers.g.cs +// +#pragma warning disable +#nullable enable + +using System; + +namespace ReactiveUI.Binding +{ + internal static partial class __ReactiveUIGeneratedBindings + { private sealed class __UnchangingPropertyObservable : global::System.IObservable + { + private readonly TSource _source; + private readonly global::System.Func _getter; + internal __UnchangingPropertyObservable(TSource source, global::System.Func getter) + { + _source = source; + _getter = getter; + } + public global::System.IDisposable Subscribe(global::System.IObserver observer) + { + if (observer == null) + { + throw new global::System.ArgumentNullException(nameof(observer)); + } + observer.OnNext(_getter(_source)); + return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + } + } + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#BindInteractionDispatch.g.verified.cs index 6e21f3bd..25405997 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - viewModel, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel)__o).Confirm, - true); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel), "Confirm", 5, false); + var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(viewModel, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel)__o).Confirm, true); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 5, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_ObservableHandler#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs index 53d8319f..a6bb5ef8 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -44,12 +44,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var __interactionObs_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, - false); - var __interactionObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel), "Child", 5, false); + var __interactionObs_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, false); + var __interactionObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Child", 5, false); var __interactionObs_s0 = __interactionObs_s0Registration == null ? (global::System.IObservable)__interactionObs_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -60,20 +56,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.MyViewModel)__o).Child, false, true); - var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, - __p1 => __p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, + __p1 => __p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4394 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration4394, __p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Confirm)).Body, "Confirm", - false, - 5, (object __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__p1, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__p1, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.DeepPropertyPath.ChildViewModel)__o).Confirm, false)) : (global::System.IObservable>)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal>(default(global::ReactiveUI.Binding.Interaction))); var interactionObs = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__interactionObs_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#BindInteractionDispatch.g.verified.cs index 4d75b2c8..f0d17eca 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -44,8 +44,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.UnchangingPropertyObservable>(viewModel.Confirm); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.NonINPCViewModel.MyViewModel), "Confirm", 0, false); + var interactionObsMechanism = new __UnchangingPropertyObservable>(viewModel, __source => ((global::SharedScenarios.BindInteraction.NonINPCViewModel.MyViewModel)__source).Confirm); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 1, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#ObservationHelpers.g.verified.cs new file mode 100644 index 00000000..bd9ef21e --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.NonINPCViewModel#ObservationHelpers.g.verified.cs @@ -0,0 +1,31 @@ +//HintName: ObservationHelpers.g.cs +// +#pragma warning disable +#nullable enable + +using System; + +namespace ReactiveUI.Binding.Generated.TestAssembly +{ + internal static partial class __ReactiveUIGeneratedBindings + { private sealed class __UnchangingPropertyObservable : global::System.IObservable + { + private readonly TSource _source; + private readonly global::System.Func _getter; + internal __UnchangingPropertyObservable(TSource source, global::System.Func getter) + { + _source = source; + _getter = getter; + } + public global::System.IDisposable Subscribe(global::System.IObserver observer) + { + if (observer == null) + { + throw new global::System.ArgumentNullException(nameof(observer)); + } + observer.OnNext(_getter(_source)); + return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + } + } + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#BindInteractionDispatch.g.verified.cs index 5671853c..e46de2ca 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -44,12 +44,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - viewModel, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel)__o).Confirm, - true); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel), "Confirm", 5, false); + var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(viewModel, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.ObservableHandler.MyViewModel)__o).Confirm, true); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 5, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.ObservableHandler#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#BindInteractionDispatch.g.verified.cs index 8c64fb13..36a46359 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#BindInteractionDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindInteractionDispatch.g.cs +//HintName: BindInteractionDispatch.g.cs // #pragma warning disable #nullable enable @@ -44,12 +44,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return serial; } - var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - viewModel, - "Confirm", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel)__o).Confirm, - true); - var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel), "Confirm", 5, false); + var interactionObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(viewModel, "Confirm", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindInteraction.TaskHandler.MyViewModel)__o).Confirm, true); + var interactionObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Confirm", 5, false); var interactionObs = interactionObsRegistration == null ? (global::System.IObservable>)interactionObsMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.TaskHandler#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#BindOneWayDispatch.g.verified.cs index cb3e045d..c3c167d9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,13 +33,13 @@ internal static partial class __ReactiveUIGeneratedBindings if (sourcePropertyExpression == "x => x.Text" && targetPropertyExpression == "x => x.Name") { - return __BindOneWay_000018C063784945(source, target); + return __BindOneWay_000018C0637849E0(source, target); } throw new global::System.InvalidOperationException( "No generated binding found. Ensure the expression is an inline lambda for compile-time optimization."); } - private static global::System.IDisposable __BindOneWay_000018C063784945(global::TestApp.MyAppleView source, global::TestApp.MyViewModel target) + private static global::System.IDisposable __BindOneWay_000018C0637849E0(global::TestApp.MyAppleView source, global::TestApp.MyViewModel target) { // BindOneWay: Text -> Name if (global::ReactiveUI.Binding.BindingHooks.Any @@ -58,13 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new __KVOObservable( - (global::Foundation.NSObject)source, - "text", - (global::Foundation.NSObject __o) => ((global::TestApp.MyAppleView)__o).Text, - true, - false); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyAppleView), "Text", 15, false); + var sourceObsMechanism = new __KVOObservable((global::Foundation.NSObject)source, "text", (global::Foundation.NSObject __o) => ((global::TestApp.MyAppleView)__o).Text, true, false); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Text", 15, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -75,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyAppleView)__o).Text, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index e9344599..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,26 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: KVO - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#ObservationHelpers.g.verified.cs index 5455f052..a0297819 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#ObservationHelpers.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.KVO_NSObject_Source#ObservationHelpers.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ObservationHelpers.g.cs +//HintName: ObservationHelpers.g.cs // #pragma warning disable #nullable enable diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#BindOneWayDispatch.g.verified.cs index 6eed9521..6caa492c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Name, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -136,12 +172,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Age, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel), "Age", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Age, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Age", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -152,7 +184,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleBindings.MyViewModel)__o).Age, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#BindOneWayDispatch.g.verified.cs index 14c1ba9f..30438560 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -63,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel), "FirstName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "FirstName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -79,7 +75,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -111,12 +147,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel), "LastName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "LastName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -127,7 +159,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#BindOneWayDispatch.g.verified.cs index d7a8a46e..5cc070a9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -56,12 +56,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel), "FirstName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "FirstName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -72,7 +68,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -104,12 +140,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel), "LastName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "LastName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -120,7 +152,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#BindOneWayDispatch.g.verified.cs index 866f417e..b48b371e 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.ReactiveObjectSource.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.ReactiveObjectSource.MyViewModel), "Name", 10, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.ReactiveObjectSource.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 10, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.ReactiveObjectSource.MyViewModel)__o).Name, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index e16e9f8f..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,26 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#BindOneWayDispatch.g.verified.cs index 3fdee11d..dee7f3fb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyIntToInt.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyIntToInt.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyIntToInt.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyIntToInt.MyViewModel)__o).Count, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_I2I#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#BindOneWayDispatch.g.verified.cs index f8207cff..3d8bbeec 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#BindOneWayDispatch.g.verified.cs index 01a044cd..f0c94cf0 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#BindOneWayDispatch.g.verified.cs index dd15cbdf..2d8acf5f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -59,12 +59,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithConverter.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyWithConverter.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithConverter.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#BindOneWayDispatch.g.verified.cs index 41b83b95..e85e8a83 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -60,12 +60,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithConverterAndScheduler.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyWithConverterAndScheduler.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithConverterAndScheduler.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#BindOneWayDispatch.g.verified.cs index 63117788..f9c0f6eb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -59,12 +59,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindOneWay.SinglePropertyWithScheduler.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -75,7 +71,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindOneWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, false, true); - var bindObs = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(sourceObs, scheduler); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var bindObs = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(__convertedForward, scheduler); return global::ReactiveUI.Binding.BindingErrors.Subscribe(bindObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#BindTwoWayDispatch.g.verified.cs index 9535c302..4052e2af 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,13 +33,13 @@ internal static partial class __ReactiveUIGeneratedBindings if (sourcePropertyExpression == "x => x.Name" && targetPropertyExpression == "x => x.Text") { - return __BindTwoWay_7FFFD5B3E84DC0B6(source, target); + return __BindTwoWay_7FFFD5B3E84DC151(source, target); } throw new global::System.InvalidOperationException( "No generated binding found. Ensure the expression is an inline lambda for compile-time optimization."); } - private static global::System.IDisposable __BindTwoWay_7FFFD5B3E84DC0B6(global::TestApp.MyViewModel source, global::TestApp.MyAppleView target) + private static global::System.IDisposable __BindTwoWay_7FFFD5B3E84DC151(global::TestApp.MyViewModel source, global::TestApp.MyAppleView target) { // BindTwoWay: Name <-> Text if (global::ReactiveUI.Binding.BindingHooks.Any @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,13 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new __KVOObservable( - (global::Foundation.NSObject)target, - "text", - (global::Foundation.NSObject __o) => ((global::TestApp.MyAppleView)__o).Text, - true, - false); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyAppleView), "Text", 15, false); + var targetObsMechanism = new __KVOObservable((global::Foundation.NSObject)target, "text", (global::Foundation.NSObject __o) => ((global::TestApp.MyAppleView)__o).Text, true, false); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "Text", 15, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -91,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyAppleView)__o).Text, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index e9344599..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,26 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: KVO - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#ObservationHelpers.g.verified.cs index 5455f052..a0297819 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#ObservationHelpers.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.KVO_NSObject_Target#ObservationHelpers.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ObservationHelpers.g.cs +//HintName: ObservationHelpers.g.cs // #pragma warning disable #nullable enable diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#BindTwoWayDispatch.g.verified.cs index 629e5605..fd4f922e 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleBindings.MyView), "NameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).NameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -165,12 +237,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Age, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel), "Age", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Age, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Age", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -181,12 +249,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyViewModel)__o).Age, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "AgeDisplay", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).AgeDisplay, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleBindings.MyView), "AgeDisplay", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "AgeDisplay", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).AgeDisplay, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "AgeDisplay", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -197,8 +261,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleBindings.MyView)__o).AgeDisplay, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 1) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#BindTwoWayDispatch.g.verified.cs index 97a8d20e..4e36f993 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -63,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel), "FirstName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "FirstName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -79,12 +75,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "FirstNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView), "FirstNameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "FirstNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "FirstNameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -95,8 +87,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -140,12 +212,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel), "LastName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "LastName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -156,12 +224,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "LastNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView), "LastNameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "LastNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "LastNameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -172,8 +236,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#BindTwoWayDispatch.g.verified.cs index 2a1e28cf..e0b124bf 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -56,12 +56,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel), "FirstName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "FirstName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -72,12 +68,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "FirstNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView), "FirstNameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "FirstNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "FirstNameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -88,8 +80,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).FirstNameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { @@ -133,12 +205,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel), "LastName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "LastName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -149,12 +217,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyViewModel)__o).LastName, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "LastNameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView), "LastNameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "LastNameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "LastNameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -165,8 +229,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MultipleSameTypeBindings.MyView)__o).LastNameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MSTB_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindOneWayDispatch.g.verified.cs index 8938606c..a5b99cbd 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindOneWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindOneWayDispatch.g.cs +//HintName: BindOneWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "ReadOnlyCount", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).ReadOnlyCount, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel), "ReadOnlyCount", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "ReadOnlyCount", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).ReadOnlyCount, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "ReadOnlyCount", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).ReadOnlyCount, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindTwoWayDispatch.g.verified.cs index 80414ab7..40dfe560 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyView), "NameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.MixedWithBindOneWay.MyView)__o).NameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.MixedWithBindOneWay#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#BindTwoWayDispatch.g.verified.cs index 7ffca0c8..ecda315b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyViewModel), "Name", 10, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 10, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyView), "NameText", 10, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 10, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.ReactiveObjectBoth.MyView)__o).NameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index dcc60788..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.ReactiveObject_Both#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#BindTwoWayDispatch.g.verified.cs index 8fa051eb..12831571 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyViewModel)__o).Count, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "DisplayCount", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyView)__o).DisplayCount, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyView), "DisplayCount", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "DisplayCount", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyView)__o).DisplayCount, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "DisplayCount", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyIntToInt.MyView)__o).DisplayCount, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 1) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_I2I#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#BindTwoWayDispatch.g.verified.cs index 95ed8060..29e61eb0 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView), "NameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#BindTwoWayDispatch.g.verified.cs index 83da48d9..94c1ee95 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,12 +63,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView), "NameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -83,8 +75,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyStringToString.MyView)__o).NameText, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#BindTwoWayDispatch.g.verified.cs index 2d16ed91..07d456ae 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -60,12 +60,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -76,12 +72,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyViewModel)__o).Count, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "CountText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyView)__o).CountText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyView), "CountText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "CountText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConverters.MyView)__o).CountText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "CountText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#BindTwoWayDispatch.g.verified.cs index 1ffaa587..ed4d2cfb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -61,12 +61,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -77,12 +73,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyViewModel)__o).Count, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "CountText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyView)__o).CountText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyView), "CountText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "CountText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithConvertersAndScheduler.MyView)__o).CountText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "CountText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WCSched#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#BindTwoWayDispatch.g.verified.cs index 8abc711e..83a1e42f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#BindTwoWayDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: BindTwoWayDispatch.g.cs +//HintName: BindTwoWayDispatch.g.cs // #pragma warning disable #nullable enable @@ -59,12 +59,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -75,12 +71,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "NameText", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyView)__o).NameText, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyView), "NameText", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "NameText", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyView)__o).NameText, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "NameText", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -91,8 +83,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.BindTwoWay.SinglePropertyWithScheduler.MyView)__o).NameText, false, true); - var sourceBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(sourceObs, scheduler); - var targetBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(targetObs, scheduler); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var sourceBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(__convertedForward, scheduler); + var targetBind = global::ReactiveUI.Primitives.LinqExtensions.ObserveOn(__convertedReverse, scheduler); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(sourceBind, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BTG.SP_WithScheduler#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#BindToDispatch.g.verified.cs index 40d9c035..48cbf012 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#BindToDispatch.g.verified.cs @@ -34,17 +34,50 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_7FFFF0EE063CE32A(global::System.IObservable source, global::SharedScenarios.BindTo.DifferingTypes.MyView target) { // BindTo: observable -> Caption - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = null; + if (__convertedSourceConverter == null) { - if (global::ReactiveUI.Binding.Fallback.RuntimeBindingConverter.TryConvert(value, null, null, out var __converted)) + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) { - if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, __converted)) + __convertedSourceConverter = null; + } + } + var __convertedSource = global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = null; + if (__convertedSourceConverter != null) { - return; + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + return (false, default(string)); } - - target.Caption = __converted; + return true ? (true, __hint is int __precision ? __value.ToString("D" + __precision.ToString()) : __hint is string __format ? __value.ToString(__format) : __value.ToString()) : (false, default(string)); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target), value => + { + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, value)) + { + return; } + + target.Caption = value; }, "x => x.Caption"); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.DifferingTypes#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#BindToDispatch.g.verified.cs index a048027d..7cf9b4f4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#BindToDispatch.g.verified.cs @@ -34,7 +34,47 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_000016A7FA446B38(global::System.IObservable source, global::SharedScenarios.BindTo.SameTypeString.MyView target) { // BindTo: observable -> Caption - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = null; + if (__convertedSourceConverter == null) + { + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) + { + __convertedSourceConverter = null; + } + } + var __convertedSource = __convertedSourceConverter == null ? (global::System.IObservable)source : global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = null; + if (__convertedSourceConverter != null) + { + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target), value => { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, value)) { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#BindToDispatch.g.verified.cs index 78240527..6d6a5f7f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#BindToDispatch.g.verified.cs @@ -33,7 +33,47 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_000016A7FA446B38(global::System.IObservable source, global::SharedScenarios.BindTo.SameTypeString.MyView target) { // BindTo: observable -> Caption - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = null; + if (__convertedSourceConverter == null) + { + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) + { + __convertedSourceConverter = null; + } + } + var __convertedSource = __convertedSourceConverter == null ? (global::System.IObservable)source : global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = null; + if (__convertedSourceConverter != null) + { + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target), value => { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, value)) { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.SameTypeString_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#BindToDispatch.g.verified.cs index a73a3357..09fb227e 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#BindToDispatch.g.verified.cs @@ -35,17 +35,50 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_7FFFF5030A82AD3A(global::System.IObservable source, global::SharedScenarios.BindTo.WithConverterOverride.MyView target, global::ReactiveUI.Binding.IBindingTypeConverter converterOverride) { // BindTo: observable -> Caption - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = converterOverride; + if (__convertedSourceConverter == null) { - if (global::ReactiveUI.Binding.Fallback.RuntimeBindingConverter.TryConvert(value, null, converterOverride, out var __converted)) + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) { - if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, __converted)) + __convertedSourceConverter = null; + } + } + var __convertedSource = global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = null; + if (__convertedSourceConverter != null) { - return; + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + return (false, default(string)); } - - target.Caption = __converted; + return true ? (true, __hint is int __precision ? __value.ToString("D" + __precision.ToString()) : __hint is string __format ? __value.ToString(__format) : __value.ToString()) : (false, default(string)); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target), value => + { + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, value)) + { + return; } + + target.Caption = value; }, "x => x.Caption"); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WCOverride#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#BindToDispatch.g.verified.cs index 04fdf492..f0d767f4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#BindToDispatch.g.verified.cs @@ -35,17 +35,50 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_7FFFC579FC797776(global::System.IObservable source, global::SharedScenarios.BindTo.WithConversionHint.MyView target, object conversionHint) { // BindTo: observable -> Caption - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = null; + if (__convertedSourceConverter == null) { - if (global::ReactiveUI.Binding.Fallback.RuntimeBindingConverter.TryConvert(value, conversionHint, null, out var __converted)) + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) { - if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, __converted)) + __convertedSourceConverter = null; + } + } + var __convertedSource = global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = conversionHint; + if (__convertedSourceConverter != null) { - return; + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + return (false, default(string)); } - - target.Caption = __converted; + return true ? (true, __hint is int __precision ? __value.ToString("D" + __precision.ToString()) : __hint is string __format ? __value.ToString(__format) : __value.ToString()) : (false, default(string)); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target), value => + { + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Caption, value)) + { + return; } + + target.Caption = value; }, "x => x.Caption"); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BToG.WithConversionHint#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/BindCommandCodeGeneratorHelperTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/BindCommandCodeGeneratorHelperTests.cs index 0ce0eb79..6ed724c1 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/BindCommandCodeGeneratorHelperTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/BindCommandCodeGeneratorHelperTests.cs @@ -274,13 +274,14 @@ public async Task EventEnabledPlugin_EmitBinding_ObservableParam_EmitsCanExecute await Assert.That(result).Contains("view.SaveButton.Enabled = cmd.CanExecute(param)"); await Assert.That(result).Contains("cmd.CanExecuteChanged += __canExecHandler"); await Assert.That(result).Contains(ViewSaveButtonClickHandlerFragment); - await Assert.That(result).Contains("Volatile.Read(ref __latestParam)"); + await Assert.That(result).Contains("lock (__parameterGate)"); + await Assert.That(result).Contains("__ReadParameter()"); } - /// Verifies EventEnabledBindingPlugin emits event+Enabled with expression parameter. + /// Expression parameters use their observed stream when synchronizing enabled state. /// A task representing the asynchronous test operation. [Test] - public async Task EventEnabledPlugin_EmitBinding_ExpressionParam_EmitsDirectPropertyAccess() + public async Task EventEnabledPlugin_EmitBinding_ExpressionParam_ReadsObservedParameter() { var paramPath = new EquatableArray( [ModelFactory.CreatePropertyPathSegment(ParamName)]); @@ -296,7 +297,7 @@ public async Task EventEnabledPlugin_EmitBinding_ExpressionParam_EmitsDirectProp plugin.EmitBinding(sb, inv, ViewSaveButtonName, false); var result = sb.ToString(); - await Assert.That(result).Contains("view.SaveButton.Enabled = cmd.CanExecute(viewModel.Param)"); + await Assert.That(result).Contains("view.SaveButton.Enabled = cmd.CanExecute(__ReadParameter())"); await Assert.That(result).Contains(ViewSaveButtonClickHandlerFragment); await Assert.That(result).DoesNotContain(VolatileName); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.DeepChain.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.DeepChain.cs index f102d094..9b60b3d6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.DeepChain.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.DeepChain.cs @@ -47,17 +47,18 @@ public async Task GenerateDeepChainObservation_TwoLevelChain_GeneratesSwitchMapS public async Task GenerateDeepChainObservation_BeforeChange_SuppressesRepeatedValues() { var sb = new StringBuilder(); + var leafInfo = ModelFactory.CreateClassBindingInfo(fullyQualifiedName: AddressTypeName, implementsINPChanging: true); var paths = new EquatableArray>([ new([ ModelFactory.CreatePropertyPathSegment(AddressName, AddressTypeName), - ModelFactory.CreatePropertyPathSegment("City", StringTypeName, AddressTypeName) + ModelFactory.CreatePropertyPathSegment("City", StringTypeName, AddressTypeName) with { DeclaringTypeInfo = leafInfo } ]) ]); var inv = ModelFactory.CreateInvocationInfo( propertyPaths: paths, isBeforeChange: true, expressionTexts: new EquatableArray([CitySelector])); - var classInfo = ModelFactory.CreateClassBindingInfo(implementsINPChanging: true); + var classInfo = ModelFactory.CreateClassBindingInfo(implementsINPC: true, implementsINPChanging: true); ObservationCodeGenerator.GenerateDeepChainObservation(sb, inv, classInfo, true); @@ -118,11 +119,12 @@ public async Task GenerateDeepChainVariable_ThreeLevelChain_OnlyTheLeafSuppresse public async Task GenerateDeepChainVariable_BeforeChange_GeneratesPropertyChangingCode() { var sb = new StringBuilder(); + var leafInfo = ModelFactory.CreateClassBindingInfo(fullyQualifiedName: AddressTypeName, implementsINPChanging: true); var path = new EquatableArray([ ModelFactory.CreatePropertyPathSegment(AddressName, AddressTypeName), - ModelFactory.CreatePropertyPathSegment("City", StringTypeName, AddressTypeName) + ModelFactory.CreatePropertyPathSegment("City", StringTypeName, AddressTypeName) with { DeclaringTypeInfo = leafInfo } ]); - var classInfo = ModelFactory.CreateClassBindingInfo(implementsINPChanging: true); + var classInfo = ModelFactory.CreateClassBindingInfo(implementsINPC: true, implementsINPChanging: true); ObservationCodeGenerator.GenerateDeepChainVariable(sb, path, classInfo, true, PropObs0Local); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.MethodGeneration.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.MethodGeneration.cs index 6760c6a3..b6457a68 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.MethodGeneration.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.MethodGeneration.cs @@ -211,13 +211,10 @@ public async Task Generate_WithInvocations_ReturnsNonNullSource() await Assert.That(result!).Contains("__WhenChanged_"); } - /// - /// Verifies Generate with invocations but no matching class info skips affinity check - /// and does not emit ObservationAffinityChecker (covers null branches for groupClassInfo/groupPlugin). - /// + /// A registered provider can observe a property even when no generated mechanism matches its type. /// A task representing the asynchronous test operation. [Test] - public async Task Generate_NoMatchingClassInfo_SkipsAffinityCheck() + public async Task Generate_NoMatchingClassInfo_AllowsCustomProvider() { var inv = ModelFactory.CreateInvocationInfo(); @@ -228,7 +225,7 @@ public async Task Generate_NoMatchingClassInfo_SkipsAffinityCheck() WhenChangedName); await Assert.That(result).IsNotNull(); - await Assert.That(result!).DoesNotContain(ObservationAffinityCheckerName); + await Assert.That(result!).Contains(ObservationAffinityCheckerName); } /// Verifies GenerateConcreteOverload with multiple invocations in a group generates else if branching. @@ -375,7 +372,7 @@ public async Task GenerateShallowObservableVariable_BeforeChange_AsksTheRegistra ObservationCodeGenerator.GenerateShallowObservableVariable(sb, path, ModelFactory.CreateClassBindingInfo(implementsINPChanging: true), true, ObservedPropertyVariable); - await Assert.That(sb.ToString()).Contains("\", 0, true);"); + await Assert.That(sb.ToString()).Contains("\", 5, true);"); } /// A binding reads through the same choice, so its generated write is untouched by a registration. diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/TypeReferenceArityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/TypeReferenceArityTests.cs new file mode 100644 index 00000000..b5bda5ed --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/TypeReferenceArityTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Binding.SourceGenerators.CodeGeneration; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.CodeGeneration; + +/// Checks generic arity matching for shared and flavour-specific runtime types. +public class TypeReferenceArityTests +{ + /// Nested syntax does not count as another outer type argument. + /// The syntax after a type name. + /// The outer generic arity. + /// A task representing the asynchronous test. + [Test] + [Arguments("", 0)] + [Arguments(".Create()", 0)] + [Arguments("", 1)] + [Arguments("", 2)] + [Arguments("<(int, string), int[,]>", 2)] + [Arguments(">, (int, int)>", 2)] + public async Task Read_CountsOuterArguments(string suffix, int expected) => + await Assert.That(TypeReferenceArity.Read(suffix, 0)).IsEqualTo(expected); + + /// CLR metadata retains the distinction between non-generic and generic names. + /// The metadata type name. + /// The declared generic arity. + /// A task representing the asynchronous test. + [Test] + [Arguments("Signal", 0)] + [Arguments("Signal`1", 1)] + [Arguments("Tuple`16", 16)] + public async Task FromMetadata_ReadsArity(string name, int expected) => + await Assert.That(TypeReferenceArity.FromMetadata(name, name.IndexOf('`'))).IsEqualTo(expected); +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionEdgeParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionEdgeParityTests.cs new file mode 100644 index 00000000..084a245b --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionEdgeParityTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes nullable, URI, equality and identity conversions without the runtime conversion engine. +public class ConversionEdgeParityTests +{ + /// A generated conversion delivers accepted values and preserves the target when it declines. + /// The stream value type. + /// The destination type. + /// The incoming value expression. + /// The destination's initial value expression. + /// The expected destination expression. + /// The conversion hint expression. + /// A task representing the asynchronous test. + [Test] + [Arguments("int", "int?", "42", "null", "(int?)42", "null")] + [Arguments("int?", "int", "42", "99", "42", "null")] + [Arguments("int?", "int", "null", "99", "99", "null")] + [Arguments("decimal", "decimal?", "1.5m", "null", "(decimal?)1.5m", "null")] + [Arguments("string", "Uri", "\"relative/path\"", "null", "new Uri(\"relative/path\", UriKind.Relative)", "null")] + [Arguments("string", "Uri", "null", "new Uri(\"initial\", UriKind.Relative)", "new Uri(\"initial\", UriKind.Relative)", "null")] + [Arguments("Uri", "string", "new Uri(\"relative/path\", UriKind.Relative)", "null", "\"relative/path\"", "null")] + [Arguments("Uri", "string", "null", "\"initial\"", "\"initial\"", "null")] + [Arguments("object", "bool", "\"same\"", "false", "true", "\"same\"")] + [Arguments("object", "bool", "\"different\"", "true", "false", "\"same\"")] + [Arguments("string", "string", "null", "\"initial\"", "null", "null")] + [Arguments("string", "string", "\"value\"", "\"initial\"", "\"value\"", "null")] + public async Task BindTo_HandlesConversionEdges(string sourceType, string targetType, string value, string initial, string expected, string hint) + { + var result = TestHelper.RunGenerator( + $$""" + using System; + using ReactiveUI.Binding; + public class Target { public {{targetType}} Value { get; set; } = {{initial}}; } + public static class Usage + { + public static bool Run() + { + var target = new Target(); + IObservable<{{sourceType}}> source = new ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{{sourceType}}>({{value}}); + using (source.BindTo(target, x => x.Value, conversionHint: {{hint}})) + return object.Equals(target.Value, {{expected}}); + } + } + """, + LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceDoesNotContain("BindToDispatch.g.cs", "RuntimeBindingConverter"); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionOverrideParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionOverrideParityTests.cs new file mode 100644 index 00000000..a8f75cea --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ConversionOverrideParityTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes converter voting with private per-test registries. +public class ConversionOverrideParityTests +{ + /// Registrations win only with higher scores; explicit overrides win regardless of score. + /// The registered converter score. + /// Whether the call supplies the converter directly. + /// Whether the converter accepts the incoming value. + /// A task representing the asynchronous test. + [Test] + [Arguments(1, false, true)] + [Arguments(2, false, true)] + [Arguments(3, false, true)] + [Arguments(3, false, false)] + [Arguments(1, true, true)] + [Arguments(1, true, false)] + public async Task BindTo_SelectsConverterByAffinity(int score, bool explicitOverride, bool accepts) + { + var result = TestHelper.RunGenerator(Scenario(score, explicitOverride, accepts, false), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// Identity assignments allow a higher-affinity converter to replace the value. + /// The custom identity converter's affinity. + /// A task representing the asynchronous test. + [Test] + [Arguments(1)] + [Arguments(2)] + public async Task BindTo_IdentityStillHonorsCustomConverters(int score) + { + var result = TestHelper.RunGenerator(Scenario(score, false, true, true), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// Runs the generated consumer with private converter state. + /// The generated consumer. + /// A task representing the asynchronous test. + private static async Task AssertRuns(GeneratorTestResult result) + { + var (assembly, context) = TestHelper.EmitAndLoad(result, true); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a typed custom visibility converter whose object adapter must never run. + /// The custom affinity. + /// Whether the converter is an explicit argument. + /// Whether conversion succeeds. + /// Whether source and destination have the same type. + /// The executable consumer. + private static string Scenario(int score, bool explicitOverride, bool accepts, bool identity) + { + var sourceType = identity ? "Visibility" : "bool"; + return $$""" + using System; + using ReactiveUI.Binding; + using Visibility = System.Windows.Visibility; + namespace System.Windows { public enum Visibility { Visible, Collapsed, Hidden } } + public class Target { public Visibility Value { get; set; } = Visibility.Collapsed; } + public class Converter : IBindingTypeConverter<{{sourceType}}, Visibility> + { + public Type FromType => typeof({{sourceType}}); + public Type ToType => typeof(Visibility); + public int Calls; + public int GetAffinityForObjects() => {{score}}; + public bool TryConvert({{sourceType}} value, object hint, out Visibility result) + { + if (!object.Equals(hint, "hint")) throw new InvalidOperationException("Hint was lost"); + Calls++; + result = Visibility.Hidden; + return {{(accepts ? "true" : "false")}}; + } + public bool TryConvertTyped(object value, object hint, out object result) + => throw new InvalidOperationException("Typed converter was boxed"); + } + public static class Usage + { + public static bool Run() + { + var converter = new Converter(); + BindingConverters.Current.TypedConverters.Register(converter); + var target = new Target(); + IObservable<{{sourceType}}> source = + new ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{{sourceType}}>({{(identity ? "Visibility.Visible" : "true")}}); + using (source.BindTo(target, x => x.Value, conversionHint: "hint", converterOverride: {{(explicitOverride ? "converter" : "null")}})) + { + var custom = {{(explicitOverride ? "true" : "false")}} || {{score}} > {{(identity ? "1" : "2")}}; + var expected = custom ? {{(accepts ? "Visibility.Hidden" : "Visibility.Collapsed")}} : Visibility.Visible; + return target.Value == expected && converter.Calls == (custom ? 1 : 0); + } + } + } + """; + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Generators/BindingGeneratorTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Generators/BindingGeneratorTests.cs new file mode 100644 index 00000000..b67f295d --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Generators/BindingGeneratorTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.Generators; + +/// Tests that generated infrastructure follows the operations a consumer uses. +public class BindingGeneratorTests +{ + /// The output containing selected native observation helpers. + private const string ObservationHelpersFile = "ObservationHelpers.g.cs"; + + /// The native callback contract and a control exposing native and ordinary properties. + private const string WinUISource = """ + namespace Microsoft.UI.Xaml + { + public class DependencyProperty { } + public class DependencyObject + { + public long RegisterPropertyChangedCallback(DependencyProperty dp, DependencyPropertyChangedCallback callback) => 0; + public void UnregisterPropertyChangedCallback(DependencyProperty dp, long token) { } + } + public delegate void DependencyPropertyChangedCallback(DependencyObject sender, DependencyProperty dp); + } + namespace Framework + { + public class Control : Microsoft.UI.Xaml.DependencyObject, System.ComponentModel.INotifyPropertyChanged + { + public static readonly Microsoft.UI.Xaml.DependencyProperty NativeProperty = new Microsoft.UI.Xaml.DependencyProperty(); + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + public string Native { get; set; } + public string Ordinary { get; set; } + } + } + """; + + /// Unused native types do not require observation helpers. + /// A task representing the asynchronous test. + [Test] + public async Task Initialize_UnusedNativeType_EmitsNoObservationHelpers() + { + var result = TestHelper.RunGenerator(ApplePlatformSource.TypeDetectionScenario()); + + await Assert.That(result.GeneratedSources.ContainsKey(ObservationHelpersFile)).IsFalse(); + await result.CompilationSucceeds(); + } + + /// Compile-time observation selection needs no runtime registration output. + /// A task representing the asynchronous test. + [Test] + public async Task Initialize_ObservableType_EmitsNoBinderRegistration() + { + var result = TestHelper.RunGenerator(ApplePlatformSource.TypeDetectionScenario()); + + await Assert.That(result.GeneratedSources.ContainsKey("GeneratedBinderRegistration.g.cs")).IsFalse(); + } + + /// A framework reference alone does not require a view-thread invoker. + /// A task representing the asynchronous test. + [Test] + public async Task Initialize_UnusedFrameworkReference_EmitsNoViewThreadInvokers() + { + const string source = """ + namespace System.Windows.Threading + { + public class DispatcherObject { } + } + """; + + var result = TestHelper.RunGenerator(source); + + await Assert.That(result.GeneratedSources.ContainsKey("ViewThreadInvokers.g.cs")).IsFalse(); + await result.CompilationSucceeds(); + } + + /// A property using INPC does not need its type's native callback helper. + /// A task representing the asynchronous test. + [Test] + public async Task Initialize_OrdinaryProperty_EmitsNoNativeHelper() + { + const string scenario = """ + using ReactiveUI.Binding; + public static class Scenario + { + public static System.IObservable Observe(Framework.Control control) + => control.WhenChanged(x => x.Ordinary); + } + """; + var result = TestHelper.RunGenerator(scenario + WinUISource, LanguageVersion.CSharp10); + + await Assert.That(result.GeneratedSources.ContainsKey(ObservationHelpersFile)).IsFalse(); + await result.CompilationSucceeds(); + } + + /// Native properties from referenced assemblies get their selected helper, including chain links. + /// The type the observation starts from. + /// The observed access chain. + /// A task representing the asynchronous test. + [Test] + [Arguments("Framework.Control", "x => x.Native")] + [Arguments("Parent", "x => x.Child.Native")] + public async Task Initialize_ReferencedNativeProperty_EmitsCompilingHelper(string sourceType, string propertyPath) + { + var framework = TestHelper.CreateCompilation(WinUISource, LanguageVersion.CSharp10) + .WithAssemblyName("NativeFramework"); + var source = $$""" + using ReactiveUI.Binding; + public class Parent + { + public Framework.Control Child { get; set; } + } + public static class Scenario + { + public static System.IObservable Observe({{sourceType}} control) + => control.WhenChanged({{propertyPath}}); + } + """; + var compilation = TestHelper.CreateCompilation(source, LanguageVersion.CSharp10) + .AddReferences(framework.ToMetadataReference()); + var result = TestHelper.RunGenerator(compilation, LanguageVersion.CSharp10, null, false); + + await Assert.That(result.GeneratedSources.ContainsKey(ObservationHelpersFile)).IsTrue(); + await result.CompilationSucceeds(); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/ApplePlatformSource.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/ApplePlatformSource.cs index 6331c028..b0190f41 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/ApplePlatformSource.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/ApplePlatformSource.cs @@ -22,6 +22,10 @@ internal static class ApplePlatformSource private const string FoundationStub = """ namespace Foundation { + public sealed class ExportAttribute : Attribute + { + public ExportAttribute(string selector) {} + } public class NSString { private readonly string _value; @@ -50,6 +54,7 @@ namespace TestApp { public class MyAppleView : Foundation.NSObject { + [Foundation.Export("text")] public string Text { get; set; } } } @@ -73,6 +78,7 @@ namespace TestApp { public class MyAppleView : Foundation.NSObject { + [Foundation.Export("text")] public string Text { get; set; } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CollectibleAssemblyLoadContext.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CollectibleAssemblyLoadContext.cs index d91f0ce5..b216b4b9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CollectibleAssemblyLoadContext.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CollectibleAssemblyLoadContext.cs @@ -19,7 +19,15 @@ public CollectibleAssemblyLoadContext() { } + /// Gets whether converter registrations belong to this test's private runtime instance. + internal bool IsolateBindingRuntime { get; init; } + /// - protected override Assembly? Load(AssemblyName assemblyName) => - Default.LoadFromAssemblyName(assemblyName); + protected override Assembly? Load(AssemblyName assemblyName) + { + var runtime = typeof(ReactiveUIBindingExtensions).Assembly; + return IsolateBindingRuntime && assemblyName.Name == runtime.GetName().Name + ? LoadFromAssemblyPath(runtime.Location) + : Default.LoadFromAssemblyName(assemblyName); + } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CommandExtractorHelperTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CommandExtractorHelperTests.cs index c914f616..f7bf61ce 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CommandExtractorHelperTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/CommandExtractorHelperTests.cs @@ -86,7 +86,7 @@ public class PlainControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasCommandProperties(classSymbol, out var hasParam); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.HasCommandProperties(classSymbol, out var hasParam); await Assert.That(result).IsFalse(); await Assert.That(hasParam).IsFalse(); @@ -113,7 +113,7 @@ public class ButtonControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasCommandProperties(classSymbol, out var hasParam); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.HasCommandProperties(classSymbol, out var hasParam); await Assert.That(result).IsTrue(); await Assert.That(hasParam).IsFalse(); @@ -141,7 +141,7 @@ public class ButtonControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasCommandProperties(classSymbol, out var hasParam); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.HasCommandProperties(classSymbol, out var hasParam); await Assert.That(result).IsTrue(); await Assert.That(hasParam).IsTrue(); @@ -168,7 +168,7 @@ public class ReadOnlyCommandControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasCommandProperties(classSymbol, out var hasParam); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.HasCommandProperties(classSymbol, out var hasParam); await Assert.That(result).IsFalse(); await Assert.That(hasParam).IsFalse(); @@ -194,7 +194,7 @@ public class PlainControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasEnabledProperty(classSymbol); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.EventEnabledBindingPlugin.HasEnabledProperty(classSymbol); await Assert.That(result).IsFalse(); } @@ -219,7 +219,7 @@ public class WinFormsControl var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasEnabledProperty(classSymbol); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.EventEnabledBindingPlugin.HasEnabledProperty(classSymbol); await Assert.That(result).IsTrue(); } @@ -244,7 +244,7 @@ public class ControlWithStringEnabled var model = compilation.GetSemanticModel(tree); var classSymbol = GetFirstClassSymbol(tree, model); - var result = CommandExtractor.HasEnabledProperty(classSymbol); + var result = ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.EventEnabledBindingPlugin.HasEnabledProperty(classSymbol); await Assert.That(result).IsFalse(); } @@ -462,28 +462,28 @@ public void Method(System.Linq.Expressions.Expression> /// A task representing the asynchronous test operation. [Test] public async Task IsSettableICommandProperty_SettableCommand_ReturnsTrue() => - await Assert.That(CommandExtractor.IsSettableICommandProperty( + await Assert.That(ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.IsSettableICommandProperty( await PropertyAsync("public ICommand Command { get; set; }", "Command"))).IsTrue(); /// Verifies that a read-only Command property is rejected. /// A task representing the asynchronous test operation. [Test] public async Task IsSettableICommandProperty_ReadOnlyCommand_ReturnsFalse() => - await Assert.That(CommandExtractor.IsSettableICommandProperty( + await Assert.That(ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.IsSettableICommandProperty( await PropertyAsync("public ICommand Command { get; }", "Command"))).IsFalse(); /// Verifies that a settable CommandParameter property is recognized. /// A task representing the asynchronous test operation. [Test] public async Task IsSettableCommandParameterProperty_Settable_ReturnsTrue() => - await Assert.That(CommandExtractor.IsSettableCommandParameterProperty( + await Assert.That(ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.IsSettableCommandParameterProperty( await PropertyAsync("public object CommandParameter { get; set; }", "CommandParameter"))).IsTrue(); /// Verifies that a differently named property is not treated as the command parameter. /// A task representing the asynchronous test operation. [Test] public async Task IsSettableCommandParameterProperty_OtherName_ReturnsFalse() => - await Assert.That(CommandExtractor.IsSettableCommandParameterProperty( + await Assert.That(ReactiveUI.Binding.SourceGenerators.Plugins.CommandBinding.CommandPropertyBindingPlugin.IsSettableCommandParameterProperty( await PropertyAsync("public object Tag { get; set; }", "Tag"))).IsFalse(); /// Verifies that a method with no withParameter reports neither overload shape. diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeObservationTestModels.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeObservationTestModels.cs new file mode 100644 index 00000000..095ab6d2 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeObservationTestModels.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Binding.SourceGenerators.Models; +using ReactiveUI.Binding.SourceGenerators.Plugins; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +/// Supplies verified native member data to the static emission tests. +internal static class NativeObservationTestModels +{ + /// Creates the native property data consumed by one mechanism's emitter. + /// The selected mechanism. + /// The property name. + /// The property type. + /// The concrete owner type. + /// The property and its verified native members. + internal static PropertyPathSegment CreateSegment( + IObservationPlugin plugin, + string name, + string type, + string declaringType = "global::TestApp.MyViewModel") + { + var kind = plugin.ObservationKind; + var eventName = kind == "Android" ? AndroidWidgetEvents.FindChangeEvent(name) : $"{name}Changed"; + var candidates = kind == "Android" && eventName is null + ? default + : new EquatableArray([new(kind, plugin.Affinity, new([new(eventName!, "global::System.EventHandler")]), null, null, null)]); + var property = new ObservablePropertyInfo(name, type, true, false, kind == "WinUIDP", eventName is not null, false, candidates, true); + var info = ModelFactory.CreateClassBindingInfo(fullyQualifiedName: declaringType, properties: new([property])); + return new(name, type, declaringType, true, info); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeTypeIdentityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeTypeIdentityTests.cs new file mode 100644 index 00000000..e52d6eda --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/NativeTypeIdentityTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using ReactiveUI.Binding.SourceGenerators.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +/// Checks allocation-free matching against nested, generic and nullable native symbols. +public class NativeTypeIdentityTests +{ + /// Names must match every namespace, nesting and arity segment. + /// The metadata lookup name. + /// The proposed native name. + /// Whether it identifies the native type. + /// A task representing the asynchronous test. + [Test] + [Arguments("Native.Control", "Native.Control", true)] + [Arguments("Native.Control+Collection", "Native.Control.Collection", true)] + [Arguments("Native.Control", "Other.Control", false)] + [Arguments("Native.Control+Collection", "Native.Collection", false)] + [Arguments("Native.Control`1", "Native.Control", false)] + [Arguments("Native.Control", "Control", false)] + public async Task Matches_UsesCompleteTypeIdentity(string metadataName, string expectedName, bool matches) + { + var compilation = TestHelper.CreateCompilation("namespace Native { public class Control { public class Collection {} } public class Control {} }"); + var type = compilation.GetTypeByMetadataName(metadataName)!; + await Assert.That(NativeTypeIdentity.Matches(type, expectedName)).IsEqualTo(matches); + await Assert.That(NativeTypeIdentity.Matches(type.WithNullableAnnotation(NullableAnnotation.Annotated), expectedName)).IsEqualTo(matches); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/TestHelper.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/TestHelper.cs index f28fdec6..fd0711ac 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/TestHelper.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Helpers/TestHelper.cs @@ -351,11 +351,19 @@ public static GeneratorTestResult RunGenerator( /// The generator test result to emit. /// The loaded assembly and the load context (dispose context to unload). /// Thrown when emission fails. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LoadedAssembly EmitAndLoad(GeneratorTestResult result) => EmitAndLoad(result, false); + + /// Loads generated bindings with optionally isolated converter registrations. + /// The generated consumer compilation. + /// Whether the binding runtime has private static state. + /// The consumer assembly and its collectible context. + /// The consumer could not be emitted. [SuppressMessage( "Security", "SES1402:Assembly loaded from an unverifiable source", Justification = "loads the compilation this test just emitted, in-process, into a collectible context")] - public static LoadedAssembly EmitAndLoad(GeneratorTestResult result) + public static LoadedAssembly EmitAndLoad(GeneratorTestResult result, bool isolateBindingRuntime) { ArgumentNullException.ThrowIfNull(result); @@ -375,7 +383,7 @@ public static LoadedAssembly EmitAndLoad(GeneratorTestResult result) } assemblyStream.Position = 0; - var context = new CollectibleAssemblyLoadContext(); + var context = new CollectibleAssemblyLoadContext { IsolateBindingRuntime = isolateBindingRuntime }; var assembly = context.LoadFromStream(assemblyStream); return new(assembly, context); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#InvokeCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#InvokeCommandDispatch.g.verified.cs index 99b700b7..d9ea1c53 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#InvokeCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty#InvokeCommandDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: InvokeCommandDispatch.g.cs +//HintName: InvokeCommandDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var commandObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel)__o).Save, - true); - var commandObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel), "Save", 5, false); + var commandObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel)__o).Save, true); + var commandObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "Save", 5, false); var commandObs = commandObsRegistration == null ? (global::System.IObservable)commandObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#InvokeCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#InvokeCommandDispatch.g.verified.cs index 25f1c8c4..4ad26b34 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#InvokeCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.CommandProperty_CFP#InvokeCommandDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: InvokeCommandDispatch.g.cs +//HintName: InvokeCommandDispatch.g.cs // #pragma warning disable @@ -39,12 +39,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var commandObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel)__o).Save, - true); - var commandObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel), "Save", 5, false); + var commandObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.CommandProperty.MyViewModel)__o).Save, true); + var commandObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "Save", 5, false); var commandObs = commandObsRegistration == null ? (global::System.IObservable)commandObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs index 2e3b8b5d..ef8f336d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: InvokeCommandDispatch.g.cs +//HintName: InvokeCommandDispatch.g.cs // #pragma warning disable #nullable enable @@ -43,12 +43,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandObs_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.MyViewModel)__o).Child, - false); - var __commandObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.InvokeCommand.DeepCommandPath.MyViewModel), "Child", 5, false); + var __commandObs_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.MyViewModel)__o).Child, false); + var __commandObs_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "Child", 5, false); var __commandObs_s0 = __commandObs_s0Registration == null ? (global::System.IObservable)__commandObs_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -59,20 +55,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.MyViewModel)__o).Child, false, true); - var __commandObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__commandObs_s0, - __p1 => __p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __commandObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__commandObs_s0, + __p1 => __p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Save", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4003 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4003, __p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Save)).Body, "Save", - false, - 5, (object __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.ChildViewModel)__o).Save, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__p1, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.ChildViewModel)__o).Save, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__p1, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.InvokeCommand.DeepCommandPath.ChildViewModel)__o).Save, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::System.Windows.Input.ICommand))); var commandObs = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__commandObs_s1); return global::ReactiveUI.Binding.CommandBinding.CommandInvoker.Invoke(source, commandObs); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/KvoEligibilityParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/KvoEligibilityParityTests.cs new file mode 100644 index 00000000..bde04a1f --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/KvoEligibilityParityTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Checks native assembly membership and exported selectors for KVO eligibility. +public class KvoEligibilityParityTests +{ + /// Native declarations use Cocoa naming while consumer CLR properties fall through. + /// The selected property. + /// The native key, or null for an ordinary consumer property. + /// A task representing the asynchronous test. + [Test] + [Arguments("Title", "title")] + [Arguments("Enabled", "isEnabled")] + [Arguments("IsHidden", "isHidden")] + [Arguments("Count", "nativeCount")] + [Arguments("Managed", null)] + public async Task InspectProperty_RespectsNativeDeclaration(string name, string? expected) + { + var framework = TestHelper.CompileToReference( + """ + namespace Foundation + { + public class NSObject {} + public class ExportAttribute : System.Attribute { public ExportAttribute(string name) {} } + public class Native : NSObject + { + public string Title { get; set; } + public bool Enabled { get; set; } + public bool IsHidden { get; set; } + public int Count { [Export("nativeCount")] get; set; } + } + } + """, + "AppleFramework", + LanguageVersion.CSharp10); + var compilation = TestHelper.CreateCompilation( + "public class Consumer : Foundation.Native { public int Managed { get; set; } }", + LanguageVersion.CSharp10, + false, + "ConsumerAssembly", + [framework]); + var owner = compilation.GetTypeByMetadataName("Consumer")!; + var property = (IPropertySymbol)PlatformSymbols.FindMember(owner, name)!; + var selected = new KVOObservationPlugin().InspectProperty(owner, property); + await Assert.That(selected?.KvoKeyPath).IsEqualTo(expected); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/MismatchedPropertyTypeBindingTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/MismatchedPropertyTypeBindingTests.cs index 2d943ed1..b5ba2d17 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/MismatchedPropertyTypeBindingTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/MismatchedPropertyTypeBindingTests.cs @@ -14,8 +14,8 @@ namespace ReactiveUI.Binding.SourceGenerators.Tests; /// public class MismatchedPropertyTypeBindingTests { - /// The runtime entry point a generated binding resolves its conversion through. - private const string ConverterName = "RuntimeBindingConverter"; + /// The registry checked before using a generated conversion. + private const string ConverterName = "BindingConverters.Current.TypedConverters.TryGetConverter"; /// A view model exposing a number bound to a view exposing text, with no converter supplied. private const string NumberToTextSource = """ @@ -106,7 +106,7 @@ public async Task Bind_BetweenDifferentPropertyTypes_ResolvesConvertersBothWays( { var result = TestHelper.RunGenerator(TwoWayNumberToTextSource, LanguageVersion.CSharp10); - await result.GeneratedSourceContains("BindDispatch.g.cs", $"{ConverterName}.TryConvert"); - await result.GeneratedSourceContains("BindDispatch.g.cs", $"{ConverterName}.TryConvert"); + await result.GeneratedSourceContains("BindDispatch.g.cs", $"{ConverterName}(typeof(int), typeof(string))"); + await result.GeneratedSourceContains("BindDispatch.g.cs", $"{ConverterName}(typeof(string), typeof(int))"); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Models/ModelEqualityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Models/ModelEqualityTests.cs index a626824d..753a7caa 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Models/ModelEqualityTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Models/ModelEqualityTests.cs @@ -21,136 +21,9 @@ public class ModelEqualityTests /// The fully qualified name of the MyViewModel type used by these tests. private const string MyViewModelTypeName = "global::TestApp.MyViewModel"; - /// The MyViewModel name these tests generate against. - private const string MyViewModelName = "MyViewModel"; - /// The string name these tests generate against. private const string StringName = "string"; - // ─────────────────────────────────────────────────────────────────────────── - // ObservableTypeInfo - // ─────────────────────────────────────────────────────────────────────────── - /// Verifies that two ObservableTypeInfo instances with identical values are equal. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_Equals_SameValues_ReturnsTrue() - { - const int Affinity = 21; - var properties = new EquatableArray([ - ModelFactory.CreateObservablePropertyInfo() - ]); - - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - var right = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left.Equals(right)).IsTrue(); - } - - /// Verifies that two ObservableTypeInfo instances with different values are not equal. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_Equals_DifferentValues_ReturnsFalse() - { - const int Affinity = 21; - var properties = new EquatableArray([ - ModelFactory.CreateObservablePropertyInfo() - ]); - - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - var right = new ObservableTypeInfo("global::TestApp.OtherType", "OtherType", "INPC", Affinity, true, properties); - - await Assert.That(left.Equals(right)).IsFalse(); - } - - /// Verifies that ObservableTypeInfo.Equals returns false when compared to null object. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_Equals_ObjectNull_ReturnsFalse() - { - const int Affinity = 21; - var properties = new EquatableArray([]); - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left.Equals(NullReference())).IsFalse(); - } - - /// Verifies that ObservableTypeInfo.Equals returns false when compared to a different type. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_Equals_ObjectWrongType_ReturnsFalse() - { - const int Affinity = 21; - var properties = new EquatableArray([]); - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left.Equals(StringName)).IsFalse(); - } - - /// Verifies that two ObservableTypeInfo instances with same values produce the same hash code. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_GetHashCode_SameValues_AreEqual() - { - const int Affinity = 21; - var properties = new EquatableArray([ - ModelFactory.CreateObservablePropertyInfo() - ]); - - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - var right = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left.GetHashCode()).IsEqualTo(right.GetHashCode()); - } - - /// Verifies that operator== returns true for ObservableTypeInfo instances with same values. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_OperatorEquals_SameValues_ReturnsTrue() - { - const int Affinity = 21; - var properties = new EquatableArray([ - ModelFactory.CreateObservablePropertyInfo() - ]); - - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - var right = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left == right).IsTrue(); - } - - /// Verifies that operator!= returns true for ObservableTypeInfo instances with different values. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_OperatorNotEquals_DifferentValues_ReturnsTrue() - { - const int Affinity = 21; - const int Affinity2 = 24; - var properties = new EquatableArray([]); - - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - var right = new ObservableTypeInfo( - MyViewModelTypeName, - MyViewModelName, - "ReactiveObject", - Affinity2, - true, - properties); - - await Assert.That(left != right).IsTrue(); - } - - /// Verifies that ObservableTypeInfo.ToString contains the type name. - /// A task representing the asynchronous test operation. - [Test] - public async Task ObservableTypeInfo_ToString_ContainsTypeName() - { - const int Affinity = 21; - var properties = new EquatableArray([]); - var left = new ObservableTypeInfo(MyViewModelTypeName, MyViewModelName, "INPC", Affinity, true, properties); - - await Assert.That(left.ToString()).Contains("ObservableTypeInfo"); - } - // ─────────────────────────────────────────────────────────────────────────── // ObservablePropertyInfo // ─────────────────────────────────────────────────────────────────────────── diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandOverrideParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandOverrideParityTests.cs new file mode 100644 index 00000000..472af399 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandOverrideParityTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Checks native command votes against scoped custom registrations. +public class NativeCommandOverrideParityTests +{ + /// The generated generic event score. + private const int ExplicitEventAffinity = 4; + + /// The generated Android native score. + private const int AndroidAffinity = 9; + + /// A native control that also offers command properties. + private const string ViewSource = """ + public class BoundControl : Android.Views.View + { + public ICommand Command { get; set; } + public object CommandParameter { get; set; } + } + public class View : IViewFor + { + public Model ViewModel { get; set; } + object IViewFor.ViewModel { get { return ViewModel; } set { ViewModel = (Model)value; } } + public BoundControl Control { get; } = new BoundControl(); + } + """; + + /// A registration must beat the generated score and receives the selected event overload. + /// The custom binder's score. + /// Whether the caller selected Click explicitly. + /// A task representing the asynchronous test. + [Test] + [Arguments(8, false)] + [Arguments(9, false)] + [Arguments(10, false)] + [Arguments(3, true)] + [Arguments(4, true)] + [Arguments(5, true)] + public async Task BindCommand_CustomProviderRequiresHigherAffinity(int score, bool explicitEvent) + { + var result = TestHelper.RunGenerator(Scenario(score, explicitEvent), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a custom registration with distinct default and named-event behavior. + /// The registration's affinity. + /// Whether the binding supplies an event name. + /// The executable consumer. + private static string Scenario(int score, bool explicitEvent) => NativeCommandParityTests.Framework + ViewSource + $$""" + public class Binder : ICreatesCommandBinding + { + public int Calls; + public int Disposals; + public string Event; + public bool EventTarget; + public int GetAffinityForObject(bool hasEventTarget) + { + EventTarget = hasEventTarget; + return typeof(T) == typeof(BoundControl) ? {{score}} : 0; + } + public IDisposable BindCommandToObject(ICommand command, T target, IObservable parameter) where T : class + => Bind(command, "default"); + public IDisposable BindCommandToObject(ICommand command, T target, IObservable parameter, string eventName) where T : class + => Bind(command, eventName); + public IDisposable BindCommandToObject(ICommand command, T target, IObservable parameter, + Action> add, Action> remove) where T : class where TArgs : EventArgs + => throw new InvalidOperationException("Unexpected delegate overload"); + private IDisposable Bind(ICommand command, string name) + { + Calls++; + Event = name; + return new ReactiveUI.Primitives.Disposables.ActionDisposable(() => Disposals++); + } + } + public static class Usage + { + public static bool Run() + { + var binder = new Binder(); + using (var resolver = new Splat.ModernDependencyResolver()) + using (Splat.DependencyResolverMixins.WithResolver(resolver)) + { + resolver.Register(() => binder); + var model = new Model(); + var view = new View { ViewModel = model }; + using (view.BindCommand(model, x => x.Command, x => x.Control{{(explicitEvent ? ", toEvent: \"Click\"" : string.Empty)}})) + { + view.Control.Raise(); + if ({{score}} > {{(explicitEvent ? ExplicitEventAffinity : AndroidAffinity)}}) + { + if (binder.Calls != 1 || model.Command.Count != 0 || binder.Event != "{{(explicitEvent ? "Click" : "default")}}") return false; + } + else if (binder.Calls != 0 || model.Command.Count != 1) return false; + if (binder.EventTarget != {{(explicitEvent ? "true" : "false")}}) return false; + } + return binder.Disposals == binder.Calls; + } + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandParityTests.cs new file mode 100644 index 00000000..1f400040 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/NativeCommandParityTests.cs @@ -0,0 +1,289 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes native command routes, enabled state, replacement and disposal. +public class NativeCommandParityTests +{ + /// The framework command contracts exercised by the generated consumer. + internal const string Framework = """ + using System; + using System.ComponentModel; + using System.Reflection; + using System.Windows.Input; + using ReactiveUI.Binding; + namespace Foundation + { + public class NSObject : IDisposable { public void Dispose() {} } + public class ExportAttribute : Attribute + { + public string Selector { get; } + public ExportAttribute(string selector) { Selector = selector; } + } + } + namespace ObjCRuntime + { + public class Selector : IDisposable + { + public string Name { get; } + public Selector(string name) { Name = name; } + public void Dispose() {} + } + } + namespace Android.Views + { + public class View + { + public event EventHandler Click; + public bool Enabled { get; set; } + public bool HasBinding => Click != null; + public void Raise() => Click?.Invoke(this, EventArgs.Empty); + } + } + namespace UIKit + { + public enum UIControlEvent { TouchUpInside } + public class UIControl : Foundation.NSObject + { + private event EventHandler _targets; + public event EventHandler TouchUpInside; + public bool Enabled { get; set; } + public bool HasBinding => _targets != null || TouchUpInside != null; + public void AddTarget(EventHandler handler, UIControlEvent kind) { _targets += handler; } + public void RemoveTarget(EventHandler handler, UIControlEvent kind) { _targets -= handler; } + public virtual void Raise() => _targets?.Invoke(this, EventArgs.Empty); + } + public class UIRefreshControl : UIControl + { + public event EventHandler ValueChanged; + public new bool HasBinding => ValueChanged != null || base.HasBinding; + public override void Raise() => ValueChanged?.Invoke(this, EventArgs.Empty); + } + public class UIBarButtonItem : Foundation.NSObject + { + public event EventHandler Clicked; + public bool Enabled { get; set; } + public bool HasBinding => Clicked != null; + public void Raise() => Clicked?.Invoke(this, EventArgs.Empty); + } + } + namespace AppKit + { + public class ActionHost : Foundation.NSObject + { + public Foundation.NSObject Target { get; set; } + public ObjCRuntime.Selector Action { get; set; } + public bool Enabled { get; set; } + public bool HasBinding => Target != null; + public void Raise() + { + if (Target == null || Action == null) return; + foreach (var method in Target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + if (method.GetCustomAttribute()?.Selector == Action.Name) + method.Invoke(Target, new object[] { this }); + } + } + public class NSControl : ActionHost {} + public class NSCell : ActionHost {} + public class NSMenu : ActionHost {} + public class NSMenuItem : ActionHost {} + public class NSToolbarItem : ActionHost {} + } + public class Command : ICommand + { + public event EventHandler CanExecuteChanged; + public int Count { get; private set; } + public object Parameter { get; private set; } + public bool Allowed { get; private set; } = true; + public bool CanExecute(object parameter) => Allowed; + public void Execute(object parameter) { Count++; Parameter = parameter; } + public void SetAllowed(bool value) { Allowed = value; CanExecuteChanged?.Invoke(this, EventArgs.Empty); } + } + public class Model : INotifyPropertyChanged + { + private Command _command = new Command(); + private int _parameter; + public event PropertyChangedEventHandler PropertyChanged; + public Command Command { get { return _command; } set { _command = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Command")); } } + public int Parameter { get { return _parameter; } set { _parameter = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Parameter")); } } + } + public class Parameters : IObservable + { + private event Action _next; + public bool HasObservers => _next != null; + public void Send(int value) => _next?.Invoke(value); + public IDisposable Subscribe(IObserver observer) + { + _next += observer.OnNext; + return new ReactiveUI.Primitives.Disposables.ActionDisposable(() => _next -= observer.OnNext); + } + } + """; + + /// The native route executes commands and detaches both replaced and disposed controls. + /// The native control type. + /// The native binder's score. + /// A task representing the asynchronous test. + [Test] + [Arguments("Android.Views.View", 9)] + [Arguments("UIKit.UIControl", 9)] + [Arguments("UIKit.UIRefreshControl", 10)] + [Arguments("UIKit.UIBarButtonItem", 10)] + [Arguments("AppKit.NSControl", 4)] + [Arguments("AppKit.NSCell", 4)] + [Arguments("AppKit.NSMenu", 4)] + [Arguments("AppKit.NSMenuItem", 4)] + [Arguments("AppKit.NSToolbarItem", 4)] + public async Task BindCommand_ExecutesNativeContract(string type, int affinity) + { + var result = TestHelper.RunGenerator(Scenario(type), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceContains("BindCommandDispatch.g.cs", $">({affinity}, false)"); + await AssertRuns(result); + } + + /// Native commands read current typed parameters and release their subscriptions. + /// The native control type. + /// Whether the parameter is a property expression. + /// A task representing the asynchronous test. + [Test] + [Arguments("Android.Views.View", false)] + [Arguments("UIKit.UIControl", false)] + [Arguments("UIKit.UIRefreshControl", false)] + [Arguments("UIKit.UIBarButtonItem", false)] + [Arguments("AppKit.NSControl", false)] + [Arguments("Android.Views.View", true)] + [Arguments("UIKit.UIControl", true)] + [Arguments("UIKit.UIRefreshControl", true)] + [Arguments("UIKit.UIBarButtonItem", true)] + [Arguments("AppKit.NSControl", true)] + public async Task BindCommand_TracksParameters(string type, bool expression) + { + var result = TestHelper.RunGenerator(ParameterScenario(type, expression), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// The System.Reactive package executes the same native command behavior. + /// The native control type. + /// A task representing the asynchronous test. + [Test] + [Arguments("Android.Views.View")] + [Arguments("UIKit.UIControl")] + [Arguments("AppKit.NSControl")] + public async Task BindCommand_ReactiveRuntimeExecutesNativeContract(string type) + { + var source = Scenario(type).Replace("using ReactiveUI.Binding;", "using ReactiveUI.Binding.Reactive;", StringComparison.Ordinal); + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10, null, true); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// Runs the consumer's behavior checks in its collectible assembly. + /// The compiled binding consumer. + /// A task representing the asynchronous test. + private static async Task AssertRuns(GeneratorTestResult result) + { + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a consumer with either a hot stream or an observed property parameter. + /// The native control. + /// Whether the property supplies parameters. + /// The executable consumer. + private static string ParameterScenario(string type, bool expression) => Framework + $$""" + public class View : IViewFor + { + public Model ViewModel { get; set; } + object IViewFor.ViewModel { get { return ViewModel; } set { ViewModel = (Model)value; } } + public {{type}} Control { get; } = new {{type}}(); + } + public static class Usage + { + public static bool Run() + { + var model = new Model { Parameter = 7 }; + var view = new View { ViewModel = model }; + var parameters = new Parameters(); + using (view.BindCommand(model, x => x.Command, x => x.Control, {{(expression ? "x => x.Parameter" : "parameters")}})) + { + view.Control.Raise(); + if (!object.Equals(model.Command.Parameter, {{(expression ? "(object)7" : "null")}})) return false; + model.Parameter = 42; + parameters.Send(42); + view.Control.Raise(); + if (!object.Equals(model.Command.Parameter, 42)) return false; + var previous = model.Command; + model.Command = new Command(); + model.Parameter = 43; + parameters.Send(43); + view.Control.Raise(); + if (previous.Count != 2 || model.Command.Count != 1 || !object.Equals(model.Command.Parameter, 43)) return false; + } + return !parameters.HasObservers && !view.Control.HasBinding; + } + } + """; + + /// Builds a view that can replace its control and command independently. + /// The native control type. + /// The executable consumer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string Scenario(string type) => Framework + $$""" + public class View : IViewFor, INotifyPropertyChanged + { + private {{type}} _control = new {{type}}(); + public Model ViewModel { get; set; } + object IViewFor.ViewModel { get { return ViewModel; } set { ViewModel = (Model)value; } } + public event PropertyChangedEventHandler PropertyChanged; + public {{type}} Control { get { return _control; } set { _control = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Control")); } } + } + public static class Usage + { + public static bool Run() + { + var model = new Model(); + var command = model.Command; + var view = new View { ViewModel = model }; + var first = view.Control; + var second = new {{type}}(); + var replacement = new Command(); + using (view.BindCommand(model, x => x.Command, x => x.Control)) + { + if (!first.Enabled) return false; + first.Raise(); + if (command.Count != 1 || command.Parameter != null) return false; + command.SetAllowed(false); + first.Raise(); + if (first.Enabled || command.Count != 1) return false; + model.Command = replacement; + first.Raise(); + if (!first.Enabled || replacement.Count != 1) return false; + view.Control = second; + first.Raise(); + if (first.HasBinding || replacement.Count != 1) return false; + second.Raise(); + if (replacement.Count != 2) return false; + } + second.Raise(); + return !second.HasBinding && replacement.Count == 2; + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#OneWayBindDispatch.g.verified.cs index d4d79ad9..acbca174 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.2STB_GEI#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -56,12 +56,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).FirstName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel), "FirstName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).FirstName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "FirstName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -72,7 +68,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).FirstName, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { @@ -110,12 +146,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).LastName, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel), "LastName", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).LastName, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "LastName", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -126,7 +158,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.TwoSameTypeBindings.MyViewModel)__o).LastName, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#OneWayBindDispatch.g.verified.cs index 2d7d2d42..77dc9fa2 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.MB#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Name, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { @@ -128,12 +164,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Age, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel), "Age", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Age, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Age", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -144,7 +176,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.MultipleBindings.MyViewModel)__o).Age, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#OneWayBindDispatch.g.verified.cs index 398083d7..06889f00 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_I2I#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyIntToInt.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.SinglePropertyIntToInt.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyIntToInt.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyIntToInt.MyViewModel)__o).Count, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(int), typeof(int)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 1) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + int __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (int)__boxed); + } + } + if (null == null) + { + return (true, (int)__value); + } + return (false, default(int)); + } + return true ? (true, (int)__value) : (true, (int)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#OneWayBindDispatch.g.verified.cs index a5cf5774..24f21e3f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#OneWayBindDispatch.g.verified.cs index 9f47490b..ef748a74 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_S2S_CFP#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyStringToString.MyViewModel)__o).Name, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#OneWayBindDispatch.g.verified.cs index 882079b7..8062d2c3 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WS#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -52,12 +52,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyWithSelector.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.SinglePropertyWithSelector.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyWithSelector.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#OneWayBindDispatch.g.verified.cs index 6fd044ee..56ef162f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/OBG.SP_WSSched#OneWayBindDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: OneWayBindDispatch.g.cs +//HintName: OneWayBindDispatch.g.cs // #pragma warning disable #nullable enable @@ -53,12 +53,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyWithSelectorAndScheduler.MyViewModel)__o).Count, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.OneWayBind.SinglePropertyWithSelectorAndScheduler.MyViewModel), "Count", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.OneWayBind.SinglePropertyWithSelectorAndScheduler.MyViewModel)__o).Count, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Count", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationContractsParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationContractsParityTests.cs new file mode 100644 index 00000000..1a14732b --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationContractsParityTests.cs @@ -0,0 +1,147 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes the observation contracts shared by the native and managed providers. +public class ObservationContractsParityTests +{ + /// The parent reports replacements after the write; the leaf reports values before it. + private const string BeforeChangeScenario = """ + using System; + using System.ComponentModel; + using ReactiveUI.Binding; + public class Leaf : INotifyPropertyChanging + { + private int _value; + public event PropertyChangingEventHandler PropertyChanging; + public int Value { get { return _value; } set { PropertyChanging?.Invoke(this, new PropertyChangingEventArgs("Value")); _value = value; } } + } + public class Model : INotifyPropertyChanged + { + private Leaf _child = new Leaf(); + public event PropertyChangedEventHandler PropertyChanged; + public Leaf Child { get { return _child; } set { _child = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Child")); } } + } + public static class Usage + { + public static bool Run() + { + var model = new Model(); + var previous = model.Child; + var observer = new Observer(); + using (model.WhenChanging(x => x.Child.Value).Subscribe(observer)) + { + model.Child = new Leaf { Value = 10 }; + model.Child.Value = 11; + model.Child.Value = 12; + previous.Value = 99; + return observer.Value == 11; + } + } + private sealed class Observer : IObserver + { + public int Value; + public void OnNext(int value) { Value = value; } + public void OnError(Exception error) { throw error; } + public void OnCompleted() {} + } + } + """; + + /// Fallback observations read at subscription time and remain open. + /// A task representing the asynchronous test. + [Test] + public async Task Poco_ReadsWhenSubscribed() + { + const string source = """ + using System; + using ReactiveUI.Binding; + public class Model { public int Value { get; set; } } + public static class Usage + { + public static bool Run() + { + var model = new Model { Value = 1 }; + var observation = model.WhenChanged(x => x.Value); + model.Value = 2; + var observer = new Observer(); + using (observation.Subscribe(observer)) return observer.Value == 2 && !observer.Completed; + } + private sealed class Observer : IObserver + { + public int Value; + public bool Completed; + public void OnNext(int value) { Value = value; } + public void OnError(Exception error) { throw error; } + public void OnCompleted() { Completed = true; } + } + } + """; + await ExecutesSuccessfully(source); + } + + /// A derived object's notification interface applies to inherited properties in generated bindings. + /// A task representing the asynchronous test. + [Test] + public async Task InheritedProperty_UsesObservedOwnersNotification() + { + const string source = """ + using System; + using System.ComponentModel; + using ReactiveUI.Binding; + public class BaseModel { public int Value { get; set; } } + public class Model : BaseModel, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public void Change(int value) { Value = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); } + } + public class Target { public int Value { get; set; } } + public static class Usage + { + public static bool Run() + { + var model = new Model { Value = 1 }; + var target = new Target(); + using (model.BindOneWay(target, x => x.Value, x => x.Value)) + { + model.Change(2); + return target.Value == 2; + } + } + } + """; + await ExecutesSuccessfully(source); + } + + /// Before-change chains follow parent replacement and observe the leaf's prior value. + /// A task representing the asynchronous test. + [Test] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task BeforeChangeChain_FollowsReplacement() => ExecutesSuccessfully(BeforeChangeScenario); + + /// Compiles and executes a generated consumer's assertions. + /// The complete consumer source. + /// A task representing the asynchronous test. + private static async Task ExecutesSuccessfully(string source) + { + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Static | BindingFlags.Public)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationOverrideParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationOverrideParityTests.cs new file mode 100644 index 00000000..ed2d2cdc --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ObservationOverrideParityTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes custom observation voting, timing, typed reads and cache refresh in generated consumers. +public class ObservationOverrideParityTests +{ + /// The notifying source and independently driven custom provider. + private const string Fixture = """ + using System; + using System.ComponentModel; + using System.Linq.Expressions; + using ReactiveUI.Binding; + public class Source : INotifyPropertyChanged, INotifyPropertyChanging + { + private int _value; + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + public int Value + { + get { return _value; } + set { PropertyChanging?.Invoke(this, new PropertyChangingEventArgs("Value")); _value = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); } + } + } + public class DerivedSource : Source {} + public class Observer : IObserver + { + public int Last; + public void OnNext(int value) { Last = value; } + public void OnError(Exception error) { throw error; } + public void OnCompleted() { throw new InvalidOperationException("Observation completed"); } + } + public class Provider : ICreatesObservableForProperty + { + public int Score, Votes, Calls; + public bool Before; + public object Sender; + public Expression Expression; + public ReactiveUI.Primitives.Signals.Signal> Notifications = new ReactiveUI.Primitives.Signals.Signal>(); + public int GetAffinityForObject(Type type, string property, bool before) + { + Votes++; + return type == typeof(DerivedSource) && property == "Value" && before == Before ? Score : 0; + } + public IObservable> GetNotificationForProperty(object sender, Expression expression, string property, bool before, bool suppress) + { + if (!(expression is MemberExpression member) || member.Member.Name != property || before != Before) throw new InvalidOperationException("Incorrect property contract"); + Calls++; + Sender = sender; + Expression = expression; + return Notifications; + } + public void Fire() { Notifications.OnNext(new ObservedChange(Sender, Expression, 999)); } + } + """; + + /// Custom providers need higher property-specific affinity, and refresh invalidates cached votes. + /// The custom provider's score. + /// Whether the call observes before-change notifications. + /// A task representing the asynchronous test. + [Test] + [Arguments(4, false)] + [Arguments(5, false)] + [Arguments(6, false)] + [Arguments(4, true)] + [Arguments(5, true)] + [Arguments(6, true)] + public async Task Observation_RespectsVotesAndRefresh(int score, bool before) + { + var result = TestHelper.RunGenerator(Scenario(score, before), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + var (assembly, context) = TestHelper.EmitAndLoad(result, true); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Exercises a generated observation with runtime-derived ownership and registration changes. + /// The initial provider's affinity. + /// The requested notification timing. + /// The executable consumer. + private static string Scenario(int score, bool before) => Fixture + $$""" + public static class Usage + { + public static IObservable Observe(Source source) => source.{{(before ? "WhenChanging" : "WhenChanged")}}(x => x.Value); + public static bool Run() + { + var provider = new Provider { Score = {{score}}, Before = {{(before ? "true" : "false")}} }; + using (var resolver = new Splat.ModernDependencyResolver()) + using (Splat.DependencyResolverMixins.WithResolver(resolver)) + { + resolver.Register(() => provider); + ReactiveUI.Binding.Fallback.ObservationAffinityChecker.Refresh(); + Source source = new DerivedSource(); + var observer = new Observer(); + using (Observe(source).Subscribe(observer)) + { + var votes = provider.Votes; + source.Value = 7; + source.Value = 9; + var custom = {{score}} > 5; + if (observer.Last != (custom ? 0 : {{(before ? "7" : "9")}})) return false; + provider.Fire(); + if (observer.Last != (custom ? 9 : {{(before ? "7" : "9")}})) return false; + if (provider.Votes != votes || provider.Calls != (custom ? 1 : 0)) return false; + } + if (provider.Notifications.HasObservers) return false; + var replacement = new Provider { Score = 99, Before = provider.Before }; + resolver.Register(() => replacement); + using (Observe(source).Subscribe(observer)) + if (replacement.Calls != 0) return false; + ReactiveUI.Binding.Fallback.ObservationAffinityChecker.Refresh(); + using (Observe(source).Subscribe(observer)) + if (replacement.Calls != 1) return false; + return !replacement.Notifications.HasObservers; + } + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.AndroidView_Detected#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.AndroidView_Detected#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 84aa7b1f..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.AndroidView_Detected#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: Android - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 8f44ff71..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: KVO - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#ObservationHelpers.g.verified.cs deleted file mode 100644 index 17115830..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.NSObject_Detected#ObservationHelpers.g.verified.cs +++ /dev/null @@ -1,132 +0,0 @@ -//HintName: ObservationHelpers.g.cs -// -#pragma warning disable - -using System; - -namespace ReactiveUI.Binding -{ - internal static partial class __ReactiveUIGeneratedBindings - { - /// - /// NSObject subclass that receives KVO ObserveValue callbacks and forwards - /// them to a delegate. Mirrors ReactiveUI's BlockObserveValueDelegate pattern. - /// - private sealed class __KVOObserver : global::Foundation.NSObject - { - private readonly global::System.Action _callback; - - internal __KVOObserver(global::System.Action callback) - { - _callback = callback; - } - - public override void ObserveValue( - global::Foundation.NSString keyPath, - global::Foundation.NSObject ofObject, - global::Foundation.NSDictionary change, - global::System.IntPtr context) - { - _callback(); - } - } - - /// - /// Fused observable for Apple KVO property observation. - /// Uses NSObject.AddObserver / NSObject.RemoveObserver - /// with a compile-time resolved KVO key path. - /// - private sealed class __KVOObservable : global::System.IObservable - { - private readonly global::Foundation.NSObject _source; - private readonly global::Foundation.NSString _keyPath; - private readonly global::System.Func _getter; - private readonly bool _distinctUntilChanged; - private readonly global::Foundation.NSKeyValueObservingOptions _options; - - internal __KVOObservable( - global::Foundation.NSObject source, - string keyPath, - global::System.Func getter, - bool distinctUntilChanged, - bool beforeChange) - { - _source = source; - _keyPath = (global::Foundation.NSString)keyPath; - _getter = getter; - _distinctUntilChanged = distinctUntilChanged; - _options = beforeChange - ? global::Foundation.NSKeyValueObservingOptions.Old - : global::Foundation.NSKeyValueObservingOptions.New; - } - - public global::System.IDisposable Subscribe(global::System.IObserver observer) - { - return new Subscription(this, observer); - } - - private sealed class Subscription : global::System.IDisposable - { - private readonly __KVOObservable _parent; - private readonly __KVOObserver _kvoObserver; - private readonly global::System.Runtime.InteropServices.GCHandle _handle; - private readonly global::System.Collections.Generic.IEqualityComparer _comparer; - private global::System.IObserver _observer; - private T _lastValue; - private bool _hasValue; - - internal Subscription(__KVOObservable parent, global::System.IObserver observer) - { - _parent = parent; - _observer = observer; - _comparer = global::System.Collections.Generic.EqualityComparer.Default; - - _kvoObserver = new __KVOObserver(OnValueChanged); - _handle = global::System.Runtime.InteropServices.GCHandle.Alloc(_kvoObserver); - - parent._source.AddObserver( - _kvoObserver, - parent._keyPath, - parent._options, - global::System.IntPtr.Zero); - - // Emit initial value - var initial = parent._getter(parent._source); - _lastValue = initial; - _hasValue = true; - observer.OnNext(initial); - } - - private void OnValueChanged() - { - var obs = System.Threading.Volatile.Read(ref _observer); - if (obs == null) - { - return; - } - - var value = _parent._getter(_parent._source); - - if (_parent._distinctUntilChanged && _hasValue && _comparer.Equals(value, _lastValue)) - { - return; - } - - _lastValue = value; - _hasValue = true; - obs.OnNext(value); - } - - public void Dispose() - { - var obs = System.Threading.Interlocked.Exchange(ref _observer, null); - if (obs != null) - { - _parent._source.RemoveObserver(_kvoObserver, _parent._keyPath); - _handle.Free(); - } - } - } - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WinFormsComponent_Detected#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WinFormsComponent_Detected#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 10a4ad58..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WinFormsComponent_Detected#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: WinForms - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WpfDependencyObject_Detected#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WpfDependencyObject_Detected#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 51e7917d..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PDS.WpfDependencyObject_Detected#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: WpfDP - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformAdapterTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformAdapterTests.cs new file mode 100644 index 00000000..5b1143ad --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformAdapterTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Checks platform subscriptions against the consumer's concrete framework members. +public class PlatformAdapterTests +{ + /// The observation dispatch output. + private const string DispatchFile = "WhenChangedDispatch.g.cs"; + + /// Android event delegates carry their framework argument type through every chain link. + /// The observed property path. + /// A task representing the asynchronous test. + [Test] + [Arguments("x => x.Text")] + [Arguments("x => x.Child.Text")] + public async Task AndroidText_UsesTypedEvent(string path) + { + var source = $$""" + using System; + using ReactiveUI.Binding; + namespace Android.Views { public class View {} } + namespace Android.Widget + { + public class TextChangedEventArgs : EventArgs {} + public class TextView : Android.Views.View + { + public event EventHandler TextChanged; + public string Text { get; set; } + public TextView Child { get; set; } + } + } + public static class Usage + { + public static IObservable Observe(Android.Widget.TextView view) + => view.WhenChanged({{path}}); + } + """; + + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceContains(DispatchFile, "global::System.EventHandler"); + await result.GeneratedSourceContains(DispatchFile, "TextChanged +="); + await result.GeneratedSourceContains(DispatchFile, "TextChanged -="); + } + + /// A matching property name on a different widget cannot claim a nonexistent event. + /// A task representing the asynchronous test. + [Test] + public async Task AndroidProperty_WithoutWidgetEvent_DoesNotClaimNotification() + { + const string source = """ + using System; + using ReactiveUI.Binding; + namespace Android.Views { public class View {} } + namespace Android.Widget + { + public class OtherView : Android.Views.View + { + public string Text { get; set; } + } + } + public static class Usage + { + public static IObservable Observe(Android.Widget.OtherView view) + => view.WhenChanged(x => x.Text); + } + """; + + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceContains(DispatchFile, "\"Text\", 1, false"); + await result.GeneratedSourceContains(DispatchFile, "__UnchangingPropertyObservable"); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformDetectionSnapshotTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformDetectionSnapshotTests.cs index cd917465..e2bfe08d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformDetectionSnapshotTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PlatformDetectionSnapshotTests.cs @@ -83,10 +83,7 @@ public class MyAndroidView : Android.Views.View return TestHelper.TestPass(source, typeof(PlatformDetectionSnapshotTests)); } - /// - /// Verifies detection of Apple NSObject (KVO), and that the KVO observation helpers it brings with it - /// compile on the minimum supported language version even with no binding call to use them. - /// + /// Verifies that an unused Apple type compiles on the minimum supported language version. /// A task representing the asynchronous test operation. [Test] public async Task NSObject_Detected() diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs index 79b5d194..56d02aa7 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs @@ -16,6 +16,12 @@ public class ObservationPluginTests /// The EventObservable name these tests generate against. private const string EventObservableName = "EventObservable"; + /// The typed WinForms observation helper. + private const string WinFormsObservableName = "__WinFormsObservable"; + + /// The typed Android observation helper. + private const string AndroidObservableName = "__AndroidObservable"; + /// The false) fragment these tests expect in the generated source. private const string FalseFragment = "false)"; @@ -201,7 +207,7 @@ public async Task WpfPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsEventObse var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); await Assert.That(result).Contains(EventObservableName); @@ -217,7 +223,7 @@ public async Task WpfPlugin_EmitDeepChainInnerSegment_BeforeChange_KeepsReportin var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, true, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); await Assert.That(result).Contains(EventObservableName); @@ -233,7 +239,7 @@ public async Task WpfPlugin_EmitDeepChainInnerSegment_EmittingDefaults_PushesDef var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.EmitDefault); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.EmitDefault); var result = sb.ToString(); await Assert.That(result).Contains(ImmediateReturnSignalName); @@ -302,12 +308,12 @@ public async Task WinFormsPlugin_EmitShallowObservationVariable_AfterChange_Emit { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyTextBoxTypeName, false, Obs0Local); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(WinFormsObservableName); await Assert.That(result).Contains(TextChangedName); } @@ -318,7 +324,7 @@ public async Task WinFormsPlugin_EmitShallowObservationVariable_BeforeChange_Emi { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyTextBoxTypeName, true, Obs0Local); @@ -332,7 +338,7 @@ public async Task WinFormsPlugin_EmitShallowObservation_BeforeChange_EmitsTheUnc { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyTextBoxTypeName, true, true); @@ -346,12 +352,12 @@ public async Task WinFormsPlugin_EmitDeepChainRootSegment_AfterChange_EmitsEvent { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyTextBoxTypeName, false, Obs0Local); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(WinFormsObservableName); await Assert.That(result).Contains(TextChangedName); } @@ -362,7 +368,7 @@ public async Task WinFormsPlugin_EmitDeepChainRootSegment_BeforeChange_EmitsTheU { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyTextBoxTypeName, true, Obs0Local); @@ -376,27 +382,27 @@ public async Task WinFormsPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsEven { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(WinFormsObservableName); await Assert.That(result).Contains(SwitchName); } /// Verifies WinForms plugin deep chain inner segment before-change emits ImmediateReturnSignal. /// A task representing the asynchronous test operation. [Test] - public async Task WinFormsPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsImmediateReturnSignal() + public async Task WinFormsPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsUnchangingValue() { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, true, NullParentObservationBehavior.SuppressEmission); - await Assert.That(sb.ToString()).Contains(ImmediateReturnSignalName); + await Assert.That(sb.ToString()).Contains(UnchangingPropertyObservableName); } /// A WinForms inner segment that is not the leaf pushes the leaf's default value down the chain. @@ -406,9 +412,9 @@ public async Task WinFormsPlugin_EmitDeepChainInnerSegment_EmittingDefaults_Push { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.EmitDefault); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.EmitDefault); var result = sb.ToString(); await Assert.That(result).Contains(ImmediateReturnSignalName); @@ -422,26 +428,26 @@ public async Task WinFormsPlugin_EmitInlineObservationVariable_EmitsEventObserva { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitInlineObservationVariable(sb, SourceName, segment, MyTextBoxTypeName, SourceObsName); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(WinFormsObservableName); await Assert.That(result).Contains(TextChangedName); } - /// Verifies WinForms plugin EmitHelperClasses is a no-op. + /// Verifies WinForms declares its typed subscription helper. /// A task representing the asynchronous test operation. [Test] - public async Task WinFormsPlugin_EmitHelperClasses_IsNoOp() + public async Task WinFormsPlugin_EmitHelperClasses_DeclaresTypedSubscription() { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); plugin.EmitHelperClasses(sb); - await Assert.That(sb.Length).IsEqualTo(0); + await Assert.That(sb.ToString()).Contains(WinFormsObservableName); } // ========== WinUIObservationPlugin ========== @@ -452,7 +458,7 @@ public async Task WinUIPlugin_EmitShallowObservation_BeforeChange_EmitsTheUnchan { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyControlTypeName, true, true); @@ -466,7 +472,7 @@ public async Task WinUIPlugin_EmitShallowObservationVariable_AfterChange_EmitsWi { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyControlTypeName, false, Obs0Local); @@ -482,7 +488,7 @@ public async Task WinUIPlugin_EmitShallowObservationVariable_BeforeChange_EmitsT { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyControlTypeName, true, Obs0Local); @@ -496,7 +502,7 @@ public async Task WinUIPlugin_EmitDeepChainRootSegment_AfterChange_EmitsWinUIDPO { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyControlTypeName, false, Obs0Local); @@ -510,7 +516,7 @@ public async Task WinUIPlugin_EmitDeepChainRootSegment_BeforeChange_EmitsTheUnch { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyControlTypeName, true, Obs0Local); @@ -524,9 +530,9 @@ public async Task WinUIPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsWinUIDP { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); await Assert.That(result).Contains(WinUIDPObservableLocal); @@ -536,15 +542,15 @@ public async Task WinUIPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsWinUIDP /// Verifies WinUI plugin deep chain inner segment before-change emits ImmediateReturnSignal. /// A task representing the asynchronous test operation. [Test] - public async Task WinUIPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsImmediateReturnSignal() + public async Task WinUIPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsUnchangingValue() { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, true, NullParentObservationBehavior.SuppressEmission); - await Assert.That(sb.ToString()).Contains(ImmediateReturnSignalName); + await Assert.That(sb.ToString()).Contains(UnchangingPropertyObservableName); } /// A WinUI inner segment that is not the leaf pushes the leaf's default value down the chain. @@ -554,9 +560,9 @@ public async Task WinUIPlugin_EmitDeepChainInnerSegment_EmittingDefaults_PushesD { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName, InnerTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.EmitDefault); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.EmitDefault); var result = sb.ToString(); await Assert.That(result).Contains(ImmediateReturnSignalName); @@ -570,7 +576,7 @@ public async Task WinUIPlugin_EmitInlineObservationVariable_EmitsWinUIDPObservab { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitInlineObservationVariable(sb, SourceName, segment, MyControlTypeName, SourceObsName); @@ -591,7 +597,7 @@ public async Task WinUIPlugin_EmitHelperClasses_EmitsWinUIDPObservable() var result = sb.ToString(); await Assert.That(result).Contains(WinUIDPObservableLocal); - await Assert.That(result).Contains("RegisterPropertyChangedCallback"); + await Assert.That(result).Contains("global::System.IObservable"); } // ========== KVOObservationPlugin ========== @@ -668,7 +674,7 @@ public async Task KVOPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsKVOObserv var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); await Assert.That(result).Contains(KVOObservableLocal); @@ -684,7 +690,7 @@ public async Task KVOPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsKVOObser var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, true, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); await Assert.That(result).Contains(KVOObservableLocal); @@ -700,7 +706,7 @@ public async Task KVOPlugin_EmitDeepChainInnerSegment_EmittingDefaults_PushesDef var sb = new StringBuilder(); var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.EmitDefault); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.EmitDefault); var result = sb.ToString(); await Assert.That(result).Contains(ImmediateReturnSignalName); @@ -805,7 +811,7 @@ public async Task AndroidPlugin_EmitShallowObservationVariable_SubscribesTheWidg { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyAndroidViewTypeName, false, Obs0Local); @@ -821,7 +827,7 @@ public async Task AndroidPlugin_EmitShallowObservationVariable_UnreportedPropert { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(UnreportedPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, UnreportedPropertyName, StringName); plugin.EmitShallowObservationVariable(sb, "obj", segment, MyAndroidViewTypeName, false, Obs0Local); @@ -837,7 +843,7 @@ public async Task AndroidPlugin_EmitDeepChainRootSegment_SubscribesTheWidgetEven { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyAndroidViewTypeName, false, Obs0Local); @@ -853,7 +859,7 @@ public async Task AndroidPlugin_EmitDeepChainRootSegment_UnreportedProperty_Emit { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(UnreportedPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, UnreportedPropertyName, StringName); plugin.EmitDeepChainRootSegment(sb, "obj", segment, MyAndroidViewTypeName, false, Obs0Local); @@ -865,16 +871,16 @@ public async Task AndroidPlugin_EmitDeepChainRootSegment_UnreportedProperty_Emit /// Verifies Android plugin deep chain inner segment emits ImmediateReturnSignal with Switch. /// A task representing the asynchronous test operation. [Test] - public async Task AndroidPlugin_EmitDeepChainInnerSegment_EmitsImmediateReturnSignal() + public async Task AndroidPlugin_EmitDeepChainInnerSegment_EmitsUnchangingValue() { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.SuppressEmission); var result = sb.ToString(); - await Assert.That(result).Contains(ImmediateReturnSignalName); + await Assert.That(result).Contains(UnchangingPropertyObservableName); await Assert.That(result).Contains(SwitchName); } @@ -885,9 +891,9 @@ public async Task AndroidPlugin_EmitDeepChainInnerSegment_EmittingDefaults_Pushe { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "City", StringName, AddressTypeName); - plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.EmitDefault); + plugin.EmitDeepChainInnerSegment(sb, new(Obs0Local, Obs1Local, "__p1"), segment, false, NullParentObservationBehavior.EmitDefault); var result = sb.ToString(); await Assert.That(result).Contains(ImmediateReturnSignalName); @@ -901,7 +907,7 @@ public async Task AndroidPlugin_EmitInlineObservationVariable_SubscribesTheWidge { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitInlineObservationVariable(sb, SourceName, segment, MyAndroidViewTypeName, SourceObsName); @@ -917,7 +923,7 @@ public async Task AndroidPlugin_EmitInlineObservationVariable_UnreportedProperty { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(UnreportedPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, UnreportedPropertyName, StringName); plugin.EmitInlineObservationVariable(sb, SourceName, segment, MyAndroidViewTypeName, SourceObsName); @@ -926,17 +932,17 @@ public async Task AndroidPlugin_EmitInlineObservationVariable_UnreportedProperty await Assert.That(result).Contains(SourceObsDeclaration); } - /// Verifies Android plugin EmitHelperClasses is a no-op. + /// Verifies Android declares its typed subscription helper. /// A task representing the asynchronous test operation. [Test] - public async Task AndroidPlugin_EmitHelperClasses_IsNoOp() + public async Task AndroidPlugin_EmitHelperClasses_DeclaresTypedSubscription() { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); plugin.EmitHelperClasses(sb); - await Assert.That(sb.Length).IsEqualTo(0); + await Assert.That(sb.ToString()).Contains(AndroidObservableName); } /// Verifies Android plugin properties. @@ -950,7 +956,7 @@ public async Task AndroidPlugin_Properties_AreCorrect() await Assert.That(plugin.Affinity).IsEqualTo(ExpectedPluginAffinity); await Assert.That(plugin.ObservationKind).IsEqualTo("Android"); await Assert.That(plugin.SupportsBeforeChanged).IsFalse(); - await Assert.That(plugin.RequiresHelperClasses).IsFalse(); + await Assert.That(plugin.RequiresHelperClasses).IsTrue(); } // ========== Shallow observation with includeStartWith=false ========== @@ -1003,7 +1009,7 @@ public async Task WinFormsPlugin_EmitShallowObservation_NoStartWith_EmitsFalse() { var plugin = new WinFormsObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyTextBoxTypeName, false, false); @@ -1017,7 +1023,7 @@ public async Task WinUIPlugin_EmitShallowObservation_NoStartWith_EmitsFalse() { var plugin = new WinUIObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, "Text", StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyControlTypeName, false, false); @@ -1045,12 +1051,12 @@ public async Task AndroidPlugin_EmitShallowObservation_SubscribesTheWidgetEvent( { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyAndroidViewTypeName, false, true); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(AndroidObservableName); await Assert.That(result).Contains(TextChangedEventName); } @@ -1061,7 +1067,7 @@ public async Task AndroidPlugin_EmitShallowObservation_UnreportedProperty_EmitsT { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(UnreportedPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, UnreportedPropertyName, StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyAndroidViewTypeName, false, true); @@ -1078,12 +1084,12 @@ public async Task AndroidPlugin_EmitShallowObservation_NoStartWith_EmitsFalse() { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyAndroidViewTypeName, false, false); var result = sb.ToString(); - await Assert.That(result).Contains(EventObservableName); + await Assert.That(result).Contains(AndroidObservableName); await Assert.That(result).EndsWith(", false)"); } @@ -1094,7 +1100,7 @@ public async Task AndroidPlugin_EmitShallowObservation_BeforeChange_EmitsTheUnch { var plugin = new AndroidObservationPlugin(); var sb = new StringBuilder(); - var segment = ModelFactory.CreatePropertyPathSegment(TextPropertyName, StringName); + var segment = NativeObservationTestModels.CreateSegment(plugin, TextPropertyName, StringName); plugin.EmitShallowObservation(sb, "obj", segment, MyAndroidViewTypeName, true, true); @@ -1154,9 +1160,9 @@ public async Task Registry_GetPluginByKind_UnknownKind_ReturnsNull() /// Verifies Count returns the correct number of plugins. /// A task representing the asynchronous test operation. [Test] - public async Task Registry_Count_Returns7() + public async Task Registry_Count_MatchesSupportedMechanisms() { - const int ExpectedPluginCount = 7; + const int ExpectedPluginCount = 12; await Assert.That(ObservationPluginRegistry.Count).IsEqualTo(ExpectedPluginCount); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/PropertyCapabilityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/PropertyCapabilityTests.cs index 5e22bf93..0c4f4d1c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/PropertyCapabilityTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/PropertyCapabilityTests.cs @@ -30,8 +30,8 @@ public class PropertyCapabilityTests [Test] public async Task AndroidPlugin_ReachesOnlyThePropertiesAWidgetReports() { - var classInfo = ModelFactory.CreateClassBindingInfo(inheritsAndroidView: true); var plugin = new AndroidObservationPlugin(); + var classInfo = NativeObservationTestModels.CreateSegment(plugin, ReportedWidgetPropertyName, "string").DeclaringTypeInfo!; await Assert.That(plugin.CanObserveProperty(classInfo, ReportedWidgetPropertyName)).IsTrue(); await Assert.That(plugin.CanObserveProperty(classInfo, PropertyName)).IsFalse(); @@ -84,15 +84,16 @@ public async Task KvoPlugin_ReachesOnlyThePropertiesTheFrameworkDeclares() await Assert.That(plugin.CanObserveProperty(consumerProperty, PropertyName)).IsFalse(); } - /// A property the type does not declare is unknown, and an unknown property stays observable. + /// A plain CLR property cannot be observed through a native dependency-property mechanism. /// A task representing the asynchronous test operation. [Test] - public async Task DependencyPropertyPlugins_TreatAnUndeclaredPropertyAsObservable() + public async Task DependencyPropertyPlugins_RejectPlainClrProperty() { - var classInfo = ModelFactory.CreateClassBindingInfo(inheritsWpfDependencyObject: true); + var property = ModelFactory.CreateObservablePropertyInfo(PropertyName) with { SymbolsInspected = true }; + var classInfo = ModelFactory.CreateClassBindingInfo(inheritsWpfDependencyObject: true, properties: new([property])); - await Assert.That(new WpfObservationPlugin().CanObserveProperty(classInfo, "NotDeclaredHere")).IsTrue(); - await Assert.That(new WinUIObservationPlugin().CanObserveProperty(classInfo, "NotDeclaredHere")).IsTrue(); + await Assert.That(new WpfObservationPlugin().CanObserveProperty(classInfo, PropertyName)).IsFalse(); + await Assert.That(new WinUIObservationPlugin().CanObserveProperty(classInfo, PropertyName)).IsFalse(); } /// A declared property with no companion change event is out of the component mechanism's reach. @@ -113,11 +114,8 @@ public async Task ComponentPlugin_DoesNotReachAPropertyWithoutAChangeEvent() [Test] public async Task ComponentPlugin_ReachesAPropertyWithAChangeEvent() { - var classInfo = ModelFactory.CreateClassBindingInfo( - inheritsWinFormsComponent: true, - properties: new EquatableArray( - [ModelFactory.CreateObservablePropertyInfo(PropertyName, hasChangeEvent: true)])); - - await Assert.That(new WinFormsObservationPlugin().CanObserveProperty(classInfo, PropertyName)).IsTrue(); + var plugin = new WinFormsObservationPlugin(); + var classInfo = NativeObservationTestModels.CreateSegment(plugin, PropertyName, "string").DeclaringTypeInfo!; + await Assert.That(plugin.CanObserveProperty(classInfo, PropertyName)).IsTrue(); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PropertyObservationCapabilityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PropertyObservationCapabilityTests.cs index 40ae158c..1d1fe023 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PropertyObservationCapabilityTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/PropertyObservationCapabilityTests.cs @@ -88,13 +88,14 @@ public static IObservable Observe(MyPanel panel) namespace System.Windows { public class DependencyObject { } + public class DependencyProperty { } } namespace Consumer { public class BaseControl : System.Windows.DependencyObject { - public static readonly object CaptionProperty = new object(); + public static readonly System.Windows.DependencyProperty CaptionProperty = new System.Windows.DependencyProperty(); public string Caption { get; set; } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ReactiveUI.Binding.SourceGenerators.Tests.csproj b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ReactiveUI.Binding.SourceGenerators.Tests.csproj index a8dcc496..b117ab64 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ReactiveUI.Binding.SourceGenerators.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ReactiveUI.Binding.SourceGenerators.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable @@ -43,8 +43,8 @@ - - + + diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/BindOneWayRuntimeTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/BindOneWayRuntimeTests.cs index 420f471c..48fed673 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/BindOneWayRuntimeTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/BindOneWayRuntimeTests.cs @@ -19,10 +19,10 @@ public class BindOneWayRuntimeTests /// The BindTwoWayDispatch.g.cs name these tests generate against. private const string BindTwoWayDispatchgcsName = "BindTwoWayDispatch.g.cs"; - /// Verifies that BindOneWay generates dispatch and registration files. + /// Verifies that BindOneWay emits direct dispatch without a registration layer. /// A task representing the asynchronous test operation. [Test] - public async Task StringBinding_GeneratesDispatchAndRegistration() + public async Task StringBinding_GeneratesDirectDispatch() { const string source = """ using System; @@ -59,7 +59,7 @@ public void Test() await result.HasNoGeneratorDiagnostics(); await result.HasGeneratedSource(BindOneWayDispatchgcsName); - await result.HasGeneratedSource("GeneratedBinderRegistration.g.cs"); + await result.DoesNotHaveGeneratedSource("GeneratedBinderRegistration.g.cs"); await result.GeneratedSourceContains(BindOneWayDispatchgcsName, "NameText"); await result.GeneratedSourceContains(BindOneWayDispatchgcsName, "Name"); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/ConversionPluginRuntimeTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/ConversionPluginRuntimeTests.cs new file mode 100644 index 00000000..a4071367 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/ConversionPluginRuntimeTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.RuntimeExecution; + +/// Executes concrete platform conversions emitted from consumer symbols. +public class ConversionPluginRuntimeTests +{ + /// The selected visibility mapping runs in both directions without a platform registration. + /// The framework namespace exposed by the consumer. + /// The native visibility enum. + /// The value representing false. + /// The asynchronous test operation. + [Test] + [Arguments("System.Windows", "Visibility", "Collapsed")] + [Arguments("Microsoft.UI.Xaml", "Visibility", "Collapsed")] + [Arguments("Windows.UI.Xaml", "Visibility", "Collapsed")] + [Arguments("Microsoft.Maui", "Visibility", "Collapsed")] + [Arguments("Android.Views", "ViewStates", "Gone")] + public async Task VisibilityBinding_ConvertsBothDirections(string platformNamespace, string enumName, string hidden) + { + var source = VisibilityScenario(platformNamespace, enumName, hidden); + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("TestApp.Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a consumer with native visibility on one side and a boolean on the other. + /// The framework namespace. + /// The visibility enum name. + /// The false enum member. + /// The complete consumer source. + private static string VisibilityScenario(string platformNamespace, string enumName, string hidden) => $$""" + using System; + using System.ComponentModel; + using ReactiveUI.Binding; + namespace {{platformNamespace}} + { + public enum {{enumName}} { Visible, {{hidden}} } + } + namespace TestApp + { + public class Model : INotifyPropertyChanged + { + private bool _value; + public event PropertyChangedEventHandler PropertyChanged; + public bool Value + { + get { return _value; } + set { _value = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); } + } + } + public class View : INotifyPropertyChanged, IViewFor + { + public Model ViewModel { get; set; } + object IViewFor.ViewModel { get { return ViewModel; } set { ViewModel = (Model)value; } } + private {{platformNamespace}}.{{enumName}} _value; + public event PropertyChangedEventHandler PropertyChanged; + public {{platformNamespace}}.{{enumName}} Value + { + get { return _value; } + set { _value = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); } + } + } + public static class Usage + { + public static bool Run() + { + var model = new Model { Value = true }; + var view = new View { ViewModel = model }; + using (view.Bind(model, x => x.Value, x => x.Value)) + { + model.Value = false; + if (view.Value != {{platformNamespace}}.{{enumName}}.{{hidden}}) return false; + view.Value = {{platformNamespace}}.{{enumName}}.Visible; + return model.Value; + } + } + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/WhenChangedRuntimeTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/WhenChangedRuntimeTests.cs index 89b26bb5..806c64de 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/WhenChangedRuntimeTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/RuntimeExecution/WhenChangedRuntimeTests.cs @@ -16,10 +16,10 @@ public class WhenChangedRuntimeTests /// The WhenChangedDispatch.g.cs name these tests generate against. private const string WhenChangedDispatchgcsName = "WhenChangedDispatch.g.cs"; - /// Verifies that single-property WhenChanged generates a dispatch file with correct structure. + /// Verifies that single-property observation emits direct dispatch without a registration layer. /// A task representing the asynchronous test operation. [Test] - public async Task SingleProperty_GeneratesDispatchAndRegistration() + public async Task SingleProperty_GeneratesDirectDispatch() { const string source = """ using System; @@ -63,7 +63,7 @@ public void Test() await result.CompilationSucceeds(); await result.HasNoGeneratorDiagnostics(); await result.HasGeneratedSource(WhenChangedDispatchgcsName); - await result.HasGeneratedSource("GeneratedBinderRegistration.g.cs"); + await result.DoesNotHaveGeneratedSource("GeneratedBinderRegistration.g.cs"); await result.GeneratedSourceContains(WhenChangedDispatchgcsName, "PropertyChanged"); await result.GeneratedSourceContains(WhenChangedDispatchgcsName, "Name"); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/StandardConversionParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/StandardConversionParityTests.cs new file mode 100644 index 00000000..c1bd470a --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/StandardConversionParityTests.cs @@ -0,0 +1,157 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Compares generated conversions with the corresponding ReactiveUI converters. +public class StandardConversionParityTests +{ + /// Formatting, parsing, nullable values and conversion hints agree with the runtime providers. + /// The converted CLR type. + /// The ReactiveUI converter's type-name stem. + /// A representative typed input expression. + /// A task representing the asynchronous test. + [Test] + [Arguments("byte", "Byte", "(byte)7")] + [Arguments("short", "Short", "(short)7")] + [Arguments("int", "Integer", "7")] + [Arguments("long", "Long", "7L")] + [Arguments("float", "Single", "7.25f")] + [Arguments("double", "Double", "7.25d")] + [Arguments("decimal", "Decimal", "7.25m")] + [Arguments("bool", "Boolean", "true")] + [Arguments("Guid", "Guid", "new Guid(\"11111111-2222-3333-4444-555555555555\")")] + [Arguments("DateTime", "DateTime", "new DateTime(2026, 1, 2, 3, 4, 5)")] + [Arguments("DateTimeOffset", "DateTimeOffset", "new DateTimeOffset(2026, 1, 2, 3, 4, 5, TimeSpan.Zero)")] + [Arguments("TimeSpan", "TimeSpan", "TimeSpan.FromMinutes(75)")] + [Arguments("DateOnly", "DateOnly", "new DateOnly(2026, 1, 2)")] + [Arguments("TimeOnly", "TimeOnly", "new TimeOnly(3, 4, 5)")] + public async Task BindTo_MatchesStandardProviders(string type, string name, string value) + { + var result = TestHelper.RunGenerator(Scenario(type, name, value), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceDoesNotContain("BindToDispatch.g.cs", "RuntimeBindingConverter"); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds formatting and parsing probes with success, invalid, empty and null inputs. + /// The converted CLR type. + /// The converter type-name stem. + /// The representative input expression. + /// The executable consumer source. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string Scenario(string type, string name, string value) => $$""" + using System; + using System.Globalization; + using ReactiveUI.Binding; + public class Target + { + private T _value; + public Target(T initial) { _value = initial; Initial = initial; } + public T Initial { get; } + public int Writes { get; private set; } + public T Value { get { return _value; } set { _value = value; Writes++; } } + } + public static class Usage + { + public static bool Run() + { + var previous = CultureInfo.CurrentCulture; + try + { + foreach (var culture in new[] { "en-US", "fr-FR" }) + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); + foreach (object hint in new object[] { null, 4 }) + { + if (!Format({{value}}, hint) || !FormatNullable({{value}}, hint) || !FormatNullable(null, hint)) return false; + } + var provider = new global::ReactiveUI.{{name}}ToStringTypeConverter(); + object formatted; + provider.TryConvertTyped({{value}}, null, out formatted); + foreach (var text in new[] { (string)formatted, "not a value", "", null }) + { + if (!Parse(text) || !ParseNullable(text)) return false; + } + } + return true; + } + finally { CultureInfo.CurrentCulture = previous; } + } + {{Formatting(type, name, false)}} + {{Formatting(type, name, true)}} + {{Parsing(type, name, false)}} + {{Parsing(type, name, true)}} + private static bool Matches(global::ReactiveUI.IBindingTypeConverter provider, TIn value, object hint, Target target) + { + object expected; + var success = provider.TryConvertTyped(value, hint, out expected); + if (!success && typeof(TOut).IsAssignableFrom(typeof(TIn))) { success = true; expected = value; } + var writes = success && !object.Equals(target.Initial, expected) ? 1 : 0; + if (target.Writes != writes || (success && !object.Equals(target.Value, expected))) + { + throw new InvalidOperationException(provider.GetType().Name + ": input=" + (object)value + ", hint=" + hint + + ", success=" + success + ", expected=" + expected + ", actual=" + target.Value + ", writes=" + target.Writes); + } + return true; + } + } + """; + + /// Creates one formatting binding alongside its runtime provider comparison. + /// The formatted value type. + /// The converter type-name stem. + /// Whether the input is nullable. + /// The consumer method. + private static string Formatting(string type, string name, bool nullable) + { + var sourceType = nullable ? $"{type}?" : type; + var method = nullable ? "FormatNullable" : "Format"; + var provider = nullable ? $"Nullable{name}ToStringTypeConverter" : $"{name}ToStringTypeConverter"; + return $$""" + private static bool {{method}}({{sourceType}} value, object hint) + { + var target = new Target("unconverted"); + IObservable<{{sourceType}}> source = new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal<{{sourceType}}>(value); + using (source.BindTo(target, x => x.Value, conversionHint: hint)) + return Matches(new global::ReactiveUI.{{provider}}(), value, hint, target); + } + """; + } + + /// Creates one parsing binding alongside its runtime provider comparison. + /// The parsed value type. + /// The converter type-name stem. + /// Whether the output is nullable. + /// The consumer method. + private static string Parsing(string type, string name, bool nullable) + { + var targetType = nullable ? $"{type}?" : type; + var method = nullable ? "ParseNullable" : "Parse"; + var provider = nullable ? $"StringToNullable{name}TypeConverter" : $"StringTo{name}TypeConverter"; + return $$""" + private static bool {{method}}(string value) + { + var target = new Target<{{targetType}}>(({{targetType}})default({{type}})); + IObservable source = new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(value); + using (source.BindTo(target, x => x.Value)) + return Matches(new global::ReactiveUI.{{provider}}(), value, null, target); + } + """; + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/UnanalyzableInvocationTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/UnanalyzableInvocationTests.cs index d0f97ccf..fa29824d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/UnanalyzableInvocationTests.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/UnanalyzableInvocationTests.cs @@ -673,10 +673,7 @@ public static void Execute(MyViewModel vm) await result.DoesNotHaveGeneratedSource(WhenAnyObservableDispatchgcsName); } - /// - /// Verifies that the generator handles source code with no INPC types at all. - /// Exercises the allTypes.IsDefaultOrEmpty guard in RegistrationGenerator (line 76). - /// + /// Verifies that the generator handles source code with no INPC types at all. /// A task representing the asynchronous test operation. [Test] public async Task NoObservableTypes_GeneratesNoRegistration() diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.AbstractExcl#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.AbstractExcl#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.AbstractExcl#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DefaultAndContractViewsDispatchCorrectly#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DefaultAndContractViewsDispatchCorrectly#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DefaultAndContractViewsDispatchCorrectly#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DuplicateViewModelsAreDeduplicated#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DuplicateViewModelsAreDeduplicated#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DuplicateViewModelsAreDeduplicated#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleContractViewsWithoutDefault#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleContractViewsWithoutDefault#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleContractViewsWithoutDefault#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleViewForImplementations#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleViewForImplementations#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleViewForImplementations#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.NoViewFor#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.NoViewFor#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.NoViewFor#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleViewForImplementation#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleViewForImplementation#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleViewForImplementation#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithPrivateConstructor#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithPrivateConstructor#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithPrivateConstructor#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithoutParameterlessConstructor#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithoutParameterlessConstructor#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index d8ddbc56..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithoutParameterlessConstructor#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,24 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#BindOneWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#BindOneWayDispatch.g.verified.cs index 1a7f37bf..cccd7994 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#BindOneWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#BindOneWayDispatch.g.verified.cs @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,7 +70,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyViewModel)__o).Name, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target, __WpfViewThreadInvoker.Instance); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target, __WpfViewThreadInvoker.Instance); return global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 1381a904..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindOneWay_ToAWpfTarget_CarriesTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#BindToDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#BindToDispatch.g.verified.cs index fcff97b4..69fd1a05 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#BindToDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#BindToDispatch.g.verified.cs @@ -34,7 +34,47 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IDisposable __BindTo_7FFFD2E8338B15D7(global::System.IObservable source, global::TestApp.MyControl target) { // BindTo: observable -> Text - return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(source, target, __WinFormsViewThreadInvoker.Instance), value => + global::ReactiveUI.Binding.IBindingTypeConverter __convertedSourceConverter = null; + if (__convertedSourceConverter == null) + { + __convertedSourceConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedSourceConverter != null && __convertedSourceConverter.GetAffinityForObjects() <= 2) + { + __convertedSourceConverter = null; + } + } + var __convertedSource = __convertedSourceConverter == null ? (global::System.IObservable)source : global::ReactiveUI.Primitives.LinqExtensions.Choose( + source, + __value => + { + object __hint = null; + if (__convertedSourceConverter != null) + { + if (__convertedSourceConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedSourceConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + return global::ReactiveUI.Binding.BindingErrors.Subscribe(global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedSource, target, __WinFormsViewThreadInvoker.Instance), value => { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(target.Text, value)) { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 1381a904..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTo_ToAWinFormsControl_CarriesTheWinFormsInvoker#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#BindTwoWayDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#BindTwoWayDispatch.g.verified.cs index 83112ae7..f20c691f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#BindTwoWayDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#BindTwoWayDispatch.g.verified.cs @@ -58,12 +58,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - source, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(source, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(source.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -74,12 +70,8 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyViewModel)__o).Name, false, true); - var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - target, - "Text", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyEntry)__o).Text, - true); - var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyEntry), "Text", 5, false); + var targetObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(target, "Text", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyEntry)__o).Text, true); + var targetObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(target.GetType(), "Text", 5, false); var targetObs = targetObsRegistration == null ? (global::System.IObservable)targetObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -90,8 +82,88 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyEntry)__o).Text, false, true); - var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, target, __MauiViewThreadInvoker.Instance); - var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(targetObs, source); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedReverseConverter = null; + if (__convertedReverseConverter == null) + { + __convertedReverseConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedReverseConverter != null && __convertedReverseConverter.GetAffinityForObjects() <= 2) + { + __convertedReverseConverter = null; + } + } + var __convertedReverse = __convertedReverseConverter == null ? (global::System.IObservable)targetObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + targetObs, + __value => + { + object __hint = null; + if (__convertedReverseConverter != null) + { + if (__convertedReverseConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedReverseConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var targetThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, target, __MauiViewThreadInvoker.Instance); + var sourceThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedReverse, source); var d1 = global::ReactiveUI.Binding.BindingErrors.Subscribe(targetThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 1381a904..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.BindTwoWay_ToAMauiTarget_CarriesTheMauiInvokerForTheTargetOnly#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#BindCommandDispatch.g.verified.cs index 2c35f1d7..64b231bb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#BindCommandDispatch.g.verified.cs @@ -50,12 +50,8 @@ internal static partial class __ReactiveUIGeneratedBindings return global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; } - var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Save", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Save, - true); - var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyViewModel), "Save", 5, false); + var __commandChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Save", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Save, true); + var __commandChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Save", 5, false); var __commandChanges = __commandChangesRegistration == null ? (global::System.IObservable)__commandChangesMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,6 +63,33 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); var commandObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__commandChanges, view, __WpfViewThreadInvoker.Instance); + var __controlChangesMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(view, "SaveButton", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyView)__o).SaveButton, true); + var __controlChangesRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(view.GetType(), "SaveButton", 5, false); + var __controlChanges = __controlChangesRegistration == null + ? (global::System.IObservable)__controlChangesMechanism + : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __controlChangesRegistration, + view, + ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveButton)).Body, + "SaveButton", + (object __o) => ((global::TestApp.MyView)__o).SaveButton, + false, + true); + var __controls = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__controlChanges, view, __WpfViewThreadInvoker.Instance); + var __controlBinding = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); + var __controlSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(__controls, __control => + { + __controlBinding.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; + if (__control != null) + { + __controlBinding.Disposable = __BindCommandCore_7FFFD92E6F450351(__control, commandObs); + } + }); + return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__controlSub, __controlBinding); + } + + private static global::System.IDisposable __BindCommandCore_7FFFD92E6F450351(global::TestApp.CommandButton __control, global::System.IObservable commandObs) + { if (global::ReactiveUI.Binding.Fallback.CommandBindingAffinityChecker .HasHigherAffinityPlugin(5, false)) @@ -81,7 +104,7 @@ internal static partial class __ReactiveUIGeneratedBindings __serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; global::System.IObservable __paramObs = global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance; __serial.Disposable = __customBinder.BindCommandToObject( - __cmd, view.SaveButton, __paramObs) + __cmd, __control, __paramObs) ?? global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__binderCmdSub, __serial); @@ -89,21 +112,21 @@ internal static partial class __ReactiveUIGeneratedBindings } - var __originalCommand = view.SaveButton.Command; - var __originalParameter = view.SaveButton.CommandParameter; + var __originalCommand = __control.Command; + var __originalParameter = __control.CommandParameter; var serial = new global::ReactiveUI.Primitives.Disposables.SwapDisposable(); var __cmdSub = global::ReactiveUI.Primitives.SubscribeExtensions.Subscribe(commandObs, cmd => { serial.Disposable = global::ReactiveUI.Primitives.Disposables.EmptyDisposable.Instance; - view.SaveButton.Command = cmd; + __control.Command = cmd; }); return new global::ReactiveUI.Primitives.Disposables.MultipleDisposable( new global::ReactiveUI.Primitives.Disposables.MultipleDisposable(__cmdSub, serial), new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => { - view.SaveButton.CommandParameter = __originalParameter; - view.SaveButton.Command = __originalCommand; + __control.CommandParameter = __originalParameter; + __control.Command = __originalCommand; })); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 1381a904..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#OneWayBindDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#OneWayBindDispatch.g.verified.cs index 249c74ab..52004069 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#OneWayBindDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VTIG.ViewFirstBindings_OnAWpfView_CarryTheWpfInvoker#OneWayBindDispatch.g.verified.cs @@ -51,12 +51,8 @@ internal static partial class __ReactiveUIGeneratedBindings { return null; } - var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - viewModel, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, - true); - var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::TestApp.MyViewModel), "Name", 5, false); + var sourceObsMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(viewModel, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyViewModel)__o).Name, true); + var sourceObsRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(viewModel.GetType(), "Name", 5, false); var sourceObs = sourceObsRegistration == null ? (global::System.IObservable)sourceObsMechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -67,7 +63,47 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::TestApp.MyViewModel)__o).Name, false, true); - var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(sourceObs, view, __WpfViewThreadInvoker.Instance); + global::ReactiveUI.Binding.IBindingTypeConverter __convertedForwardConverter = null; + if (__convertedForwardConverter == null) + { + __convertedForwardConverter = global::ReactiveUI.Binding.BindingConverters.Current.TypedConverters.TryGetConverter(typeof(string), typeof(string)); + if (__convertedForwardConverter != null && __convertedForwardConverter.GetAffinityForObjects() <= 2) + { + __convertedForwardConverter = null; + } + } + var __convertedForward = __convertedForwardConverter == null ? (global::System.IObservable)sourceObs : global::ReactiveUI.Primitives.LinqExtensions.Choose( + sourceObs, + __value => + { + object __hint = null; + if (__convertedForwardConverter != null) + { + if (__convertedForwardConverter is global::ReactiveUI.Binding.IBindingTypeConverter __typed) + { + string __converted; + if (__typed.TryConvert(__value, __hint, out __converted)) + { + return (true, __converted); + } + } + else + { + object __boxed; + if (__convertedForwardConverter.TryConvertTyped(__value, __hint, out __boxed)) + { + return (true, (string)__boxed); + } + } + if (null == null) + { + return (true, (string)__value); + } + return (false, default(string)); + } + return __value != null ? (true, __value) : (true, (string)__value); + }); + var viewThreadObs = global::ReactiveUI.Binding.BindingSchedulers.ObserveOnViewThread(__convertedForward, view, __WpfViewThreadInvoker.Instance); var sub = global::ReactiveUI.Binding.BindingErrors.Subscribe(viewThreadObs, value => { diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VisibilityHintParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VisibilityHintParityTests.cs new file mode 100644 index 00000000..1ef20dc5 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VisibilityHintParityTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes the framework-specific visibility hint contracts in generated bindings. +public class VisibilityHintParityTests +{ + /// Inverse and hidden flags follow the selected framework's converters in both directions. + /// The framework enum namespace. + /// The namespace declaring the hint enum. + /// Whether this framework uses Hidden for a false value. + /// A task representing the asynchronous test. + [Test] + [Arguments("System.Windows", "ReactiveUI", true)] + [Arguments("Microsoft.Maui", "ReactiveUI", true)] + [Arguments("Microsoft.UI.Xaml", "ReactiveUI", false)] + [Arguments("Windows.UI.Xaml", "ReactiveUI.Uno", false)] + [Arguments("System.Windows", "ReactiveUI.Reactive", true)] + public async Task BindTo_UsesNativeVisibilityHints(string framework, string hintNamespace, bool supportsHidden) + { + var result = TestHelper.RunGenerator(Scenario(framework, hintNamespace, supportsHidden), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceDoesNotContain("BindToDispatch.g.cs", "RuntimeBindingConverter"); + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a consumer covering every combination of the native visibility flags. + /// The framework enum namespace. + /// The hint enum namespace. + /// Whether Hidden is meaningful on this framework. + /// The executable consumer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string Scenario(string framework, string hintNamespace, bool supportsHidden) => $$""" + using System; + using ReactiveUI.Binding; + using Visibility = {{framework}}.Visibility; + using Hint = {{hintNamespace}}.BooleanToVisibilityHint; + namespace {{framework}} { public enum Visibility { Visible, Collapsed, Hidden } } + namespace {{hintNamespace}} { [Flags] public enum BooleanToVisibilityHint { None = 0, Inverse = 2, UseHidden = 4 } } + public class Target { public T Value { get; set; } } + public static class Usage + { + public static bool Run() + { + foreach (var hint in new[] { Hint.None, Hint.Inverse, Hint.UseHidden, Hint.Inverse | Hint.UseHidden }) + { + foreach (var value in new[] { true, false }) + { + var visible = (hint & Hint.Inverse) != 0 ? !value : value; + var hidden = {{(supportsHidden ? "true" : "false")}} && (hint & Hint.UseHidden) != 0; + var expected = visible ? Visibility.Visible : hidden ? Visibility.Hidden : Visibility.Collapsed; + var target = new Target(); + IObservable source = new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(value); + using (source.BindTo(target, x => x.Value, conversionHint: hint)) + if (target.Value != expected) return false; + } + foreach (var visibility in new[] { Visibility.Visible, Visibility.Collapsed, Visibility.Hidden }) + { + var expected = (visibility == Visibility.Visible) != ((hint & Hint.Inverse) != 0); + var target = new Target(); + IObservable source = new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(visibility); + using (source.BindTo(target, x => x.Value, conversionHint: hint)) + if (target.Value != expected) return false; + } + } + return true; + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#WhenAnyDispatch.g.verified.cs index 46acc475..ad06c9fd 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MI_STS#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -35,12 +35,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_00002A5206691BE8(global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel obj, global::System.Func, string> selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel)__o).FirstName, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel), "FirstName", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel)__o).FirstName, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "FirstName", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -58,12 +55,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_00002A522B790276(global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel obj, global::System.Func, string> selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel)__o).LastName, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel), "LastName", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultipleInvocationsSameType.MyViewModel)__o).LastName, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "LastName", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#WhenAnyDispatch.g.verified.cs index c3906844..caf56f26 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_2P#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_7FFFD022211E67B0(global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel obj, global::System.Func, global::ReactiveUI.Binding.IObservedChange, string> selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel)__o).FirstName, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel), "FirstName", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel)__o).FirstName, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "FirstName", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -51,12 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel)__o).LastName, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel), "LastName", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyTwoProperties.MyViewModel)__o).LastName, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "LastName", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs index 193f1961..e79709cd 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_7FFFD8112D4F4829(global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel obj, global::System.Func, global::ReactiveUI.Binding.IObservedChange, string> selector) { - var __propObs0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel)__o).Child, - false); - var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel), "Child", 5, false); + var __propObs0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel)__o).Child, false); + var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __propObs0_s0 = __propObs0_s0Registration == null ? (global::System.IObservable)__propObs0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,30 +46,25 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel)__o).Child, false, true); - var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, - __propObs0_p1 => __propObs0_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, + __propObs0_p1 => __propObs0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4603 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4603, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ChildModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__propObs0_p1, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ChildModel)__o).Name, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__propObs0_p1, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ChildModel)__o).Name, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); var __propObs0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__propObs0_s1); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Title", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel)__o).Title, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel), "Title", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Title", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ParentViewModel)__o).Title, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Title", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs index bbff5f6e..7f6941f9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -31,12 +31,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_0000044C01973E8C(global::SharedScenarios.WhenAny.DeepPropertyChain.ParentViewModel obj, global::System.Func, string> selector) { - var __propObs0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ParentViewModel)__o).Child, - false); - var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.DeepPropertyChain.ParentViewModel), "Child", 5, false); + var __propObs0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ParentViewModel)__o).Child, false); + var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __propObs0_s0 = __propObs0_s0Registration == null ? (global::System.IObservable)__propObs0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -47,20 +43,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ParentViewModel)__o).Child, false, true); - var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, - __propObs0_p1 => __propObs0_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, + __propObs0_p1 => __propObs0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3747 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3747, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ChildModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__propObs0_p1, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ChildModel)__o).Name, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__propObs0_p1, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ChildModel)__o).Name, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); var __propObs0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__propObs0_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#WhenAnyDispatch.g.verified.cs index 375be3a3..76c3948f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -31,12 +31,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_00002E2F49F124B9(global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel obj, global::System.Func, string> selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#WhenAnyDispatch.g.verified.cs index 42e3577a..fc209c27 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_INPC_CFP#WhenAnyDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyDispatch.g.cs +//HintName: WhenAnyDispatch.g.cs // #pragma warning disable #nullable enable @@ -29,12 +29,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAny_00002E2F49F124B9(global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel obj, global::System.Func, string> selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.SinglePropertyINPC.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs index b5af30a9..d4e86a8d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFF799BE8759BB(global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel obj, global::System.Func selector) { - var __obsProperty0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, - false); - var __obsProperty0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel), "Child", 5, false); + var __obsProperty0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, false); + var __obsProperty0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obsProperty0_s0 = __obsProperty0_s0Registration == null ? (global::System.IObservable)__obsProperty0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,20 +46,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, false, true); - var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, - __obsProperty0_p1 => __obsProperty0_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, + __obsProperty0_p1 => __obsProperty0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4334 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration4334, __obsProperty0_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Count)).Body, "Count", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Count, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__obsProperty0_p1, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Count, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__obsProperty0_p1, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Count, false)) : (global::System.IObservable>)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal>.Instance); var __obsProperty0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obsProperty0_s1); @@ -71,12 +65,8 @@ internal static partial class __ReactiveUIGeneratedBindings var __switched0 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal, int>(__obsProperty0, __obs => __obs ?? (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); - var __obsProperty1_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, - false); - var __obsProperty1_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel), "Child", 5, false); + var __obsProperty1_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, false); + var __obsProperty1_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obsProperty1_s0 = __obsProperty1_s0Registration == null ? (global::System.IObservable)__obsProperty1_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -87,20 +77,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ParentViewModel)__o).Child, false, true); - var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, - __obsProperty1_p1 => __obsProperty1_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, + __obsProperty1_p1 => __obsProperty1_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Message", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration8139 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration8139, __obsProperty1_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Message)).Body, "Message", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Message, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__obsProperty1_p1, - "Message", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Message, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__obsProperty1_p1, "Message", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Message, false)) : (global::System.IObservable>)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal>.Instance); var __obsProperty1 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obsProperty1_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs index 1c1f2348..7e6a8c93 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,12 +33,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_000031E1E650E394(global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel obj) { - var __obsProperty0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, - false); - var __obsProperty0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel), "Child", 5, false); + var __obsProperty0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, false); + var __obsProperty0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obsProperty0_s0 = __obsProperty0_s0Registration == null ? (global::System.IObservable)__obsProperty0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -49,20 +45,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, false, true); - var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, - __obsProperty0_p1 => __obsProperty0_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, + __obsProperty0_p1 => __obsProperty0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Command1", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4098 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration4098, __obsProperty0_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Command1)).Body, "Command1", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command1, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__obsProperty0_p1, - "Command1", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command1, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__obsProperty0_p1, "Command1", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command1, false)) : (global::System.IObservable>)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal>.Instance); var __obsProperty0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obsProperty0_s1); @@ -70,12 +64,8 @@ internal static partial class __ReactiveUIGeneratedBindings var __switched0 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal, string>(__obsProperty0, __obs => __obs ?? (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); - var __obsProperty1_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, - false); - var __obsProperty1_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel), "Child", 5, false); + var __obsProperty1_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, false); + var __obsProperty1_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obsProperty1_s0 = __obsProperty1_s0Registration == null ? (global::System.IObservable)__obsProperty1_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -86,20 +76,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ParentViewModel)__o).Child, false, true); - var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, - __obsProperty1_p1 => __obsProperty1_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, + __obsProperty1_p1 => __obsProperty1_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Command2", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7858 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration7858, __obsProperty1_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Command2)).Body, "Command2", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command2, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__obsProperty1_p1, - "Command2", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command2, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__obsProperty1_p1, "Command2", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command2, false)) : (global::System.IObservable>)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal>.Instance); var __obsProperty1 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obsProperty1_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#WhenAnyObservableDispatch.g.verified.cs index 18339009..c84c2e57 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_Merge#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,12 +33,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_000004E8406EC0BF(global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel obj) { - var __obsProperty0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Command1", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel)__o).Command1, - true); - var __obsProperty0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel), "Command1", 5, false); + var __obsProperty0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Command1", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel)__o).Command1, true); + + var __obsProperty0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Command1", 5, false); var __obsProperty0 = __obsProperty0Registration == null ? (global::System.IObservable>)__obsProperty0Mechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( @@ -53,12 +50,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __switched0 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal, string>(__obsProperty0, __obs => __obs ?? (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); - var __obsProperty1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Command2", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel)__o).Command2, - true); - var __obsProperty1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel), "Command2", 5, false); + var __obsProperty1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Command2", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesMerge.MyViewModel)__o).Command2, true); + + var __obsProperty1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Command2", 5, false); var __obsProperty1 = __obsProperty1Registration == null ? (global::System.IObservable>)__obsProperty1Mechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#WhenAnyObservableDispatch.g.verified.cs index ca34276f..40e4db2b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_WS#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFE3651E3C40DF(global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel obj, global::System.Func selector) { - var __obsProperty0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Count", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel)__o).Count, - true); - var __obsProperty0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel), "Count", 5, false); + var __obsProperty0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel)__o).Count, true); + + var __obsProperty0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false); var __obsProperty0 = __obsProperty0Registration == null ? (global::System.IObservable>)__obsProperty0Mechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( @@ -54,12 +51,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __switched0 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal, int>(__obsProperty0, __obs => __obs ?? (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); - var __obsProperty1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Message", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel)__o).Message, - true); - var __obsProperty1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel), "Message", 5, false); + var __obsProperty1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Message", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.TwoObservablesWithSelector.MyViewModel)__o).Message, true); + + var __obsProperty1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Message", 5, false); var __obsProperty1 = __obsProperty1Registration == null ? (global::System.IObservable>)__obsProperty1Mechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#WhenAnyObservableDispatch.g.verified.cs index 74b30eaa..6dc82096 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.MI_STS#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFC6DF44F563AC(global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel obj) { - var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Command1", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel)__o).Command1, - true); - var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel), "Command1", 5, false); + var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Command1", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel)__o).Command1, true); + + var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Command1", 5, false); var __obsProperty = __obsPropertyRegistration == null ? (global::System.IObservable>)__obsPropertyMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( @@ -57,12 +54,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFC6DF47F56865(global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel obj) { - var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "Command2", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel)__o).Command2, - true); - var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel), "Command2", 5, false); + var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "Command2", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.MultipleInvocationsSameType.MyViewModel)__o).Command2, true); + + var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Command2", 5, false); var __obsProperty = __obsPropertyRegistration == null ? (global::System.IObservable>)__obsPropertyMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#WhenAnyObservableDispatch.g.verified.cs index fb9c37c4..c524fc7d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFCD9779338746(global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel obj) { - var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "MyCommand", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel)__o).MyCommand, - true); - var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel), "MyCommand", 5, false); + var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "MyCommand", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel)__o).MyCommand, true); + + var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "MyCommand", 5, false); var __obsProperty = __obsPropertyRegistration == null ? (global::System.IObservable>)__obsPropertyMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#WhenAnyObservableDispatch.g.verified.cs index b0e003d6..ac2bdd77 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_CFP#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,12 +28,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFCD9779338746(global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel obj) { - var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>( - obj, - "MyCommand", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel)__o).MyCommand, - true); - var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel), "MyCommand", 5, false); + var __obsPropertyMechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable>(obj, "MyCommand", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.SingleObservable.MyViewModel)__o).MyCommand, true); + + var __obsPropertyRegistration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "MyCommand", 5, false); var __obsProperty = __obsPropertyRegistration == null ? (global::System.IObservable>)__obsPropertyMechanism : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs index 69f1f0b6..d56fe394 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyObservableDispatch.g.cs +//HintName: WhenAnyObservableDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyObservable_7FFFD9447DB2EDB8(global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ParentViewModel obj) { - var __obsProperty_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ParentViewModel)__o).Child, - false); - var __obsProperty_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ParentViewModel), "Child", 5, false); + var __obsProperty_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ParentViewModel)__o).Child, false); + var __obsProperty_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obsProperty_s0 = __obsProperty_s0Registration == null ? (global::System.IObservable)__obsProperty_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,20 +42,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ParentViewModel)__o).Child, false, true); - var __obsProperty_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty_s0, - __obsProperty_p1 => __obsProperty_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose>( + var __obsProperty_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty_s0, + __obsProperty_p1 => __obsProperty_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty_p1.GetType(), "MyCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3583 + ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( + __registration3583, __obsProperty_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.MyCommand)).Body, "MyCommand", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ChildModel)__o).MyCommand, - new global::ReactiveUI.Binding.Observables.PropertyObservable>( - (global::System.ComponentModel.INotifyPropertyChanged)__obsProperty_p1, - "MyCommand", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ChildModel)__o).MyCommand, - false)) + false, false) + : (global::System.IObservable>) +new global::ReactiveUI.Binding.Observables.PropertyObservable>(__obsProperty_p1, "MyCommand", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ChildModel)__o).MyCommand, false)) : (global::System.IObservable>)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal>.Instance); var __obsProperty = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obsProperty_s1); diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs index 14f2fd4e..d450e0a1 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_000009AD7646CA30(global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ParentViewModel obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ParentViewModel)__o).Child, - false); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ParentViewModel), "Child", 5, false); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ParentViewModel)__o).Child, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,20 +42,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ParentViewModel)__o).Child, false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3423 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3423, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ChildModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent1, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ChildModel)__o).Name, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ChildModel)__o).Name, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs1); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#WhenAnyValueDispatch.g.verified.cs index 5dc42c2d..2842cb36 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_2P#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,12 +33,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenAnyValue_0000038CCDEFA447(global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,12 +47,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel)__o).Age, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel), "Age", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwoProperties.MyViewModel)__o).Age, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#WhenAnyValueDispatch.g.verified.cs index 25c8954a..b19373a4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_3P#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -36,12 +36,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenAnyValue_7FFFD35E67D97FB8(global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -53,12 +50,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Age, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel), "Age", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Age, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -70,12 +64,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Score", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Score, - true); - var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel), "Score", 5, false); + var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Score", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyThreeProperties.MyViewModel)__o).Score, true); + + var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, false); var __propObs2 = __propObs2Registration == null ? (global::System.IObservable)__propObs2Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#WhenAnyValueDispatch.g.verified.cs index 3f1128dc..85441824 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_FiveProperties#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -42,12 +42,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenAnyValue_00003486AFD4FA6C(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Prop1", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop1, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel), "Prop1", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Prop1", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop1, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Prop1", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -59,12 +56,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Prop2", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop2, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel), "Prop2", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Prop2", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop2, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Prop2", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -76,12 +70,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Prop3", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop3, - true); - var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel), "Prop3", 5, false); + var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Prop3", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop3, true); + + var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Prop3", 5, false); var __propObs2 = __propObs2Registration == null ? (global::System.IObservable)__propObs2Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -93,12 +84,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs3Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Prop4", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop4, - true); - var __propObs3Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel), "Prop4", 5, false); + var __propObs3Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Prop4", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop4, true); + + var __propObs3Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Prop4", 5, false); var __propObs3 = __propObs3Registration == null ? (global::System.IObservable)__propObs3Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -110,12 +98,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs4Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Prop5", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop5, - true); - var __propObs4Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel), "Prop5", 5, false); + var __propObs4Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Prop5", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyFiveProperties.MyViewModel)__o).Prop5, true); + + var __propObs4Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Prop5", 5, false); var __propObs4 = __propObs4Registration == null ? (global::System.IObservable)__propObs4Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#WhenAnyValueDispatch.g.verified.cs index 5617b64f..af13e469 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_TwelveProperties#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -63,12 +63,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenAnyValue_7FFFC43E653A6144(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value1", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value1, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value1", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value1", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value1, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value1", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -80,12 +77,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value2", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value2, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value2", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value2", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value2, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value2", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -97,12 +91,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value3", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value3, - true); - var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value3", 5, false); + var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value3", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value3, true); + + var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value3", 5, false); var __propObs2 = __propObs2Registration == null ? (global::System.IObservable)__propObs2Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -114,12 +105,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs3Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value4", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value4, - true); - var __propObs3Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value4", 5, false); + var __propObs3Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value4", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value4, true); + + var __propObs3Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value4", 5, false); var __propObs3 = __propObs3Registration == null ? (global::System.IObservable)__propObs3Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -131,12 +119,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs4Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value5", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value5, - true); - var __propObs4Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value5", 5, false); + var __propObs4Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value5", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value5, true); + + var __propObs4Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value5", 5, false); var __propObs4 = __propObs4Registration == null ? (global::System.IObservable)__propObs4Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -148,12 +133,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs5Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value6", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value6, - true); - var __propObs5Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value6", 5, false); + var __propObs5Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value6", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value6, true); + + var __propObs5Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value6", 5, false); var __propObs5 = __propObs5Registration == null ? (global::System.IObservable)__propObs5Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -165,12 +147,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs6Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value7", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value7, - true); - var __propObs6Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value7", 5, false); + var __propObs6Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value7", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value7, true); + + var __propObs6Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value7", 5, false); var __propObs6 = __propObs6Registration == null ? (global::System.IObservable)__propObs6Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -182,12 +161,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs7Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value8", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value8, - true); - var __propObs7Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value8", 5, false); + var __propObs7Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value8", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value8, true); + + var __propObs7Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value8", 5, false); var __propObs7 = __propObs7Registration == null ? (global::System.IObservable)__propObs7Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -199,12 +175,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs8Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value9", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value9, - true); - var __propObs8Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value9", 5, false); + var __propObs8Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value9", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value9, true); + + var __propObs8Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value9", 5, false); var __propObs8 = __propObs8Registration == null ? (global::System.IObservable)__propObs8Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -216,12 +189,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs9Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value10", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value10, - true); - var __propObs9Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value10", 5, false); + var __propObs9Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value10", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value10, true); + + var __propObs9Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value10", 5, false); var __propObs9 = __propObs9Registration == null ? (global::System.IObservable)__propObs9Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -233,12 +203,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs10Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value11", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value11, - true); - var __propObs10Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value11", 5, false); + var __propObs10Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value11", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value11, true); + + var __propObs10Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value11", 5, false); var __propObs10 = __propObs10Registration == null ? (global::System.IObservable)__propObs10Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -250,12 +217,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs11Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Value12", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value12, - true); - var __propObs11Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture), "Value12", 5, false); + var __propObs11Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Value12", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyTwelveProperties.WhenAnyFixture)__o).Value12, true); + + var __propObs11Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Value12", 5, false); var __propObs11 = __propObs11Registration == null ? (global::System.IObservable)__propObs11Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#WhenAnyValueDispatch.g.verified.cs index 88160bc8..6eb1b727 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.MP_WS#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_00000ABCF94E0289(global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel obj, global::System.Func selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel)__o).FirstName, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel), "FirstName", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel)__o).FirstName, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "FirstName", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -51,12 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel)__o).LastName, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel), "LastName", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.MultiPropertyWithSelector.MyViewModel)__o).LastName, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "LastName", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs index 94d22048..dcdc1b89 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_00001B08EE215D76(global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1755 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1755, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableName)).Body, "NullableName", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel)__o).NullableName, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "NullableName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel)__o).NullableName, true)); } @@ -61,13 +63,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_00001B08D596E358(global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableAge", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4422 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4422, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableAge)).Body, "NullableAge", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel)__o).NullableAge, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "NullableAge", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel)__o).NullableAge, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs index 9a3f1724..34006fe3 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_0000263492582AFF(global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1746 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1746, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel)__o).Name, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs index a2dcb3b5..ca9ef5af 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenAnyValueDispatch.g.cs +//HintName: WhenAnyValueDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,13 +28,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_0000263492582AFF(global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1535 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1535, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel)__o).Name, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index dcc60788..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs index f5af7a26..cdeedc7f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_0000130742850C0E(global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level1 obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Model", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level1)__o).Model, - false); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level1), "Model", 5, false); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level1)__o).Model, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Model", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,52 +42,46 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level1)__o).Model, false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3390 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3390, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level2)__o).Model, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent1, - "Model", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level2)__o).Model, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level2)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level3))); - var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, - __parent2 => __parent2 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, + __parent2 => __parent2 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5326 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration5326, __parent2, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level3)__o).Model, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent2, - "Model", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level3)__o).Model, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent2, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Level3)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanged.FourLevelDeepChain.Model))); - var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, - __parent3 => __parent3 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, + __parent3 => __parent3 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7199 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration7199, __parent3, ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Model)__o).Value, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent3, - "Value", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Model)__o).Value, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent3, "Value", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Model)__o).Value, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs3); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs index 8ac9f6ca..ec189030 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFE0F8FBCE6526(global::TestApp.MyAndroidView obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1551 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1551, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", - false, - 5, (object __o) => ((global::TestApp.MyAndroidView)__o).Text, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Text", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyAndroidView)__o).Text, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs index 1ecb0dd0..777f9f27 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFCA2DD50C8513(global::SharedScenarios.WhenChanged.DeepPropertyChain.ParentViewModel obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ParentViewModel)__o).Child, - false); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.DeepPropertyChain.ParentViewModel), "Child", 5, false); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ParentViewModel)__o).Child, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,20 +42,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ParentViewModel)__o).Child, false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3404 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3404, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ChildModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent1, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ChildModel)__o).Name, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ChildModel)__o).Name, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs1); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs index ce40ed5a..d74b3bf5 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFEF8ACA05A8AD(global::SharedScenarios.WhenChanged.IntProperty.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1690 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1690, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Count)).Body, "Count", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.IntProperty.MyViewModel)__o).Count, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.IntProperty.MyViewModel)__o).Count, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index c25ed965..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: KVO - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#ObservationHelpers.g.verified.cs index 5455f052..a0297819 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#ObservationHelpers.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#ObservationHelpers.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ObservationHelpers.g.cs +//HintName: ObservationHelpers.g.cs // #pragma warning disable #nullable enable diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs index 64f9ad03..832635e5 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,14 +30,16 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_000018C06482B4BD(global::TestApp.MyAppleView obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 15, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1541 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1541, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", - false, - 5, (object __o) => ((global::TestApp.MyAppleView)__o).Text, - new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Text", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyAppleView)__o).Text, true)); + false, false) + : (global::System.IObservable) + new __KVOObservable((global::Foundation.NSObject)obj, "text", (global::Foundation.NSObject __o) => ((global::TestApp.MyAppleView)__o).Text, true, false)); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs index 45a88ad4..a176195b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8E16EAA670(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1806 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1806, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Name, true)); } @@ -61,13 +63,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8E19F31E38(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4511 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4511, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Age)).Body, "Age", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Age, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Age, true)); } @@ -92,13 +96,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8D81A702DF(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7209 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration7209, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Score)).Body, "Score", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Score, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Score", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel)__o).Score, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#WhenChangedDispatch.g.verified.cs index e5c40e8c..dd103d5e 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_2P#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,12 +33,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenChanged_7FFFFC6CDD42386A(global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,12 +47,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel)__o).Age, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel), "Age", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyTwoProperties.MyViewModel)__o).Age, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#WhenChangedDispatch.g.verified.cs index c28bb114..eb270e7d 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_3P#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -36,12 +36,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenChanged_000035AA2B4CCE33(global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Name, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel), "Name", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Name, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -53,12 +50,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Age, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel), "Age", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Age", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Age, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -70,12 +64,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Score", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Score, - true); - var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel), "Score", 5, false); + var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Score", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyThreeProperties.MyViewModel)__o).Score, true); + + var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, false); var __propObs2 = __propObs2Registration == null ? (global::System.IObservable)__propObs2Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs index 92ea5acd..99a7335a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFD8A9CF83ACC3(global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel obj, global::System.Func selector) { - var __propObs0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Address", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel)__o).Address, - false); - var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel), "Address", 5, false); + var __propObs0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Address", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel)__o).Address, false); + var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Address", 5, false); var __propObs0_s0 = __propObs0_s0Registration == null ? (global::System.IObservable)__propObs0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,30 +46,25 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel)__o).Address, false, true); - var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, - __propObs0_p1 => __propObs0_p1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, + __propObs0_p1 => __propObs0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4281 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4281, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.City)).Body, "City", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.AddressModel)__o).City, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__propObs0_p1, - "City", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.AddressModel)__o).City, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__propObs0_p1, "City", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.AddressModel)__o).City, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); var __propObs0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__propObs0_s1); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel)__o).Name, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel), "Name", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.MyViewModel)__o).Name, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#WhenChangedDispatch.g.verified.cs index 3984f2d8..711fc8cb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WS#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,12 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_00002EE07F183006(global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel obj, global::System.Func selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel)__o).FirstName, - true); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel), "FirstName", 5, false); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "FirstName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel)__o).FirstName, true); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "FirstName", 5, false); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -51,12 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings false, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel)__o).LastName, - true); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel), "LastName", 5, false); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "LastName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithSelector.MyViewModel)__o).LastName, true); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "LastName", 5, false); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs index 24d2ae33..428193dc 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_000013C2FFE01BC5(global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel1 obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1731 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1731, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel1)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel1)__o).Name, true)); } @@ -61,13 +63,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_00001520715BEC78(global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel2 obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4318 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4318, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Count)).Body, "Count", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel2)__o).Count, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Count", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel2)__o).Count, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NoInvocations#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NoInvocations#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NoInvocations#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs index cf26d170..aae59414 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,12 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFCA7893201375(global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ParentViewModel obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyObservable( - obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ParentViewModel)__o).Child, - false); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ParentViewModel), "Child", 5, false); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ParentViewModel)__o).Child, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,20 +42,18 @@ internal static partial class __ReactiveUIGeneratedBindings (object __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ParentViewModel)__o).Child, false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3475 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3475, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ChildModel)__o).Name, - new global::ReactiveUI.Binding.Observables.PropertyObservable( - (global::System.ComponentModel.INotifyPropertyChanged)__parent1, - "Name", - (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ChildModel)__o).Name, - false)) + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ChildModel)__o).Name, false)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs1); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs index b34b5962..c1ce07c8 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFF613BD504637(global::SharedScenarios.WhenChanged.NullableProperty.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1735 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1735, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableName)).Body, "NullableName", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.NullableProperty.MyViewModel)__o).NullableName, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "NullableName", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullableProperty.MyViewModel)__o).NullableName, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs index 5567c2de..223a7fef 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFD2E8D6FC818E(global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1736 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1736, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel)__o).Name, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs index a84e89a3..89e17652 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,13 +28,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFD2E8D6FC818E(global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1525 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1525, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 5, (object __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel)__o).Name, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index dcc60788..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs index dbc09f59..d97944b2 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_0000039705C7F4BC(global::SharedScenarios.WhenChanged.SinglePropertyReactiveObject.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1786 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1786, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - false, - 10, (object __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyReactiveObject.MyViewModel)__o).Name, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Name", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.SinglePropertyReactiveObject.MyViewModel)__o).Name, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 2cfb2c96..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: WinForms - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#ObservationHelpers.g.verified.cs new file mode 100644 index 00000000..6d71f0ea --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#ObservationHelpers.g.verified.cs @@ -0,0 +1,79 @@ +//HintName: ObservationHelpers.g.cs +// +#pragma warning disable +#nullable enable + +using System; + +namespace ReactiveUI.Binding.Generated.TestAssembly +{ + internal static partial class __ReactiveUIGeneratedBindings + { private sealed class __WinFormsObservable : global::System.IObservable + { + private readonly TSource _source; + private readonly global::System.Func _subscribe; + private readonly global::System.Func _getter; + private readonly bool _distinct; + internal __WinFormsObservable(TSource source, + global::System.Func subscribe, + global::System.Func getter, bool distinct) + { + _source = source; + _subscribe = subscribe; + _getter = getter; + _distinct = distinct; + } + public global::System.IDisposable Subscribe(global::System.IObserver observer) + { + if (observer == null) throw new global::System.ArgumentNullException(nameof(observer)); + return new Subscription(this, observer); + } + private sealed class Subscription : global::System.IDisposable + { + private readonly __WinFormsObservable _parent; + private readonly global::System.IDisposable _inner; + private global::System.IObserver _observer; + private TValue _lastValue; + private bool _hasValue; + internal Subscription(__WinFormsObservable parent, global::System.IObserver observer) + { + _parent = parent; + _observer = observer; + _inner = parent._subscribe(parent._source, Publish); + try + { + Publish(); + } + catch + { + Dispose(); + throw; + } + } + public void Dispose() + { + if (global::System.Threading.Interlocked.Exchange(ref _observer, null) != null) + { + _inner.Dispose(); + } + } + private void Publish() + { + var observer = global::System.Threading.Volatile.Read(ref _observer); + if (observer == null) + { + return; + } + var value = _parent._getter(_parent._source); + if (_parent._distinct && _hasValue && global::System.Collections.Generic.EqualityComparer.Default.Equals(_lastValue, value)) + { + return; + } + _lastValue = value; + _hasValue = true; + observer.OnNext(value); + } + } + } + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs index 5b5fd571..935896dc 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,14 +30,24 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_0000245FAC302C0E(global::TestApp.MyWinFormsControl obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 8, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1571 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1571, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", - false, - 8, (object __o) => ((global::TestApp.MyWinFormsControl)__o).Text, - new global::ReactiveUI.Binding.Observables.EventObservable(__h => ((global::TestApp.MyWinFormsControl)obj).TextChanged += __h, __h => ((global::TestApp.MyWinFormsControl)obj).TextChanged -= __h, () => ((global::TestApp.MyWinFormsControl)obj).Text, true)); + false, false) + : (global::System.IObservable) + new __WinFormsObservable((global::TestApp.MyWinFormsControl)obj, (__source, __notify) => + { + global::System.EventHandler __handler0 = (__sender, __args) => __notify(); + __source.TextChanged += __handler0; + return new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => + { + __source.TextChanged -= __handler0; + }); + }, __source => __source.Text, true)); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 1924c798..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: WinUIDP - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#ObservationHelpers.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#ObservationHelpers.g.verified.cs index 8fdc265a..57cae129 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#ObservationHelpers.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#ObservationHelpers.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ObservationHelpers.g.cs +//HintName: ObservationHelpers.g.cs // #pragma warning disable #nullable enable @@ -8,90 +8,72 @@ namespace ReactiveUI.Binding.Generated.TestAssembly { internal static partial class __ReactiveUIGeneratedBindings - { - /// - /// Fused observable for WinUI DependencyProperty observation. - /// Uses RegisterPropertyChangedCallback / UnregisterPropertyChangedCallback - /// for token-based subscription management. - /// - private sealed class __WinUIDPObservable : global::System.IObservable - { - private readonly global::Microsoft.UI.Xaml.DependencyObject _source; - private readonly global::Microsoft.UI.Xaml.DependencyProperty _dp; - private readonly global::System.Func _getter; - private readonly bool _distinctUntilChanged; - - internal __WinUIDPObservable( - global::Microsoft.UI.Xaml.DependencyObject source, - global::Microsoft.UI.Xaml.DependencyProperty dp, - global::System.Func getter, - bool distinctUntilChanged) - { - _source = source; - _dp = dp; - _getter = getter; - _distinctUntilChanged = distinctUntilChanged; - } - - public global::System.IDisposable Subscribe(global::System.IObserver observer) - { - return new Subscription(this, observer); - } - - private sealed class Subscription : global::System.IDisposable + { private sealed class __WinUIDPObservable : global::System.IObservable { - private readonly __WinUIDPObservable _parent; - private readonly long _token; - private readonly global::System.Collections.Generic.IEqualityComparer _comparer; - private global::System.IObserver _observer; - private T _lastValue; - private bool _hasValue; - - internal Subscription(__WinUIDPObservable parent, global::System.IObserver observer) + private readonly TSource _source; + private readonly global::System.Func _subscribe; + private readonly global::System.Func _getter; + private readonly bool _distinct; + internal __WinUIDPObservable(TSource source, + global::System.Func subscribe, + global::System.Func getter, bool distinct) { - _parent = parent; - _observer = observer; - _comparer = global::System.Collections.Generic.EqualityComparer.Default; - _token = parent._source.RegisterPropertyChangedCallback(parent._dp, OnPropertyChanged); - - // Emit initial value - var initial = parent._getter(parent._source); - _lastValue = initial; - _hasValue = true; - observer.OnNext(initial); + _source = source; + _subscribe = subscribe; + _getter = getter; + _distinct = distinct; } - - private void OnPropertyChanged( - global::Microsoft.UI.Xaml.DependencyObject sender, - global::Microsoft.UI.Xaml.DependencyProperty dp) + public global::System.IDisposable Subscribe(global::System.IObserver observer) { - var obs = System.Threading.Volatile.Read(ref _observer); - if (obs == null) + if (observer == null) throw new global::System.ArgumentNullException(nameof(observer)); + return new Subscription(this, observer); + } + private sealed class Subscription : global::System.IDisposable + { + private readonly __WinUIDPObservable _parent; + private readonly global::System.IDisposable _inner; + private global::System.IObserver _observer; + private TValue _lastValue; + private bool _hasValue; + internal Subscription(__WinUIDPObservable parent, global::System.IObserver observer) { - return; + _parent = parent; + _observer = observer; + _inner = parent._subscribe(parent._source, Publish); + try + { + Publish(); + } + catch + { + Dispose(); + throw; + } } - - var value = _parent._getter(sender); - - if (_parent._distinctUntilChanged && _hasValue && _comparer.Equals(value, _lastValue)) + public void Dispose() { - return; + if (global::System.Threading.Interlocked.Exchange(ref _observer, null) != null) + { + _inner.Dispose(); + } } - - _lastValue = value; - _hasValue = true; - obs.OnNext(value); - } - - public void Dispose() - { - var obs = System.Threading.Interlocked.Exchange(ref _observer, null); - if (obs != null) + private void Publish() { - _parent._source.UnregisterPropertyChangedCallback(_parent._dp, _token); + var observer = global::System.Threading.Volatile.Read(ref _observer); + if (observer == null) + { + return; + } + var value = _parent._getter(_parent._source); + if (_parent._distinct && _hasValue && global::System.Collections.Generic.EqualityComparer.Default.Equals(_lastValue, value)) + { + return; + } + _lastValue = value; + _hasValue = true; + observer.OnNext(value); } } } } - } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs index 2d81823a..8817eab6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,14 +30,21 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDEC5A322381F(global::TestApp.MyWinUIControl obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 6, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1556 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1556, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", - false, - 6, (object __o) => ((global::TestApp.MyWinUIControl)__o).Text, - new __WinUIDPObservable((global::Microsoft.UI.Xaml.DependencyObject)obj, global::TestApp.MyWinUIControl.TextProperty, (global::Microsoft.UI.Xaml.DependencyObject __o) => ((global::TestApp.MyWinUIControl)__o).Text, true)); + false, false) + : (global::System.IObservable) + new __WinUIDPObservable((global::TestApp.MyWinUIControl)obj, (__source, __notify) => + { + var __token = __source.RegisterPropertyChangedCallback(global::TestApp.MyWinUIControl.TextProperty, (__sender, __property) => __notify()); + return new global::ReactiveUI.Primitives.Disposables.ActionDisposable(() => + __source.UnregisterPropertyChangedCallback(global::TestApp.MyWinUIControl.TextProperty, __token)); + }, __source => __source.Text, true)); } } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs index 033b5df8..7b15ad2e 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangedDispatch.g.cs +//HintName: WhenChangedDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDFDB348EEE2E(global::TestApp.MyWpfControl obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1546 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1546, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", - false, - 5, (object __o) => ((global::TestApp.MyWpfControl)__o).Text, + false, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Text", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::TestApp.MyWpfControl)__o).Text, true)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs index 0691707c..8f7a1d2c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,11 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_00001C315F48E7DF(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1 obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1), "Model", 5, true); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Model", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -43,30 +40,48 @@ internal static partial class __ReactiveUIGeneratedBindings ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model, - true, + false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent1, + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3410 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3410, + __parent1, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model, + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3))); - var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, - __parent2 => __parent2 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent2, + var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, + __parent2 => __parent2 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5359 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration5359, + __parent2, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model, + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent2, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model))); - var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, - __parent3 => __parent3 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent3, + var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, + __parent3 => __parent3 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7244 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration7244, + __parent3, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value, + true, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)__parent3, "Value", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs3); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs index 81fc561e..21384f8c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,11 +28,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_00001C315F48E7DF(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1 obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1), "Model", 5, true); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Model", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -41,30 +38,48 @@ internal static partial class __ReactiveUIGeneratedBindings ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level1)__o).Model, - true, + false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent1, + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3181 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3181, + __parent1, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model, + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent1, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level2)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3))); - var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, - __parent2 => __parent2 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent2, + var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, + __parent2 => __parent2 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5130 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration5130, + __parent2, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model, + false, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyObservable(__parent2, "Model", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Level3)__o).Model, false)) : (global::System.IObservable)new global::ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(default(global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model))); - var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, - __parent3 => __parent3 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent3, + var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, + __parent3 => __parent3 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7015 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration7015, + __parent3, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value) + (object __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value, + true, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)__parent3, "Value", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs3); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs index 075ea21a..82c8585a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,11 +30,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFF85E7720B498(global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel), "Child", 5, true); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -43,14 +40,20 @@ internal static partial class __ReactiveUIGeneratedBindings ((global::System.Linq.Expressions.Expression>)(__e => __e.Child)).Body, "Child", (object __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child, - true, + false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent1, + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3423 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3423, + __parent1, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name) + (object __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name, + true, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)__parent1, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs1); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs index fcaed434..f75b51f6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,11 +28,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFF85E7720B498(global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel obj) { - var __obs0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Child", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child); - var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel), "Child", 5, true); + var __obs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Child", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child, false); + var __obs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Child", 5, false); var __obs0 = __obs0Registration == null ? (global::System.IObservable)__obs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -41,14 +38,20 @@ internal static partial class __ReactiveUIGeneratedBindings ((global::System.Linq.Expressions.Expression>)(__e => __e.Child)).Body, "Child", (object __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ParentViewModel)__o).Child, - true, + false, true); - var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, - __parent1 => __parent1 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__parent1, + var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, + __parent1 => __parent1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3206 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration3206, + __parent1, + ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name) + (object __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name, + true, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)__parent1, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); return global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__obs1); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs index 3aeb1b3c..32dbf00b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFE1C28268960F(global::TestApp.MyChangingViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1586 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1586, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - true, - 5, (object __o) => ((global::TestApp.MyChangingViewModel)__o).Name, + true, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::TestApp.MyChangingViewModel)__o).Name)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#WhenChangingDispatch.g.verified.cs index 18659ae9..2bd22c0a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -33,11 +33,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenChanging_000011A95039C09F(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Name); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel), "Name", 5, true); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Name); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -49,11 +47,9 @@ internal static partial class __ReactiveUIGeneratedBindings true, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Age); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel), "Age", 5, true); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Age", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Age); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, true); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#WhenChangingDispatch.g.verified.cs index fe41f252..7bd20338 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_2P_CFP#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,11 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenChanging_000011A95039C09F(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Name); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel), "Name", 5, true); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Name); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -46,11 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Age); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel), "Age", 5, true); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Age", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyTwoProperties.MyViewModel)__o).Age); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, true); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#WhenChangingDispatch.g.verified.cs index 5aeaa9cf..7360b94c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_3P#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -36,11 +36,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable> __WhenChanging_7FFFF53EB8A56F90(global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel obj) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Name); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel), "Name", 5, true); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Name); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -52,11 +50,9 @@ internal static partial class __ReactiveUIGeneratedBindings true, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Age", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Age); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel), "Age", 5, true); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Age", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Age); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, true); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -68,11 +64,9 @@ internal static partial class __ReactiveUIGeneratedBindings true, true); - var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Score", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Score); - var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel), "Score", 5, true); + var __propObs2Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Score", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyThreeProperties.MyViewModel)__o).Score); + + var __propObs2Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, true); var __propObs2 = __propObs2Registration == null ? (global::System.IObservable)__propObs2Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs index 3151c2b1..a83d9614 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,11 +34,8 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFFF97E6DC4D84(global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel obj, global::System.Func selector) { - var __propObs0_s0Mechanism = (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Address", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel)__o).Address); - var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel), "Address", 5, true); + var __propObs0_s0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyObservable(obj, "Address", (global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel)__o).Address, false); + var __propObs0_s0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Address", 5, false); var __propObs0_s0 = __propObs0_s0Registration == null ? (global::System.IObservable)__propObs0_s0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -47,23 +44,27 @@ internal static partial class __ReactiveUIGeneratedBindings ((global::System.Linq.Expressions.Expression>)(__e => __e.Address)).Body, "Address", (object __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel)__o).Address, - true, + false, true); - var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, - __propObs0_p1 => __propObs0_p1 != null - ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)__propObs0_p1, + var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, + __propObs0_p1 => __propObs0_p1 != null + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4301 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration4301, + __propObs0_p1, + ((global::System.Linq.Expressions.Expression>)(__e => __e.City)).Body, "City", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.AddressModel)__o).City) + (object __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.AddressModel)__o).City, + true, false) + : (global::System.IObservable) +new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)__propObs0_p1, "City", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.AddressModel)__o).City)) : (global::System.IObservable)global::ReactiveUI.Primitives.Advanced.ImmutableEmptySignal.Instance); var __propObs0 = global::ReactiveUI.Primitives.LinqExtensions.DistinctUntilChanged(__propObs0_s1); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "Name", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel)__o).Name); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel), "Name", 5, true); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.MyViewModel)__o).Name); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#WhenChangingDispatch.g.verified.cs index 625f5d84..96b6637b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WS#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -34,11 +34,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_00001B25E45F4801(global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel obj, global::System.Func selector) { - var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "FirstName", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel)__o).FirstName); - var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel), "FirstName", 5, true); + var __propObs0Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "FirstName", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel)__o).FirstName); + + var __propObs0Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "FirstName", 5, true); var __propObs0 = __propObs0Registration == null ? (global::System.IObservable)__propObs0Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( @@ -50,11 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings true, true); - var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable( - (global::System.ComponentModel.INotifyPropertyChanging)obj, - "LastName", - (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel)__o).LastName); - var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel), "LastName", 5, true); + var __propObs1Mechanism = new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "LastName", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithSelector.MyViewModel)__o).LastName); + + var __propObs1Registration = global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "LastName", 5, true); var __propObs1 = __propObs1Registration == null ? (global::System.IObservable)__propObs1Mechanism : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index dcc60788..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs index d770db84..07bb690f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD5A9BCD06EB3(global::TestApp.MyReactiveViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1586 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1586, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - true, - 10, (object __o) => ((global::TestApp.MyReactiveViewModel)__o).Name, + true, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::TestApp.MyReactiveViewModel)__o).Name)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs index 2d84980d..66e891b7 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD2B6A9CCF5C7(global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1746 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1746, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - true, - 5, (object __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel)__o).Name, + true, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel)__o).Name)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index 377a7c11..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: INPC - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs index df670534..de9cc0b4 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -28,13 +28,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD2B6A9CCF5C7(global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1535 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1535, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - true, - 5, (object __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel)__o).Name, + true, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel)__o).Name)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs deleted file mode 100644 index dcc60788..00000000 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#GeneratedBinderRegistration.g.verified.cs +++ /dev/null @@ -1,25 +0,0 @@ -//HintName: GeneratedBinderRegistration.g.cs -// -#pragma warning disable -#nullable enable - -namespace ReactiveUI.Binding.Generated -{ - /// - /// Auto-generated binder registration. Registers high-affinity - /// ICreatesObservableForProperty implementations detected at compile time. - /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static class __GeneratedBinderRegistration - { - /// - /// Registers all generated binders with the Splat service locator. - /// - internal static void Initialize() - { - // Generated binder registrations will be added here in future phases. - // Each per-kind binder provides high-affinity observation for detected types. - // Detected types for kind: ReactiveObject - } - } -} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs index 80a15522..7c2c22df 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: WhenChangingDispatch.g.cs +//HintName: WhenChangingDispatch.g.cs // #pragma warning disable #nullable enable @@ -30,13 +30,15 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFCA92B45AE4E5(global::SharedScenarios.WhenChanging.SinglePropertyReactiveObject.MyViewModel obj) { - return global::ReactiveUI.Binding.Observables.PluginObservationSource.Choose( + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1796 + ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( + __registration1796, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", - true, - 10, (object __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyReactiveObject.MyViewModel)__o).Name, + true, false) + : (global::System.IObservable) new global::ReactiveUI.Binding.Observables.PropertyChangingObservable((global::System.ComponentModel.INotifyPropertyChanging)obj, "Name", (global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.SinglePropertyReactiveObject.MyViewModel)__o).Name)); } diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterOverrideParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterOverrideParityTests.cs new file mode 100644 index 00000000..b7168bd0 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterOverrideParityTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Checks custom set-method precedence and layout recovery after a native collection fault. +public class WinFormsSetterOverrideParityTests +{ + /// The strongest set-method provider is selected once and must beat the native score. + /// The native owner type. + /// The custom provider score. + /// A task representing the asynchronous test. + [Test] + [Arguments("Panel", 9)] + [Arguments("Panel", 10)] + [Arguments("Panel", 11)] + [Arguments("TableLayoutPanel", 9)] + [Arguments("TableLayoutPanel", 10)] + [Arguments("TableLayoutPanel", 11)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task BindTo_CustomSetterRequiresHigherAffinity(string panel, int score) => + AssertRuns(OverrideScenario(panel, score)); + + /// Layout resumes when the native collection rejects a write. + /// The native owner type. + /// A task representing the asynchronous test. + [Test] + [Arguments("Panel")] + [Arguments("TableLayoutPanel")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task BindTo_NativeFailureResumesLayout(string panel) => + AssertRuns(WinFormsSetterParityTests.Framework + $$""" + public class Target { public System.Windows.Forms.{{panel}} Panel { get; } = new System.Windows.Forms.{{panel}}(); } + public static class Usage + { + public static bool Run() + { + var target = new Target(); + var source = new ReactiveUI.Primitives.Signals.Signal>(); + using (source.BindTo(target, x => x.Panel.Controls)) + { + target.Panel.Controls.ThrowOnAdd = true; + try { source.OnNext(new List()); } + catch (InvalidOperationException) {} + return target.Panel.Suspends == 1 && target.Panel.Resumes == 1; + } + } + } + """); + + /// Executes the generated consumer with private converter registrations. + /// The executable consumer. + /// A task representing the asynchronous test. + private static async Task AssertRuns(string source) + { + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + var (assembly, context) = TestHelper.EmitAndLoad(result, true); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Builds a custom setter whose calls and affinity queries remain observable. + /// The collection owner type. + /// The custom affinity. + /// The executable consumer. + private static string OverrideScenario(string panel, int score) => WinFormsSetterParityTests.Framework + $$""" + public class Target { public System.Windows.Forms.{{panel}} Panel { get; } = new System.Windows.Forms.{{panel}}(); } + public class Setter : ISetMethodBindingConverter + { + public int Calls, Votes; + public int GetAffinityForObjects(Type from, Type to) { Votes++; return {{score}}; } + public object PerformSet(object target, object value, object[] args) + { + var collection = (System.Windows.Forms.Control.ControlCollection)target; + if (!(value is List) || args != null) throw new InvalidOperationException("Invalid adapter arguments"); + Calls++; + return collection; + } + } + public static class Usage + { + public static bool Run() + { + var setter = new Setter(); + BindingConverters.Current.SetMethodConverters.Register(setter); + var target = new Target(); + var source = new ReactiveUI.Primitives.Signals.Signal>(); + using (source.BindTo(target, x => x.Panel.Controls)) + { + var votes = setter.Votes; + source.OnNext(new List()); + source.OnNext(new List()); + var custom = {{score}} > 10; + return setter.Calls == (custom ? 2 : 0) && setter.Votes == votes + && target.Panel.Suspends == (custom ? 0 : 2); + } + } + } + """; +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterParityTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterParityTests.cs new file mode 100644 index 00000000..57b0903a --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WinFormsSetterParityTests.cs @@ -0,0 +1,186 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests; + +/// Executes collection mutations through generated WinForms set-method adapters. +public class WinFormsSetterParityTests +{ + /// The native collection and layout contracts exercised by the generated consumer. + internal const string Framework = """ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using ReactiveUI.Binding; + namespace System.Windows.Forms + { + public class Control + { + public int Suspends, Resumes; + public void SuspendLayout() { Suspends++; } + public void ResumeLayout() { Resumes++; } + public class ControlCollection : List + { + public Control Owner { get; } + public bool ThrowOnAdd { get; set; } + public Control[] LastInput { get; private set; } + public ControlCollection(Control owner) { Owner = owner; } + public void AddRange(Control[] controls) + { + if (ThrowOnAdd) throw new InvalidOperationException("Native collection failure"); + LastInput = controls; + base.AddRange(controls); + } + } + } + public class Button : Control {} + public class Panel : Control + { + public ControlCollection Controls { get; } + public Panel() { Controls = new ControlCollection(this); } + } + public class TableLayoutControlCollection : Control.ControlCollection + { + public Control Container => Owner; + public TableLayoutControlCollection(Control owner) : base(owner) {} + } + public class TableLayoutPanel : Control + { + public TableLayoutControlCollection Controls { get; } + public TableLayoutPanel() { Controls = new TableLayoutControlCollection(this); } + } + } + """; + + /// Read-only control collections receive typed values with balanced layout suspension. + /// The native collection owner. + /// The binding entry point. + /// A task representing the asynchronous test. + [Test] + [Arguments("Panel", "BindTo")] + [Arguments("TableLayoutPanel", "BindTo")] + [Arguments("Panel", "OneWayBind")] + [Arguments("TableLayoutPanel", "OneWayBind")] + public async Task CollectionBinding_MutatesExistingCollection(string panel, string api) + { + var result = TestHelper.RunGenerator(Scenario(panel, api), LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await result.GeneratedSourceDoesNotContain($"{api}Dispatch.g.cs", "RuntimeBindingConverter"); + await AssertRuns(result); + } + + /// The System.Reactive package applies the same native collection mutations. + /// The native collection owner. + /// The binding entry point. + /// A task representing the asynchronous test. + [Test] + [Arguments("Panel", "BindTo")] + [Arguments("TableLayoutPanel", "OneWayBind")] + public async Task CollectionBinding_ReactiveRuntimeMutatesExistingCollection(string panel, string api) + { + var source = Scenario(panel, api).Replace("using ReactiveUI.Binding;", "using ReactiveUI.Binding.Reactive;", StringComparison.Ordinal); + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10, null, true); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// Concrete control arrays reach the native array API without an intermediate copy. + /// The native collection owner. + /// A task representing the asynchronous test. + [Test] + [Arguments("Panel")] + [Arguments("TableLayoutPanel")] + public async Task CollectionBinding_ReusesConcreteArray(string panel) + { + var source = Framework + $$""" + public class Target { public System.Windows.Forms.{{panel}} Panel { get; } = new System.Windows.Forms.{{panel}}(); } + public static class Usage + { + public static bool Run() + { + var target = new Target(); + var controls = new[] { new System.Windows.Forms.Button() }; + IObservable source = new ReactiveUI.Primitives.Advanced.ImmediateReturnSignal(controls); + using (source.BindTo(target, x => x.Panel.Controls)) + return ReferenceEquals(controls, target.Panel.Controls.LastInput) + && target.Panel.Controls.Count == 1 && ReferenceEquals(controls[0], target.Panel.Controls[0]); + } + } + """; + var result = TestHelper.RunGenerator(source, LanguageVersion.CSharp10); + await result.CompilationSucceeds(); + await AssertRuns(result); + } + + /// Runs the generated consumer's behavior checks. + /// The compiled consumer. + /// A task representing the asynchronous test. + private static async Task AssertRuns(GeneratorTestResult result) + { + var (assembly, context) = TestHelper.EmitAndLoad(result); + try + { + var run = assembly.GetType("Usage")!.GetMethod("Run", BindingFlags.Public | BindingFlags.Static)!; + await Assert.That((bool)run.Invoke(null, null)!).IsTrue(); + } + finally + { + context.Unload(); + } + } + + /// Creates a minimal WinForms collection contract and a notifying source. + /// The framework panel. + /// The binding API. + /// The executable consumer. + private static string Scenario(string panel, string api) + { + var bind = api switch + { + "BindTo" => "model.WhenChanged(x => x.Items).BindTo(view, x => x.Panel.Controls)", + _ => "view.OneWayBind(model, x => x.Items, x => x.Panel.Controls)", + }; + return Framework + $$""" + public class Model : INotifyPropertyChanged + { + private List _items = new List(); + public event PropertyChangedEventHandler PropertyChanged; + public List Items + { + get { return _items; } + set { _items = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Items")); } + } + } + public class View : IViewFor + { + public Model ViewModel { get; set; } + object IViewFor.ViewModel { get { return ViewModel; } set { ViewModel = (Model)value; } } + public System.Windows.Forms.{{panel}} Panel { get; } = new System.Windows.Forms.{{panel}}(); + } + public static class Usage + { + public static bool Run() + { + var model = new Model(); + var view = new View { ViewModel = model }; + var collection = view.Panel.Controls; + var button = new System.Windows.Forms.Button(); + using ({{bind}}) + { + model.Items = new List { button }; + if (collection.Count != 1 || collection[0] != button) return false; + model.Items = new List(); + if (collection.Count != 0 || view.Panel.Suspends != 3 || view.Panel.Resumes != 3) return false; + } + model.Items = new List { button }; + return ReferenceEquals(collection, view.Panel.Controls) && collection.Count == 0 && view.Panel.Suspends == 3; + } + } + """; + } +} diff --git a/src/tests/ReactiveUI.Binding.Tests/Fallback/ObservationAffinityCheckerTests.cs b/src/tests/ReactiveUI.Binding.Tests/Fallback/ObservationAffinityCheckerTests.cs index 7e194fa6..0f13226f 100644 --- a/src/tests/ReactiveUI.Binding.Tests/Fallback/ObservationAffinityCheckerTests.cs +++ b/src/tests/ReactiveUI.Binding.Tests/Fallback/ObservationAffinityCheckerTests.cs @@ -195,10 +195,7 @@ public async Task HasHigherAffinityPlugin_BeforeChangedTrue_PassesThroughToPlugi } } - /// - /// Verifies that when multiple plugins are registered and only one has higher affinity, - /// the method returns true (short-circuits on first match). - /// + /// Verifies that a stronger provider wins among several registered providers. /// A task representing the asynchronous test operation. [Test] public async Task HasHigherAffinityPlugin_MultiplePlugins_OnlyOneHigher_ReturnsTrue() @@ -343,6 +340,169 @@ public async Task FindHigherAffinityPlugin_NullPropertyName_ThrowsArgumentNullEx await Assert.That(action).ThrowsExactly(); } + /// Repeated selections reuse the custom score while comparing each generated affinity independently. + /// A task representing the asynchronous test operation. + [Test] + public async Task FindHigherAffinityPlugin_RepeatedSelection_ScoresOnceAcrossGeneratedAffinities() + { + AppLocator.UnregisterAll(); + try + { + var plugin = new StubObservableForProperty(HigherPluginAffinity); + AppLocator.Register(() => plugin); + ObservationAffinityChecker.Refresh(); + + var winner = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + var tie = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, HigherPluginAffinity, false); + var repeated = ObservationAffinityChecker.HasHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + + await Assert.That(winner).IsSameReferenceAs(plugin); + await Assert.That(tie).IsNull(); + await Assert.That(repeated).IsTrue(); + await Assert.That(plugin.AffinityCallCount).IsEqualTo(1); + } + finally + { + RestoreDefaultPlugins(); + } + } + + /// Types, properties and notification timing each receive independent cached scores. + /// A task representing the asynchronous test operation. + [Test] + public async Task FindHigherAffinityPlugin_DifferentObservationKeys_ScoresEachOnce() + { + const int repetitions = 2; + const int distinctObservationCount = 4; + AppLocator.UnregisterAll(); + try + { + var plugin = new StubObservableForProperty(HigherPluginAffinity); + AppLocator.Register(() => plugin); + ObservationAffinityChecker.Refresh(); + + for (var repeat = 0; repeat < repetitions; repeat++) + { + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(object), ObservedPropertyName, GeneratedAffinity, false); + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), UnscoredPropertyName, GeneratedAffinity, false); + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, true); + } + + await Assert.That(plugin.AffinityCallCount).IsEqualTo(distinctObservationCount); + } + finally + { + RestoreDefaultPlugins(); + } + } + + /// Refresh invalidates scored selections even when the registered provider instances are unchanged. + /// A task representing the asynchronous test operation. + [Test] + public async Task FindHigherAffinityPlugin_Refresh_ScoresRegistrationsAgain() + { + const int expectedAffinityCalls = 2; + AppLocator.UnregisterAll(); + try + { + var plugin = new StubObservableForProperty(LowerPluginAffinity); + AppLocator.Register(() => plugin); + ObservationAffinityChecker.Refresh(); + + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + ObservationAffinityChecker.Refresh(); + _ = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + + await Assert.That(plugin.AffinityCallCount).IsEqualTo(expectedAffinityCalls); + } + finally + { + RestoreDefaultPlugins(); + } + } + + /// The highest custom score wins regardless of registration order. + /// A task representing the asynchronous test operation. + [Test] + public async Task FindHigherAffinityPlugin_MultipleWinners_ReturnsHighestAffinity() + { + AppLocator.UnregisterAll(); + try + { + var highest = new StubObservableForProperty(HigherPluginAffinity); + AppLocator.Register(static () => new StubObservableForProperty(GeneratedAffinity)); + AppLocator.Register(() => highest); + AppLocator.Register(static () => new StubObservableForProperty(AlternatePluginAffinity)); + ObservationAffinityChecker.Refresh(); + + var winner = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, LowerPluginAffinity, false); + + await Assert.That(winner).IsSameReferenceAs(highest); + } + finally + { + RestoreDefaultPlugins(); + } + } + + /// A resolution overlapping refresh cannot publish its registrations into the refreshed cache. + /// Cancels the synchronization if the test is interrupted. + /// A task representing the asynchronous test operation. + [Test] + public async Task FindHigherAffinityPlugin_RefreshDuringResolution_DiscardsStaleRegistrations(CancellationToken cancellationToken) + { + AppLocator.UnregisterAll(); + using var releaseResolution = new ManualResetEventSlim(); + var resolutionStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Task? pending = null; + try + { + var stale = new StubObservableForProperty(HigherPluginAffinity); + var current = new StubObservableForProperty(HigherPluginAffinity); + var resolutionCount = 0; + AppLocator.Register(() => + { + if (Interlocked.Increment(ref resolutionCount) == 1) + { + resolutionStarted.SetResult(true); + releaseResolution.Wait(cancellationToken); + return stale; + } + + return current; + }); + ObservationAffinityChecker.Refresh(); + pending = Task.Run( + static () => ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false), + cancellationToken); + _ = await resolutionStarted.Task.WaitAsync(cancellationToken); + + ObservationAffinityChecker.Refresh(); + releaseResolution.Set(); + _ = await pending; + var winner = ObservationAffinityChecker.FindHigherAffinityPlugin(typeof(string), ObservedPropertyName, GeneratedAffinity, false); + + await Assert.That(winner).IsSameReferenceAs(current); + } + finally + { + releaseResolution.Set(); + try + { + if (pending is not null) + { + _ = await pending; + } + } + finally + { + RestoreDefaultPlugins(); + } + } + } + /// Restores default plugins by re-initializing the binding infrastructure. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void RestoreDefaultPlugins() @@ -396,9 +556,15 @@ public StubObservableForProperty(int beforeChangedAffinity, int afterChangedAffi _afterChangedAffinity = afterChangedAffinity; } + /// Gets the number of times this registration has been scored. + public int AffinityCallCount { get; private set; } + /// - public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged) => - beforeChanged ? _beforeChangedAffinity : _afterChangedAffinity; + public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged) + { + AffinityCallCount++; + return beforeChanged ? _beforeChangedAffinity : _afterChangedAffinity; + } /// public IObservable> GetNotificationForProperty( diff --git a/src/tests/ReactiveUI.Binding.Tests/Observables/AppliedChangeObservableTests.cs b/src/tests/ReactiveUI.Binding.Tests/Observables/AppliedChangeObservableTests.cs index dcacc18c..af11ca1b 100644 --- a/src/tests/ReactiveUI.Binding.Tests/Observables/AppliedChangeObservableTests.cs +++ b/src/tests/ReactiveUI.Binding.Tests/Observables/AppliedChangeObservableTests.cs @@ -25,6 +25,19 @@ public class AppliedChangeObservableTests /// What an observer that joined after the first change receives. private static readonly string[] SecondOnly = [SecondValue]; + /// The public boundary is needed only while a subscriber is present. + /// The asynchronous test operation. + [Test] + public async Task HasObservers_TracksSubscriptionLifetime() + { + var changes = new AppliedChangeObservable(); + await Assert.That(changes.HasObservers).IsFalse(); + var subscription = changes.Subscribe(new RecordingObserver()); + await Assert.That(changes.HasObservers).IsTrue(); + subscription.Dispose(); + await Assert.That(changes.HasObservers).IsFalse(); + } + /// Reporting a change with nobody watching is what a binding usually does. /// A task representing the asynchronous test operation. [Test] diff --git a/src/tests/ReactiveUI.Binding.Tests/ReactiveUI.Binding.Tests.csproj b/src/tests/ReactiveUI.Binding.Tests/ReactiveUI.Binding.Tests.csproj index 64ae57be..c89fc1bd 100644 --- a/src/tests/ReactiveUI.Binding.Tests/ReactiveUI.Binding.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.Tests/ReactiveUI.Binding.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0;net11.0 + $(BindingTestTargets) false enable diff --git a/src/tests/ReactiveUI.Binding.WinForms.Tests.Reactive/ReactiveUI.Binding.WinForms.Tests.Reactive.csproj b/src/tests/ReactiveUI.Binding.WinForms.Tests.Reactive/ReactiveUI.Binding.WinForms.Tests.Reactive.csproj index 30110fd2..583d4b2b 100644 --- a/src/tests/ReactiveUI.Binding.WinForms.Tests.Reactive/ReactiveUI.Binding.WinForms.Tests.Reactive.csproj +++ b/src/tests/ReactiveUI.Binding.WinForms.Tests.Reactive/ReactiveUI.Binding.WinForms.Tests.Reactive.csproj @@ -2,7 +2,7 @@ - $(BindingWindowsTargets) + $(BindingTestPlatformTargets) true false diff --git a/src/tests/ReactiveUI.Binding.WinForms.Tests/ReactiveUI.Binding.WinForms.Tests.csproj b/src/tests/ReactiveUI.Binding.WinForms.Tests/ReactiveUI.Binding.WinForms.Tests.csproj index 953a6306..8655ded5 100644 --- a/src/tests/ReactiveUI.Binding.WinForms.Tests/ReactiveUI.Binding.WinForms.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.WinForms.Tests/ReactiveUI.Binding.WinForms.Tests.csproj @@ -2,7 +2,7 @@ - $(BindingWindowsTargets) + $(BindingTestPlatformTargets) true false diff --git a/src/tests/ReactiveUI.Binding.Wpf.Tests.Reactive/ReactiveUI.Binding.Wpf.Tests.Reactive.csproj b/src/tests/ReactiveUI.Binding.Wpf.Tests.Reactive/ReactiveUI.Binding.Wpf.Tests.Reactive.csproj index 96ead7ae..298425b3 100644 --- a/src/tests/ReactiveUI.Binding.Wpf.Tests.Reactive/ReactiveUI.Binding.Wpf.Tests.Reactive.csproj +++ b/src/tests/ReactiveUI.Binding.Wpf.Tests.Reactive/ReactiveUI.Binding.Wpf.Tests.Reactive.csproj @@ -2,7 +2,7 @@ - $(BindingWindowsTargets) + $(BindingTestPlatformTargets) true false diff --git a/src/tests/ReactiveUI.Binding.Wpf.Tests/ReactiveUI.Binding.Wpf.Tests.csproj b/src/tests/ReactiveUI.Binding.Wpf.Tests/ReactiveUI.Binding.Wpf.Tests.csproj index c0cfdf33..af1f8fd1 100644 --- a/src/tests/ReactiveUI.Binding.Wpf.Tests/ReactiveUI.Binding.Wpf.Tests.csproj +++ b/src/tests/ReactiveUI.Binding.Wpf.Tests/ReactiveUI.Binding.Wpf.Tests.csproj @@ -3,7 +3,7 @@ - $(BindingWindowsTargets) + $(BindingTestPlatformTargets) true false From 56e43c8ca3e3e6d0bbe5da87fdd37be963e5970b Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:32:22 +1000 Subject: [PATCH 2/2] further work --- .../Observation/ChainRegistrationEmitter.cs | 2 +- ...mandPath#BindCommandDispatch.g.verified.cs | 4 +-- ...Path#BindInteractionDispatch.g.verified.cs | 4 +-- ...Path#BindInteractionDispatch.g.verified.cs | 4 +-- ...ndPath#InvokeCommandDispatch.g.verified.cs | 4 +-- .../Plugins/ChainRegistrationEmitterTests.cs | 35 +++++++++++++++++++ .../WAG.MP_DC#WhenAnyDispatch.g.verified.cs | 4 +-- .../WAG.SP_DC#WhenAnyDispatch.g.verified.cs | 4 +-- ...CL#WhenAnyObservableDispatch.g.verified.cs | 8 ++--- ...ge#WhenAnyObservableDispatch.g.verified.cs | 8 ++--- ...DC#WhenAnyObservableDispatch.g.verified.cs | 4 +-- ...AVG.DPC#WhenAnyValueDispatch.g.verified.cs | 4 +-- ...perties#WhenAnyValueDispatch.g.verified.cs | 8 ++--- ...SP_INPC#WhenAnyValueDispatch.g.verified.cs | 4 +-- ...NPC_CFP#WhenAnyValueDispatch.g.verified.cs | 4 +-- ...WCG.4LDC#WhenChangedDispatch.g.verified.cs | 12 +++---- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- .../WCG.DPC#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ...iewModel#WhenChangedDispatch.g.verified.cs | 12 +++---- ...G.MP_WDC#WhenChangedDispatch.g.verified.cs | 4 +-- ...ewModels#WhenChangedDispatch.g.verified.cs | 8 ++--- ...givingDC#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ....SP_INPC#WhenChangedDispatch.g.verified.cs | 4 +-- ...INPC_CFP#WhenChangedDispatch.g.verified.cs | 4 +-- ...veObject#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ...Property#WhenChangedDispatch.g.verified.cs | 4 +-- ...nG.4LDC#WhenChangingDispatch.g.verified.cs | 12 +++---- ...LDC_CFP#WhenChangingDispatch.g.verified.cs | 12 +++---- ...CnG.DPC#WhenChangingDispatch.g.verified.cs | 4 +-- ...DPC_CFP#WhenChangingDispatch.g.verified.cs | 4 +-- ...roperty#WhenChangingDispatch.g.verified.cs | 4 +-- ....MP_WDC#WhenChangingDispatch.g.verified.cs | 4 +-- ...roperty#WhenChangingDispatch.g.verified.cs | 4 +-- ...SP_INPC#WhenChangingDispatch.g.verified.cs | 4 +-- ...NPC_CFP#WhenChangingDispatch.g.verified.cs | 4 +-- ...eObject#WhenChangingDispatch.g.verified.cs | 4 +-- 41 files changed, 138 insertions(+), 103 deletions(-) create mode 100644 src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ChainRegistrationEmitterTests.cs diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs index e6c7e806..e268848d 100644 --- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs +++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ChainRegistrationEmitter.cs @@ -47,7 +47,7 @@ internal static void AppendChoiceOpen( { var declaringType = segment.DeclaringTypeFullName; var valueType = segment.PropertyTypeFullName; - var registration = $"__registration{sb.Length}"; + var registration = $"__registration_{sourceExpression}"; _ = sb.Append(opening).Append("(global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(") .Append(sourceExpression).Append(".GetType(), \"").Append(segment.PropertyName).Append("\", ") diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs index 5f7c6cc2..58405466 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs @@ -64,9 +64,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var ____commandChanges_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(____commandChanges_s0, __p1 => __p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "SaveCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4707 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "SaveCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4707, + __registration___p1, __p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.SaveCommand)).Body, "SaveCommand", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs index ab260139..3cb6e96a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.CFPFallback_DeepPropertyPath#BindInteractionDispatch.g.verified.cs @@ -57,9 +57,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, __p1 => __p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4180 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration4180, + __registration___p1, __p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Confirm)).Body, "Confirm", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs index 101a429e..a60936db 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs @@ -58,9 +58,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __interactionObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__interactionObs_s0, __p1 => __p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4394 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Confirm", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration4394, + __registration___p1, __p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Confirm)).Body, "Confirm", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs index f067c139..b6636e94 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ICG.DeepCommandPath#InvokeCommandDispatch.g.verified.cs @@ -57,9 +57,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __commandObs_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__commandObs_s0, __p1 => __p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Save", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4003 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__p1.GetType(), "Save", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4003, + __registration___p1, __p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Save)).Body, "Save", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ChainRegistrationEmitterTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ChainRegistrationEmitterTests.cs new file mode 100644 index 00000000..be17d961 --- /dev/null +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ChainRegistrationEmitterTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2019-2026 ReactiveUI and Contributors. All rights reserved. +// ReactiveUI and Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Text; +using ReactiveUI.Binding.SourceGenerators.Plugins.Observation; +using ReactiveUI.Binding.SourceGenerators.Tests.Helpers; + +namespace ReactiveUI.Binding.SourceGenerators.Tests.Plugins; + +/// Checks that registration choices are independent of preceding output formatting. +public class ChainRegistrationEmitterTests +{ + /// Windows and Unix line endings produce the same registration declaration and references. + /// Whether the registration observes before-change notifications. + /// A task representing the asynchronous test. + [Test] + [Arguments(false)] + [Arguments(true)] + public async Task AppendChoiceOpen_WithDifferentLineEndings_EmitsTheSameChoice(bool isBeforeChange) + { + const int generatedAffinity = 5; + var unixOutput = new StringBuilder("// Prefix\n\n"); + var windowsOutput = new StringBuilder("// Prefix\r\n\r\n"); + var unixStart = unixOutput.Length; + var windowsStart = windowsOutput.Length; + var segment = ModelFactory.CreatePropertyPathSegment(); + + ChainRegistrationEmitter.AppendChoiceOpen(unixOutput, "__p1", segment, generatedAffinity, isBeforeChange); + ChainRegistrationEmitter.AppendChoiceOpen(windowsOutput, "__p1", segment, generatedAffinity, isBeforeChange); + + await Assert.That(windowsOutput.ToString(windowsStart, windowsOutput.Length - windowsStart)) + .IsEqualTo(unixOutput.ToString(unixStart, unixOutput.Length - unixStart)); + } +} diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs index c0fac3a7..1a388834 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs @@ -48,9 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, __propObs0_p1 => __propObs0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4603 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___propObs0_p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4603, + __registration___propObs0_p1, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs index 8fe4352f..4d3ff9e8 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs @@ -45,9 +45,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, __propObs0_p1 => __propObs0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3747 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___propObs0_p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3747, + __registration___propObs0_p1, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs index 84b2239b..b23fb6ea 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs @@ -48,9 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, __obsProperty0_p1 => __obsProperty0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4334 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___obsProperty0_p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration4334, + __registration___obsProperty0_p1, __obsProperty0_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Count)).Body, "Count", @@ -79,9 +79,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, __obsProperty1_p1 => __obsProperty1_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Message", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration8256 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Message", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___obsProperty1_p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration8256, + __registration___obsProperty1_p1, __obsProperty1_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Message)).Body, "Message", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs index 40c46975..8fce78e8 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs @@ -47,9 +47,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obsProperty0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty0_s0, __obsProperty0_p1 => __obsProperty0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Command1", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4098 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty0_p1.GetType(), "Command1", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___obsProperty0_p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration4098, + __registration___obsProperty0_p1, __obsProperty0_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Command1)).Body, "Command1", @@ -78,9 +78,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obsProperty1_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty1_s0, __obsProperty1_p1 => __obsProperty1_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Command2", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7981 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty1_p1.GetType(), "Command2", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___obsProperty1_p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration7981, + __registration___obsProperty1_p1, __obsProperty1_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.Command2)).Body, "Command2", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs index 8d17ec7e..ec3483c9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obsProperty_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal>(__obsProperty_s0, __obsProperty_p1 => __obsProperty_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty_p1.GetType(), "MyCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3583 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__obsProperty_p1.GetType(), "MyCommand", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___obsProperty_p1 ? (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable>( - __registration3583, + __registration___obsProperty_p1, __obsProperty_p1, ((global::System.Linq.Expressions.Expression>>)(__e => __e.MyCommand)).Body, "MyCommand", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs index 9a69fc27..18f959f1 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3423 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3423, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs index dcdc1b89..169a0fe9 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_00001B08EE215D76(global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1755 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1755, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableName)).Body, "NullableName", @@ -63,9 +63,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_00001B08D596E358(global::SharedScenarios.WhenAnyValue.NullableProperties.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableAge", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4422 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableAge", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4422, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableAge)).Body, "NullableAge", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs index 34006fe3..c6afebb7 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_0000263492582AFF(global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1746 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1746, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs index ca9ef5af..7432a287 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs @@ -28,9 +28,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenAnyValue_0000263492582AFF(global::SharedScenarios.WhenAnyValue.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1535 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1535, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs index caef2758..f6756fa2 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3390 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3390, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -58,9 +58,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, __parent2 => __parent2 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5326 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent2 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration5326, + __registration___parent2, __parent2, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -72,9 +72,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, __parent3 => __parent3 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7199 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent3 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration7199, + __registration___parent3, __parent3, ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs index ec189030..c758707f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFE0F8FBCE6526(global::TestApp.MyAndroidView obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1551 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1551, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs index bf38fed0..3fe04423 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3404 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3404, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs index d74b3bf5..a99c115a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFEF8ACA05A8AD(global::SharedScenarios.WhenChanged.IntProperty.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1690 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1690, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Count)).Body, "Count", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs index 832635e5..c33813df 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_000018C06482B4BD(global::TestApp.MyAppleView obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 15, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1541 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 15, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1541, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs index a176195b..accb6d89 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8E16EAA670(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1806 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1806, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", @@ -63,9 +63,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8E19F31E38(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4511 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Age", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4511, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Age)).Body, "Age", @@ -96,9 +96,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDB8D81A702DF(global::SharedScenarios.WhenChanged.MultipleInvocationsSameViewModel.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7209 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Score", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration7209, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Score)).Body, "Score", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs index 3a4ac29f..2b406cff 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs @@ -48,9 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, __propObs0_p1 => __propObs0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4281 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___propObs0_p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4281, + __registration___propObs0_p1, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.City)).Body, "City", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs index 428193dc..43100b1b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_000013C2FFE01BC5(global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel1 obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1731 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1731, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", @@ -63,9 +63,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_00001520715BEC78(global::SharedScenarios.WhenChanged.MultipleViewModels.ViewModel2 obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4318 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Count", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4318, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Count)).Body, "Count", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs index eeea6912..d38090cb 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3475 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3475, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs index c1ce07c8..f218e60b 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFF613BD504637(global::SharedScenarios.WhenChanged.NullableProperty.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1735 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "NullableName", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1735, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.NullableName)).Body, "NullableName", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs index 223a7fef..776a2e31 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFD2E8D6FC818E(global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1736 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1736, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs index 89e17652..1dcddb3c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs @@ -28,9 +28,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFD2E8D6FC818E(global::SharedScenarios.WhenChanged.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1525 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1525, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs index d97944b2..bdc6ddc2 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_0000039705C7F4BC(global::SharedScenarios.WhenChanged.SinglePropertyReactiveObject.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1786 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1786, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs index 935896dc..aae2193f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_0000245FAC302C0E(global::TestApp.MyWinFormsControl obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 8, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1571 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 8, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1571, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs index 8817eab6..0108c905 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDEC5A322381F(global::TestApp.MyWinUIControl obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 6, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1556 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 6, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1556, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs index 7b15ad2e..e1f331a6 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanged_7FFFDFDB348EEE2E(global::TestApp.MyWpfControl obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1546 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Text", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1546, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Text)).Body, "Text", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs index b160271d..08a91874 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3410 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3410, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -58,9 +58,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, __parent2 => __parent2 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5359 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent2 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration5359, + __registration___parent2, __parent2, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -72,9 +72,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, __parent3 => __parent3 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7244 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent3 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration7244, + __registration___parent3, __parent3, ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs index 394f3572..b80f8f8f 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs @@ -42,9 +42,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3181 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3181, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -56,9 +56,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs2 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs1, __parent2 => __parent2 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration5130 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent2.GetType(), "Model", 5, false) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent2 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration5130, + __registration___parent2, __parent2, ((global::System.Linq.Expressions.Expression>)(__e => __e.Model)).Body, "Model", @@ -70,9 +70,9 @@ internal static partial class __ReactiveUIGeneratedBindings var __obs3 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs2, __parent3 => __parent3 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration7015 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent3.GetType(), "Value", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent3 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration7015, + __registration___parent3, __parent3, ((global::System.Linq.Expressions.Expression>)(__e => __e.Value)).Body, "Value", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs index d5ba8958..c669bdd0 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs @@ -44,9 +44,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3423 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3423, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs index 8daabbdf..3826edde 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs @@ -42,9 +42,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __obs1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__obs0, __parent1 => __parent1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration3206 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__parent1.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___parent1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration3206, + __registration___parent1, __parent1, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs index 32dbf00b..fe92a99a 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFE1C28268960F(global::TestApp.MyChangingViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1586 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1586, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs index cb04eb88..bca04f31 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs @@ -48,9 +48,9 @@ internal static partial class __ReactiveUIGeneratedBindings true); var __propObs0_s1 = new global::ReactiveUI.Primitives.Advanced.SwitchMapSignal(__propObs0_s0, __propObs0_p1 => __propObs0_p1 != null - ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration4301 + ? (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(__propObs0_p1.GetType(), "City", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration___propObs0_p1 ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration4301, + __registration___propObs0_p1, __propObs0_p1, ((global::System.Linq.Expressions.Expression>)(__e => __e.City)).Body, "City", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs index 07bb690f..a5ae00e5 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD5A9BCD06EB3(global::TestApp.MyReactiveViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1586 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1586, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs index 66e891b7..08ca3fb2 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD2B6A9CCF5C7(global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1746 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1746, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs index de9cc0b4..4139351c 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs @@ -28,9 +28,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFD2B6A9CCF5C7(global::SharedScenarios.WhenChanging.SinglePropertyINPC.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1535 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 5, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1535, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name", diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs index 7c2c22df..06cb1140 100644 --- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs +++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs @@ -30,9 +30,9 @@ internal static partial class __ReactiveUIGeneratedBindings private static global::System.IObservable __WhenChanging_7FFFCA92B45AE4E5(global::SharedScenarios.WhenChanging.SinglePropertyReactiveObject.MyViewModel obj) { - return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration1796 + return (global::ReactiveUI.Binding.Fallback.ObservationAffinityChecker.FindHigherAffinityPlugin(obj.GetType(), "Name", 10, true) is global::ReactiveUI.Binding.ICreatesObservableForProperty __registration_obj ? (global::System.IObservable)new global::ReactiveUI.Binding.Observables.PluginPropertyObservable( - __registration1796, + __registration_obj, obj, ((global::System.Linq.Expressions.Expression>)(__e => __e.Name)).Body, "Name",