diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs
index 71a06ba..f1be4f2 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs
@@ -16,7 +16,7 @@ public partial class TestVM
private double? _testProperty;
///
- private ReactiveUI.ObservableAsPropertyHelper? _testPropertyHelper;
+ private readonly ReactiveUI.ObservableAsPropertyHelper _testPropertyHelper;
///
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs
new file mode 100644
index 0000000..f24c8cb
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs
@@ -0,0 +1,51 @@
+// 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.Test;
+
+///
+/// TestClassOAPH VM.
+///
+public partial class TestClassOAPH_VM : ReactiveObject
+{
+ [ObservableAsProperty]
+ private bool _observableTestField;
+
+ [Reactive]
+ private bool _reactiveTestField;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TestClassOAPH_VM()
+ {
+ _observableTestPropertyHelper = this.WhenAnyValue(x => x.ReactiveTestProperty)
+ .ToProperty(this, x => x.ObservableTestProperty);
+
+ _observableTestFieldHelper = this.WhenAnyValue(x => x.ReactiveTestField)
+ .ToProperty(this, x => x.ObservableTestField);
+ }
+
+ ///
+ /// Gets a value indicating whether [observable test property].
+ ///
+ ///
+ /// true if [observable test property]; otherwise, false.
+ ///
+ [ObservableAsProperty]
+ public partial bool ObservableTestProperty { get; }
+
+ ///
+ /// Gets or sets a value indicating whether [reactive test property].
+ ///
+ ///
+ /// true if [reactive test property]; otherwise, false.
+ ///
+ [Reactive]
+ public partial bool ReactiveTestProperty { get; set; }
+}
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
index a6d9504..65b013e 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
@@ -70,6 +70,10 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
[Reactive]
private IEnumerable _people = [new Person()];
+ [Reactive]
+ private double? _myDoubleProperty;
+ [Reactive]
+ private double _myDoubleNonNullProperty;
[BindableDerivedList]
private ReadOnlyObservableCollection? _visiblePeople;
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs
index 2afb46c..90b221d 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs
@@ -19,6 +19,7 @@ internal record ObservableMethodInfo(
bool IsNullableType,
bool IsProperty,
EquatableArray ForwardedPropertyAttributes,
+ string IsReadOnly,
string AccessModifier,
string? InitialValue)
{
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
index 662a649..7b3b47c 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
@@ -41,6 +41,11 @@ public sealed partial class ObservableAsPropertyGenerator
attributeData.TryGetNamedArgument("UseProtected", out bool useProtected);
var useProtectedModifier = useProtected ? "protected" : "private";
+ token.ThrowIfCancellationRequested();
+
+ // Get the can ReadOnly member, if any
+ attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);
+
token.ThrowIfCancellationRequested();
var compilation = context.SemanticModel.Compilation;
@@ -106,6 +111,7 @@ public sealed partial class ObservableAsPropertyGenerator
isNullableType,
false,
propertyAttributes,
+ string.Empty,
useProtectedModifier,
initialValue),
diagnostics.ToImmutable());
@@ -131,6 +137,7 @@ public sealed partial class ObservableAsPropertyGenerator
var observableType = string.Empty;
var isNullableType = false;
+ var isPartialProperty = false;
token.ThrowIfCancellationRequested();
context.GetForwardedAttributes(
@@ -173,10 +180,9 @@ public sealed partial class ObservableAsPropertyGenerator
token.ThrowIfCancellationRequested();
- var inheritance = propertySymbol.IsVirtual ? " virtual" : propertySymbol.IsOverride ? " override" : string.Empty;
+ isPartialProperty = true;
- // Get the can ReadOnly member, if any
- attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);
+ var inheritance = propertySymbol.IsVirtual ? " virtual" : propertySymbol.IsOverride ? " override" : string.Empty;
token.ThrowIfCancellationRequested();
@@ -215,6 +221,12 @@ public sealed partial class ObservableAsPropertyGenerator
}
#endif
+ var isReadOnlyString = string.Empty;
+ if (isPartialProperty)
+ {
+ isReadOnlyString = isReadonly == false ? string.Empty : "readonly";
+ }
+
// Get the containing type info
var targetInfo = TargetInfo.From(propertySymbol.ContainingType);
@@ -229,6 +241,7 @@ public sealed partial class ObservableAsPropertyGenerator
isNullableType,
true,
propertyAttributes,
+ isReadOnlyString,
useProtectedModifier,
initialValue),
diagnostics.ToImmutable());
@@ -307,12 +320,20 @@ private static string GetPropertySyntax(ObservableMethodInfo propertyInfo)
propertyType = propertyInfo.PartialPropertyType;
}
+ var helperTypeName = $"{propertyInfo.AccessModifier} ReactiveUI.ObservableAsPropertyHelper<{propertyType}>?";
+
+ // If the property is readonly, we need to change the helper to be non-nullable
+ if (propertyInfo.IsReadOnly == "readonly")
+ {
+ helperTypeName = $"{propertyInfo.AccessModifier} readonly ReactiveUI.ObservableAsPropertyHelper<{propertyType}>";
+ }
+
return $$"""
///
private {{propertyType}} {{getterFieldIdentifierName}}{{initialValue}};
///
- {{propertyInfo.AccessModifier}} ReactiveUI.ObservableAsPropertyHelper<{{propertyType}}>? {{getterFieldIdentifierName}}Helper;
+ {{helperTypeName}} {{getterFieldIdentifierName}}Helper;
///
{{propertyAttributes}}
diff --git a/src/ReactiveUI.SourceGenerators.slnx b/src/ReactiveUI.SourceGenerators.slnx
deleted file mode 100644
index 752cecd..0000000
--- a/src/ReactiveUI.SourceGenerators.slnx
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file