Skip to content

Commit 2dd8b54

Browse files
authored
Fix for OAPH can't be created outside of constructor (#80)
1 parent 421035a commit 2dd8b54

File tree

5 files changed

+76
-17
lines changed

5 files changed

+76
-17
lines changed

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,29 @@ public partial class MyReactiveClass : ReactiveObject
111111
}
112112
```
113113

114+
### Usage ObservableAsPropertyHelper with Field and non readonly nullable OAPH field
115+
```csharp
116+
114117
### Usage ObservableAsPropertyHelper with Observable Property
115118
```csharp
116119
using ReactiveUI.SourceGenerators;
117120

118-
public partial class MyReactiveClass : ReactiveObject
119-
{
121+
public partial class MyReactiveClass : ReactiveObject, IActivatableViewModel
122+
{
123+
[ObservableAsProperty(ReadOnly = false)]
124+
private string _myProperty = "Default Value";
125+
120126
public MyReactiveClass()
121127
{
122-
// default value for MyObservableProperty prior to initialization.
123-
_myObservable = "Test Value Pre Init";
124-
125-
// Initialize generated _myObservablePropertyHelper
126-
// for the generated MyObservableProperty
127-
InitializeOAPH();
128+
this.WhenActivated(disposables =>
129+
{
130+
_myPrpertyHelper = MyPropertyObservable()
131+
.ToProperty(this, x => x.MyProperty)
132+
.DisposeWith(disposables);
133+
});
128134
}
129135

130-
[ObservableAsProperty]
131-
IObservable<string> MyObservable => Observable.Return("Test Value");
136+
IObservable<string> MyPropertyObservable() => Observable.Return("Test Value");
132137
}
133138
```
134139

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using System.Reactive;
7+
using System.Reactive.Disposables;
78
using System.Reactive.Linq;
89
using System.Reactive.Subjects;
910
using System.Runtime.Serialization;
@@ -16,8 +17,11 @@ namespace SGReactiveUI.SourceGenerators.Test;
1617
/// <summary>
1718
/// TestClass.
1819
/// </summary>
20+
/// <seealso cref="ReactiveUI.ReactiveObject" />
21+
/// <seealso cref="ReactiveUI.IActivatableViewModel" />
22+
/// <seealso cref="System.IDisposable" />
1923
[DataContract]
20-
public partial class TestViewModel : ReactiveObject, IDisposable
24+
public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDisposable
2125
{
2226
private readonly IObservable<bool> _observable = Observable.Return(true);
2327
private readonly Subject<double?> _testSubject = new();
@@ -28,6 +32,12 @@ public partial class TestViewModel : ReactiveObject, IDisposable
2832
[ObservableAsProperty]
2933
private double? _test2Property = 1.1d;
3034

35+
[ObservableAsProperty(ReadOnly = false)]
36+
private double? _test11Property = 11.1d;
37+
38+
[Reactive]
39+
private double? _test12Property = 12.1d;
40+
3141
[JsonInclude]
3242
[Reactive(SetModifier = AccessModifier.Protected)]
3343
[DataMember]
@@ -39,6 +49,12 @@ public partial class TestViewModel : ReactiveObject, IDisposable
3949
/// </summary>
4050
public TestViewModel()
4151
{
52+
this.WhenActivated(disposables =>
53+
{
54+
Console.Out.WriteLine("Activated");
55+
_test11PropertyHelper = this.WhenAnyValue(x => x.Test12Property).ToProperty(this, x => x.Test11Property, out _).DisposeWith(disposables);
56+
});
57+
4258
Console.Out.WriteLine("MyReadOnlyProperty before init");
4359

4460
// only settable prior to init, after init it will be ignored.
@@ -201,6 +217,11 @@ public TestViewModel()
201217
[ObservableAsProperty]
202218
public IObservable<int> ObservableAsPropertyTest2 => Observable.Return(9);
203219

220+
/// <summary>
221+
/// Gets the Activator which will be used by the View when Activation/Deactivation occurs.
222+
/// </summary>
223+
public ViewModelActivator Activator { get; } = new();
224+
204225
/// <summary>
205226
/// Gets observables as property test.
206227
/// </summary>

src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ internal sealed class ObservableAsPropertyAttribute : Attribute
161161
/// The name of the property.
162162
/// </value>
163163
public string? PropertyName { get; init; }
164+
165+
/// <summary>
166+
/// Gets the Readonly state of the OAPH property.
167+
/// </summary>
168+
/// <value>
169+
/// The is read only of the OAPH property.
170+
/// </value>
171+
public bool ReadOnly { get; init; } = true;
164172
}
165173
#nullable restore
166174
#pragma warning restore

src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Diagnostics.CodeAnalysis;
89
using System.Globalization;
@@ -79,6 +80,19 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
7980
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
8081
.ToImmutableArray();
8182

83+
var modifiers = new List<SyntaxToken>();
84+
var helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>";
85+
if (propertyInfo.AccessModifier == "readonly")
86+
{
87+
modifiers.Add(Token(SyntaxKind.PrivateKeyword));
88+
modifiers.Add(Token(SyntaxKind.ReadOnlyKeyword));
89+
}
90+
else
91+
{
92+
helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>?";
93+
modifiers.Add(Token(SyntaxKind.PrivateKeyword));
94+
}
95+
8296
// Construct the generated property as follows:
8397
//
8498
// /// <inheritdoc cref="<FIELD_NAME>"/>
@@ -91,7 +105,7 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
91105
// }
92106
return
93107
ImmutableArray.Create<MemberDeclarationSyntax>(
94-
FieldDeclaration(VariableDeclaration(ParseTypeName($"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>")))
108+
FieldDeclaration(VariableDeclaration(ParseTypeName(helperTypeName)))
95109
.AddDeclarationVariables(VariableDeclarator(getterFieldIdentifierName + "Helper"))
96110
.AddAttributeLists(
97111
AttributeList(SingletonSeparatedList(
@@ -100,9 +114,7 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
100114
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
101115
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString()))))))
102116
.WithOpenBracketToken(Token(TriviaList(Comment($"/// <inheritdoc cref=\"{propertyInfo.FieldName + "Helper"}\"/>")), SyntaxKind.OpenBracketToken, TriviaList())))
103-
.AddModifiers(
104-
Token(SyntaxKind.PrivateKeyword),
105-
Token(SyntaxKind.ReadOnlyKeyword)),
117+
.AddModifiers([.. modifiers]),
106118
PropertyDeclaration(propertyType, Identifier(propertyInfo.PropertyName))
107119
.AddAttributeLists(
108120
AttributeList(SingletonSeparatedList(
@@ -124,6 +136,7 @@ internal static bool GetFieldInfoFromClass(
124136
FieldDeclarationSyntax fieldSyntax,
125137
IFieldSymbol fieldSymbol,
126138
SemanticModel semanticModel,
139+
bool? isReadonly,
127140
CancellationToken token,
128141
[NotNullWhen(true)] out PropertyInfo? propertyInfo,
129142
out ImmutableArray<DiagnosticInfo> diagnostics)
@@ -282,7 +295,7 @@ internal static bool GetFieldInfoFromClass(
282295
isReferenceTypeOrUnconstraindTypeParameter,
283296
includeMemberNotNullOnSetAccessor,
284297
forwardedAttributes.ToImmutable(),
285-
"public");
298+
isReadonly == false ? string.Empty : "readonly");
286299

287300
diagnostics = builder.ToImmutable();
288301

src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3535
static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
3636
static (context, token) =>
3737
{
38+
var symbol = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, context.TargetNode, token)!;
39+
token.ThrowIfCancellationRequested();
40+
41+
// Skip symbols without the target attribute
42+
if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ObservableAsPropertyAttributeType, out var attributeData))
43+
{
44+
return default;
45+
}
46+
47+
// Get the can PropertyName member, if any
48+
attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);
49+
3850
var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
3951
var fieldSymbol = (IFieldSymbol)context.TargetSymbol;
4052

@@ -43,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4355

4456
token.ThrowIfCancellationRequested();
4557

46-
Execute.GetFieldInfoFromClass(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out var propertyInfo, out var diagnostics);
58+
Execute.GetFieldInfoFromClass(fieldDeclaration, fieldSymbol, context.SemanticModel, isReadonly, token, out var propertyInfo, out var diagnostics);
4759

4860
token.ThrowIfCancellationRequested();
4961
return (Hierarchy: hierarchy, new Result<PropertyInfo?>(propertyInfo, diagnostics));

0 commit comments

Comments
 (0)