Skip to content

Commit 1f8e527

Browse files
authored
Merge pull request #944 from CommunityToolkit/dev/nullable-old-property-value
Improve nullable annotations in [ObservableProperty] generated code
2 parents af626a6 + 8e23ac1 commit 1f8e527

File tree

2 files changed

+305
-16
lines changed

2 files changed

+305
-16
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,6 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
834834
{
835835
using ImmutableArrayBuilder<StatementSyntax> setterStatements = ImmutableArrayBuilder<StatementSyntax>.Rent();
836836

837-
// Get the property type syntax
838-
TypeSyntax propertyType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations);
839-
840837
string getterFieldIdentifierName;
841838
ExpressionSyntax getterFieldExpression;
842839
ExpressionSyntax setterFieldExpression;
@@ -872,7 +869,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
872869
// <PROPERTY_TYPE> __oldValue = <FIELD_EXPRESSIONS>;
873870
setterStatements.Add(
874871
LocalDeclarationStatement(
875-
VariableDeclaration(propertyType)
872+
VariableDeclaration(GetMaybeNullPropertyType(propertyInfo))
876873
.AddVariables(
877874
VariableDeclarator(Identifier("__oldValue"))
878875
.WithInitializer(EqualsValueClause(setterFieldExpression)))));
@@ -1001,6 +998,9 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
1001998
Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyInfo.PropertyName))))));
1002999
}
10031000

1001+
// Get the property type syntax
1002+
TypeSyntax propertyType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations);
1003+
10041004
// Generate the inner setter block as follows:
10051005
//
10061006
// if (!global::System.Collections.Generic.EqualityComparer<<PROPERTY_TYPE>>.Default.Equals(<FIELD_EXPRESSION>, value))
@@ -1128,17 +1128,8 @@ public static ImmutableArray<MemberDeclarationSyntax> GetOnPropertyChangeMethods
11281128
Comment($"/// <remarks>This method is invoked right before the value of <see cref=\"{propertyInfo.PropertyName}\"/> is changed.</remarks>")), SyntaxKind.OpenBracketToken, TriviaList())))
11291129
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
11301130

1131-
// Prepare the nullable type for the previous property value. This is needed because if the type is a reference
1132-
// type, the previous value might be null even if the property type is not nullable, as the first invocation would
1133-
// happen when the property is first set to some value that is not null (but the backing field would still be so).
1134-
// As a cheap way to check whether we need to add nullable, we can simply check whether the type name with nullability
1135-
// annotations ends with a '?'. If it doesn't and the type is a reference type, we add it. Otherwise, we keep it.
1136-
TypeSyntax oldValueTypeSyntax = propertyInfo.IsReferenceTypeOrUnconstraindTypeParameter switch
1137-
{
1138-
true when !propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?")
1139-
=> IdentifierName($"{propertyInfo.TypeNameWithNullabilityAnnotations}?"),
1140-
_ => parameterType
1141-
};
1131+
// Get the type for the 'oldValue' parameter (which can be null on first invocation)
1132+
TypeSyntax oldValueTypeSyntax = GetMaybeNullPropertyType(propertyInfo);
11421133

11431134
// Construct the generated method as follows:
11441135
//
@@ -1224,6 +1215,26 @@ public static ImmutableArray<MemberDeclarationSyntax> GetOnPropertyChangeMethods
12241215
onPropertyChanged2Declaration);
12251216
}
12261217

1218+
/// <summary>
1219+
/// Gets the <see cref="TypeSyntax"/> for the type of a given property, when it can possibly be <see langword="null"/>.
1220+
/// </summary>
1221+
/// <param name="propertyInfo">The input <see cref="PropertyInfo"/> instance to process.</param>
1222+
/// <returns>The type of a given property, when it can possibly be <see langword="null"/></returns>
1223+
private static TypeSyntax GetMaybeNullPropertyType(PropertyInfo propertyInfo)
1224+
{
1225+
// Prepare the nullable type for the previous property value. This is needed because if the type is a reference
1226+
// type, the previous value might be null even if the property type is not nullable, as the first invocation would
1227+
// happen when the property is first set to some value that is not null (but the backing field would still be so).
1228+
// As a cheap way to check whether we need to add nullable, we can simply check whether the type name with nullability
1229+
// annotations ends with a '?'. If it doesn't and the type is a reference type, we add it. Otherwise, we keep it.
1230+
return propertyInfo.IsReferenceTypeOrUnconstraindTypeParameter switch
1231+
{
1232+
true when !propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?")
1233+
=> IdentifierName($"{propertyInfo.TypeNameWithNullabilityAnnotations}?"),
1234+
_ => IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations)
1235+
};
1236+
}
1237+
12271238
/// <summary>
12281239
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args of a specified type.
12291240
/// </summary>

0 commit comments

Comments
 (0)