diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs
new file mode 100644
index 0000000..7e579df
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.cs
+// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+
+//
+#pragma warning disable
+#nullable enable
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// ObservableAsPropertyAttribute.
+///
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+internal sealed class ObservableAsPropertyAttribute : Attribute
+{
+ ///
+ /// Gets the name of the property.
+ ///
+ ///
+ /// The name of the property.
+ ///
+ public string? PropertyName { get; init; }
+
+ ///
+ /// Gets the Readonly state of the OAPH property.
+ ///
+ ///
+ /// The is read only of the OAPH property.
+ ///
+ public bool ReadOnly { get; init; } = true;
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs
new file mode 100644
index 0000000..7eef1ad
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeNullableRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs
@@ -0,0 +1,34 @@
+//HintName: TestVM.ObservableAsPropertyFromObservable.g.cs
+//
+using ReactiveUI;
+
+#pragma warning disable
+#nullable enable
+
+namespace TestNs
+{
+ ///
+ /// Partial class for the TestVM which contains ReactiveUI Reactive property initialization.
+ ///
+ public partial class TestVM
+ {
+ ///
+ private object? _test7Property;
+
+ ///
+ private ReactiveUI.ObservableAsPropertyHelper? _test7PropertyHelper;
+
+ ///
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.Text.Json.Serialization.JsonIncludeAttribute()]
+ public object? Test7Property { get => _test7Property = (_test7PropertyHelper == null ? _test7Property : _test7PropertyHelper.Value); }
+
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ protected void InitializeOAPH()
+ {
+ _test7PropertyHelper = Test7!.ToProperty(this, nameof(Test7Property));
+ }
+ }
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs
new file mode 100644
index 0000000..7e579df
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute.g.cs
+// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+
+//
+#pragma warning disable
+#nullable enable
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// ObservableAsPropertyAttribute.
+///
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+internal sealed class ObservableAsPropertyAttribute : Attribute
+{
+ ///
+ /// Gets the name of the property.
+ ///
+ ///
+ /// The name of the property.
+ ///
+ public string? PropertyName { get; init; }
+
+ ///
+ /// Gets the Readonly state of the OAPH property.
+ ///
+ ///
+ /// The is read only of the OAPH property.
+ ///
+ public bool ReadOnly { get; init; } = true;
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs
new file mode 100644
index 0000000..99a39e7
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromObservablePropertiesWithAttributeRef#TestVM.ObservableAsPropertyFromObservable.g.verified.cs
@@ -0,0 +1,34 @@
+//HintName: TestVM.ObservableAsPropertyFromObservable.g.cs
+//
+using ReactiveUI;
+
+#pragma warning disable
+#nullable enable
+
+namespace TestNs
+{
+ ///
+ /// Partial class for the TestVM which contains ReactiveUI Reactive property initialization.
+ ///
+ public partial class TestVM
+ {
+ ///
+ private object _test6Property;
+
+ ///
+ private ReactiveUI.ObservableAsPropertyHelper? _test6PropertyHelper;
+
+ ///
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.Text.Json.Serialization.JsonIncludeAttribute()]
+ public object Test6Property { get => _test6Property = _test6PropertyHelper?.Value ?? _test6Property; }
+
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ protected void InitializeOAPH()
+ {
+ _test6PropertyHelper = Test6!.ToProperty(this, nameof(Test6Property));
+ }
+ }
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/OAPFromObservableGeneratorTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/OAPFromObservableGeneratorTests.cs
index 5833d51..ca4e7d6 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/OAPFromObservableGeneratorTests.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/OAPFromObservableGeneratorTests.cs
@@ -169,5 +169,73 @@ public partial class TestVM : ReactiveObject
return VerifyGenerator(driver);
}
+ ///
+ /// Tests that the source generator correctly generates observable properties.
+ ///
+ /// A task to monitor the async.
+ [Fact]
+ public Task FromObservablePropertiesWithAttributeRef()
+ {
+ // Arrange: Setup the source code that matches the generator input expectations.
+ const string sourceCode = """
+ using System;
+ using System.Runtime.Serialization;
+ using System.Text.Json.Serialization;
+ using ReactiveUI;
+ using ReactiveUI.SourceGenerators;
+ using System.Reactive.Linq;
+
+ namespace TestNs;
+
+ public partial class TestVM : ReactiveObject
+ {
+ [ObservableAsProperty(PropertyName = "MyNamedProperty")]
+ [property: JsonInclude]
+ [DataMember]
+ public IObservable Test6 => Observable.Return(new object());
+ }
+ """;
+
+ // Act: Initialize the helper and run the generator.
+ var driver = TestHelper.TestPass(sourceCode);
+
+ // Assert: Verify the generated code.
+ return VerifyGenerator(driver);
+ }
+
+ ///
+ /// Tests that the source generator correctly generates observable properties.
+ ///
+ /// A task to monitor the async.
+ [Fact]
+ public Task FromObservablePropertiesWithAttributeNullableRef()
+ {
+ // Arrange: Setup the source code that matches the generator input expectations.
+ const string sourceCode = """
+ using System;
+ using System.Runtime.Serialization;
+ using System.Text.Json.Serialization;
+ using ReactiveUI;
+ using ReactiveUI.SourceGenerators;
+ using System.Reactive.Linq;
+
+ namespace TestNs;
+
+ public partial class TestVM : ReactiveObject
+ {
+ [ObservableAsProperty(PropertyName = "MyNamedProperty")]
+ [property: JsonInclude]
+ [DataMember]
+ public IObservable Test7 => Observable.Return(new object());
+ }
+ """;
+
+ // Act: Initialize the helper and run the generator.
+ var driver = TestHelper.TestPass(sourceCode);
+
+ // Assert: Verify the generated code.
+ return VerifyGenerator(driver);
+ }
+
private SettingsTask VerifyGenerator(GeneratorDriver driver) => Verify(driver).UseDirectory(TestHelper.VerifiedFilePath()).ScrubLinesContaining("[global::System.CodeDom.Compiler.GeneratedCode(\"");
}
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs
new file mode 100644
index 0000000..503dae1
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI;
+using ReactiveUI.SourceGenerators;
+
+namespace SGReactiveUI.SourceGenerators.Execute.Nested1;
+
+///
+/// Class1.
+///
+public partial class Class1 : ReactiveObject
+{
+ [Reactive]
+ private string? _property1;
+}
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested1/ReactiveUI.SourceGenerators.Execute.Nested1.csproj b/src/ReactiveUI.SourceGenerators.Execute.Nested1/ReactiveUI.SourceGenerators.Execute.Nested1.csproj
new file mode 100644
index 0000000..081b2ab
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested1/ReactiveUI.SourceGenerators.Execute.Nested1.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs
new file mode 100644
index 0000000..40f77bf
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI;
+using ReactiveUI.SourceGenerators;
+
+namespace SGReactiveUI.SourceGenerators.Execute.Nested2;
+
+///
+/// Class1.
+///
+public partial class Class1 : ReactiveObject
+{
+ [Reactive]
+ private string? _property1;
+}
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested2/ReactiveUI.SourceGenerators.Execute.Nested2.csproj b/src/ReactiveUI.SourceGenerators.Execute.Nested2/ReactiveUI.SourceGenerators.Execute.Nested2.csproj
new file mode 100644
index 0000000..e4ab9f7
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested2/ReactiveUI.SourceGenerators.Execute.Nested2.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs
new file mode 100644
index 0000000..90524ce
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI;
+using ReactiveUI.SourceGenerators;
+
+namespace SGReactiveUI.SourceGenerators.Execute.Nested3;
+
+///
+/// Class1.
+///
+public partial class Class1 : ReactiveObject
+{
+ [Reactive]
+ private string? _property1;
+}
diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested3/ReactiveUI.SourceGenerators.Execute.Nested3.csproj b/src/ReactiveUI.SourceGenerators.Execute.Nested3/ReactiveUI.SourceGenerators.Execute.Nested3.csproj
new file mode 100644
index 0000000..2cbe8c2
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute.Nested3/ReactiveUI.SourceGenerators.Execute.Nested3.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+ 12.0
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
index eb453ea..d0b7fcc 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
@@ -84,6 +84,11 @@ public TestViewModel()
_observableAsPropertyTest2Property = 11223344;
Console.Out.WriteLine(ObservableAsPropertyTest2Property);
Console.Out.WriteLine(_observableAsPropertyTest2Property);
+
+ _referenceTypeObservableProperty = default!;
+ ReferenceTypeObservable = Observable.Return(new object());
+ NullableReferenceTypeObservable = Observable.Return(new object());
+
InitializeOAPH();
Console.Out.WriteLine(Test1Command);
@@ -239,6 +244,12 @@ public TestViewModel()
///
public ViewModelActivator Activator { get; } = new();
+ [ObservableAsProperty]
+ private IObservable ReferenceTypeObservable { get; }
+
+ [ObservableAsProperty]
+ private IObservable NullableReferenceTypeObservable { get; }
+
///
/// Gets observables as property test.
///
diff --git a/src/ReactiveUI.SourceGenerators.sln b/src/ReactiveUI.SourceGenerators.sln
index 1a47a1d..762f6a7 100644
--- a/src/ReactiveUI.SourceGenerators.sln
+++ b/src/ReactiveUI.SourceGenerators.sln
@@ -1,5 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
+# 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionConfig", "SolutionConfig", "{F29AF2F3-DEC8-58BC-043A-1447862C832D}"
@@ -26,6 +26,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestLibs", "TestLibs", "{B8
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.SourceGenerators.Analyzers.CodeFixes", "ReactiveUI.SourceGenerators.Analyzers.CodeFixes\ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj", "{BD4FADD9-C0E5-46E9-906E-01B04CC856B5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.SourceGenerators.Execute.Nested1", "ReactiveUI.SourceGenerators.Execute.Nested1\ReactiveUI.SourceGenerators.Execute.Nested1.csproj", "{A4971B7D-E35F-4891-BF32-BE911AE86900}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.SourceGenerators.Execute.Nested2", "ReactiveUI.SourceGenerators.Execute.Nested2\ReactiveUI.SourceGenerators.Execute.Nested2.csproj", "{CB36161E-8F9E-48B5-8CE0-AC130A73BD2A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.SourceGenerators.Execute.Nested3", "ReactiveUI.SourceGenerators.Execute.Nested3\ReactiveUI.SourceGenerators.Execute.Nested3.csproj", "{B42F683D-91D8-4378-9DFE-EC55DB0FE43A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NestedTest", "NestedTest", "{CAFBD27B-5078-4A0C-A4E9-19DCF2A7DF16}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +60,18 @@ Global
{BD4FADD9-C0E5-46E9-906E-01B04CC856B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD4FADD9-C0E5-46E9-906E-01B04CC856B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD4FADD9-C0E5-46E9-906E-01B04CC856B5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4971B7D-E35F-4891-BF32-BE911AE86900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4971B7D-E35F-4891-BF32-BE911AE86900}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4971B7D-E35F-4891-BF32-BE911AE86900}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4971B7D-E35F-4891-BF32-BE911AE86900}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CB36161E-8F9E-48B5-8CE0-AC130A73BD2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CB36161E-8F9E-48B5-8CE0-AC130A73BD2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB36161E-8F9E-48B5-8CE0-AC130A73BD2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CB36161E-8F9E-48B5-8CE0-AC130A73BD2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B42F683D-91D8-4378-9DFE-EC55DB0FE43A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B42F683D-91D8-4378-9DFE-EC55DB0FE43A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B42F683D-91D8-4378-9DFE-EC55DB0FE43A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B42F683D-91D8-4378-9DFE-EC55DB0FE43A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -59,6 +79,10 @@ Global
GlobalSection(NestedProjects) = preSolution
{76D5AC8C-4935-3E4B-BD12-71FAEC2B9A9D} = {B86ED9C1-AFFB-4854-AD80-F4B4050CAD0A}
{849CACF4-B85F-47B5-84B3-7C94DE864E7E} = {B86ED9C1-AFFB-4854-AD80-F4B4050CAD0A}
+ {A4971B7D-E35F-4891-BF32-BE911AE86900} = {CAFBD27B-5078-4A0C-A4E9-19DCF2A7DF16}
+ {CB36161E-8F9E-48B5-8CE0-AC130A73BD2A} = {CAFBD27B-5078-4A0C-A4E9-19DCF2A7DF16}
+ {B42F683D-91D8-4378-9DFE-EC55DB0FE43A} = {CAFBD27B-5078-4A0C-A4E9-19DCF2A7DF16}
+ {CAFBD27B-5078-4A0C-A4E9-19DCF2A7DF16} = {B86ED9C1-AFFB-4854-AD80-F4B4050CAD0A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {173F891B-86A2-4226-B563-A7318CE0E2EC}
diff --git a/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs b/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
index 7fd8eef..0343dff 100644
--- a/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
+++ b/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
@@ -288,6 +288,15 @@ public static bool IsObservableBoolType(this ITypeSymbol? typeSymbol)
return false;
}
+ ///
+ /// Determines whether [is nullable type].
+ ///
+ /// The type symbol.
+ ///
+ /// true if [is nullable type] [the specified type symbol]; otherwise, false .
+ ///
+ public static bool IsNullableType(this ITypeSymbol? typeSymbol) => typeSymbol?.NullableAnnotation == NullableAnnotation.Annotated;
+
public static ITypeSymbol GetTaskReturnType(this ITypeSymbol typeSymbol, Compilation compilation) => typeSymbol switch
{
INamedTypeSymbol { TypeArguments.Length: 1 } namedTypeSymbol => namedTypeSymbol.TypeArguments[0],
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/Models/ObservableMethodInfo.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/Models/ObservableMethodInfo.cs
index b8f2380..a335d4a 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/Models/ObservableMethodInfo.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/Models/ObservableMethodInfo.cs
@@ -20,6 +20,7 @@ internal record ObservableMethodInfo(
string? ArgumentType,
string PropertyName,
string ObservableType,
+ bool IsNullableType,
bool IsProperty,
EquatableArray ForwardedPropertyAttributes)
{
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
index 6e0817c..7d759a4 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
@@ -64,7 +64,9 @@ public sealed partial class ObservableAsPropertyGenerator
var observableType = methodSymbol.ReturnType is not INamedTypeSymbol typeSymbol
? string.Empty
- : typeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ : typeSymbol.TypeArguments[0].GetFullyQualifiedNameWithNullabilityAnnotations();
+
+ var isNullableType = methodSymbol.ReturnType is INamedTypeSymbol nullcheck && nullcheck.TypeArguments[0].IsNullableType();
// Get the hierarchy info for the target symbol, and try to gather the property info
hierarchy = HierarchyInfo.From(methodSymbol.ContainingType);
@@ -82,10 +84,11 @@ public sealed partial class ObservableAsPropertyGenerator
targetInfo.TargetVisibility,
targetInfo.TargetType,
methodSymbol.Name,
- methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
+ methodSymbol.ReturnType.GetFullyQualifiedNameWithNullabilityAnnotations(),
methodSymbol.Parameters.FirstOrDefault()?.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
propertyName ?? (methodSymbol.Name + "Property"),
observableType,
+ isNullableType,
false,
propertyAttributes),
diagnostics.ToImmutable());
@@ -109,7 +112,9 @@ public sealed partial class ObservableAsPropertyGenerator
var observableType = propertySymbol.Type is not INamedTypeSymbol typeSymbol
? string.Empty
- : typeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ : typeSymbol.TypeArguments[0].GetFullyQualifiedNameWithNullabilityAnnotations();
+
+ var isNullableType = propertySymbol.Type is INamedTypeSymbol nullcheck && nullcheck.TypeArguments[0].IsNullableType();
// Get the hierarchy info for the target symbol, and try to gather the property info
hierarchy = HierarchyInfo.From(propertySymbol.ContainingType);
@@ -127,10 +132,11 @@ public sealed partial class ObservableAsPropertyGenerator
targetInfo.TargetVisibility,
targetInfo.TargetType,
propertySymbol.Name,
- propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
+ propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(),
propertySymbol.Parameters.FirstOrDefault()?.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
propertyName ?? (propertySymbol.Name + "Property"),
observableType,
+ isNullableType,
true,
propertyAttributes),
diagnostics.ToImmutable());
@@ -173,15 +179,9 @@ private static string GetPropertySyntax(ObservableMethodInfo propertyInfo)
{
var propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedPropertyAttributes));
var getterFieldIdentifierName = propertyInfo.GetGeneratedFieldName();
- string getterArrowExpression;
- if (propertyInfo.ObservableType.EndsWith("?"))
- {
- getterArrowExpression = $"{getterFieldIdentifierName} = ({getterFieldIdentifierName}Helper == null ? {getterFieldIdentifierName} : {getterFieldIdentifierName}Helper.Value)";
- }
- else
- {
- getterArrowExpression = $"{getterFieldIdentifierName} = {getterFieldIdentifierName}Helper?.Value ?? {getterFieldIdentifierName}";
- }
+ var getterArrowExpression = propertyInfo.IsNullableType
+ ? $"{getterFieldIdentifierName} = ({getterFieldIdentifierName}Helper == null ? {getterFieldIdentifierName} : {getterFieldIdentifierName}Helper.Value)"
+ : $"{getterFieldIdentifierName} = {getterFieldIdentifierName}Helper?.Value ?? {getterFieldIdentifierName}";
return $$"""
///