4
4
5
5
using System . Collections . Immutable ;
6
6
using System . ComponentModel ;
7
+ using System . Diagnostics . CodeAnalysis ;
7
8
using System . Globalization ;
8
9
using System . Linq ;
10
+ using System . Threading ;
9
11
using CommunityToolkit . Mvvm . SourceGenerators . ComponentModel . Models ;
10
12
using CommunityToolkit . Mvvm . SourceGenerators . Diagnostics ;
11
13
using CommunityToolkit . Mvvm . SourceGenerators . Extensions ;
@@ -28,10 +30,20 @@ internal static class Execute
28
30
/// <summary>
29
31
/// Processes a given field.
30
32
/// </summary>
33
+ /// <param name="fieldSyntax">The <see cref="FieldDeclarationSyntax"/> instance to process.</param>
31
34
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
35
+ /// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
36
+ /// <param name="token">The cancellation token for the current operation.</param>
37
+ /// <param name="propertyInfo">The resulting <see cref="PropertyInfo"/> value, if successfully retrieved.</param>
32
38
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
33
39
/// <returns>The resulting <see cref="PropertyInfo"/> instance for <paramref name="fieldSymbol"/>, if successful.</returns>
34
- public static PropertyInfo ? TryGetInfo ( IFieldSymbol fieldSymbol , out ImmutableArray < Diagnostic > diagnostics )
40
+ public static bool TryGetInfo (
41
+ FieldDeclarationSyntax fieldSyntax ,
42
+ IFieldSymbol fieldSymbol ,
43
+ SemanticModel semanticModel ,
44
+ CancellationToken token ,
45
+ [ NotNullWhen ( true ) ] out PropertyInfo ? propertyInfo ,
46
+ out ImmutableArray < Diagnostic > diagnostics )
35
47
{
36
48
ImmutableArray < Diagnostic > . Builder builder = ImmutableArray . CreateBuilder < Diagnostic > ( ) ;
37
49
@@ -44,9 +56,10 @@ internal static class Execute
44
56
fieldSymbol . ContainingType ,
45
57
fieldSymbol . Name ) ;
46
58
59
+ propertyInfo = null ;
47
60
diagnostics = builder . ToImmutable ( ) ;
48
61
49
- return null ;
62
+ return false ;
50
63
}
51
64
52
65
// Get the property type and name
@@ -63,12 +76,13 @@ internal static class Execute
63
76
fieldSymbol . ContainingType ,
64
77
fieldSymbol . Name ) ;
65
78
79
+ propertyInfo = null ;
66
80
diagnostics = builder . ToImmutable ( ) ;
67
81
68
82
// If the generated property would collide, skip generating it entirely. This makes sure that
69
83
// users only get the helpful diagnostic about the collision, and not the normal compiler error
70
84
// about a definition for "Property" already existing on the target type, which might be confusing.
71
- return null ;
85
+ return false ;
72
86
}
73
87
74
88
// Check for special cases that are explicitly not allowed
@@ -80,9 +94,10 @@ internal static class Execute
80
94
fieldSymbol . ContainingType ,
81
95
fieldSymbol . Name ) ;
82
96
97
+ propertyInfo = null ;
83
98
diagnostics = builder . ToImmutable ( ) ;
84
99
85
- return null ;
100
+ return false ;
86
101
}
87
102
88
103
ImmutableArray < string > . Builder propertyChangedNames = ImmutableArray . CreateBuilder < string > ( ) ;
@@ -168,6 +183,45 @@ internal static class Execute
168
183
}
169
184
}
170
185
186
+ // Gather explicit forwarded attributes info
187
+ foreach ( AttributeListSyntax attributeList in fieldSyntax . AttributeLists )
188
+ {
189
+ // Only look for attribute lists explicitly targeting the (generated) property. Roslyn will normally emit a
190
+ // CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic suppressor
191
+ // that recognizes uses of this target specifically to support [ObservableProperty].
192
+ if ( attributeList . Target ? . Identifier . Kind ( ) is not SyntaxKind . PropertyKeyword )
193
+ {
194
+ continue ;
195
+ }
196
+
197
+ foreach ( AttributeSyntax attribute in attributeList . Attributes )
198
+ {
199
+ // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
200
+ // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
201
+ // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
202
+ // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
203
+ // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
204
+ // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
205
+ // - We then go over each attribute argument expression and get the operation for it. This will still be available even
206
+ // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
207
+ // constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
208
+ // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
209
+ // - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
210
+ // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
211
+ // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
212
+ // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
213
+ SymbolInfo attributeSymbolInfo = semanticModel . GetSymbolInfo ( attribute , token ) ;
214
+
215
+ if ( ( attributeSymbolInfo . Symbol ?? attributeSymbolInfo . CandidateSymbols . SingleOrDefault ( ) ) is not ISymbol attributeSymbol ||
216
+ ( attributeSymbol as INamedTypeSymbol ?? attributeSymbol . ContainingType ) is not INamedTypeSymbol attributeTypeSymbol )
217
+ {
218
+ continue ;
219
+ }
220
+
221
+ forwardedAttributes . Add ( AttributeInfo . From ( attributeTypeSymbol , semanticModel , attribute . ArgumentList ? . Arguments ?? Enumerable . Empty < AttributeArgumentSyntax > ( ) , token ) ) ;
222
+ }
223
+ }
224
+
171
225
// Log the diagnostic for missing ObservableValidator, if needed
172
226
if ( hasAnyValidationAttributes &&
173
227
! fieldSymbol . ContainingType . InheritsFromFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator" ) )
@@ -190,9 +244,7 @@ internal static class Execute
190
244
fieldSymbol . Name ) ;
191
245
}
192
246
193
- diagnostics = builder . ToImmutable ( ) ;
194
-
195
- return new (
247
+ propertyInfo = new PropertyInfo (
196
248
typeNameWithNullabilityAnnotations ,
197
249
fieldName ,
198
250
propertyName ,
@@ -202,6 +254,10 @@ internal static class Execute
202
254
notifyRecipients ,
203
255
notifyDataErrorInfo ,
204
256
forwardedAttributes . ToImmutable ( ) ) ;
257
+
258
+ diagnostics = builder . ToImmutable ( ) ;
259
+
260
+ return true ;
205
261
}
206
262
207
263
/// <summary>
0 commit comments