Skip to content

Commit

Permalink
Update generator for unconstrained generic nullability
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Mar 14, 2023
1 parent d2fe8a0 commit de885cb
Show file tree
Hide file tree
Showing 3 changed files with 847 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
/// <param name="NotifyPropertyChangedRecipients">Whether or not the generated property also broadcasts changes.</param>
/// <param name="NotifyDataErrorInfo">Whether or not the generated property also validates its value.</param>
/// <param name="IsOldPropertyValueDirectlyReferenced">Whether the old property value is being directly referenced.</param>
/// <param name="IsReferenceType">Indicates whether the property is of a reference type.</param>
/// <param name="IsReferenceTypeOrUnconstraindTypeParameter">Indicates whether the property is of a reference type or an unconstrained type parameter.</param>
/// <param name="IncludeMemberNotNullOnSetAccessor">Indicates whether to include nullability annotations on the setter.</param>
/// <param name="ForwardedAttributes">The sequence of forwarded attributes for the generated property.</param>
internal sealed record PropertyInfo(
Expand All @@ -31,6 +31,6 @@ internal sealed record PropertyInfo(
bool NotifyPropertyChangedRecipients,
bool NotifyDataErrorInfo,
bool IsOldPropertyValueDirectlyReferenced,
bool IsReferenceType,
bool IsReferenceTypeOrUnconstraindTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
EquatableArray<AttributeInfo> ForwardedAttributes);
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ public static bool TryGetInfo(
bool hasOrInheritsClassLevelNotifyDataErrorInfo = false;
bool hasAnyValidationAttributes = false;
bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced(fieldSymbol, propertyName);
bool isReferenceType = fieldSymbol.Type.IsReferenceType;
bool includeMemberNotNullOnSetAccessor = GetIncludeMemberNotNullOnSetAccessor(fieldSymbol, semanticModel);

// Get the nullability info for the property
GetNullabilityInfo(
fieldSymbol,
semanticModel,
out bool isReferenceTypeOrUnconstraindTypeParameter,
out bool includeMemberNotNullOnSetAccessor);

// Track the property changing event for the property, if the type supports it
if (shouldInvokeOnPropertyChanging)
Expand Down Expand Up @@ -262,7 +267,7 @@ public static bool TryGetInfo(
notifyRecipients,
notifyDataErrorInfo,
isOldPropertyValueDirectlyReferenced,
isReferenceType,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributes.ToImmutable());

Expand Down Expand Up @@ -671,13 +676,23 @@ private static bool IsOldPropertyValueDirectlyReferenced(IFieldSymbol fieldSymbo
}

/// <summary>
/// Checks whether <see cref="MemberNotNullAttribute"/> should be used on the setter.
/// Gets the nullability info on the generated property
/// </summary>
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
/// <returns>Whether <see cref="MemberNotNullAttribute"/> should be used on the setter.</returns>
private static bool GetIncludeMemberNotNullOnSetAccessor(IFieldSymbol fieldSymbol, SemanticModel semanticModel)
/// <param name="isReferenceTypeOrUnconstraindTypeParameter">Whether the property type supports nullability.</param>
/// <param name="includeMemberNotNullOnSetAccessor">Whether <see cref="MemberNotNullAttribute"/> should be used on the setter.</param>
/// <returns></returns>
private static void GetNullabilityInfo(
IFieldSymbol fieldSymbol,
SemanticModel semanticModel,
out bool isReferenceTypeOrUnconstraindTypeParameter,
out bool includeMemberNotNullOnSetAccessor)
{
// We're using IsValueType here and not IsReferenceType to also conver unconstrained type parameter cases.
// This will cover both reference types as well T when the constraints are not struct or unmanaged.
isReferenceTypeOrUnconstraindTypeParameter = !fieldSymbol.Type.IsValueType;

// This is used to avoid nullability warnings when setting the property from a constructor, in case the field
// was marked as not nullable. Nullability annotations are assumed to always be enabled to make the logic simpler.
// Consider this example:
Expand All @@ -695,8 +710,8 @@ private static bool GetIncludeMemberNotNullOnSetAccessor(IFieldSymbol fieldSymbo
//
// The [MemberNotNull] attribute is needed on the setter for the generated Name property so that when Name
// is set, the compiler can determine that the name backing field is also being set (to a non null value).
return
fieldSymbol.Type is { NullableAnnotation: not NullableAnnotation.Annotated, IsReferenceType: true } &&
includeMemberNotNullOnSetAccessor =
fieldSymbol.Type is { NullableAnnotation: not NullableAnnotation.Annotated, IsValueType: false } &&
semanticModel.Compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.MemberNotNullAttribute");
}

Expand Down Expand Up @@ -1001,7 +1016,7 @@ public static ImmutableArray<MemberDeclarationSyntax> GetOnPropertyChangeMethods
// happen when the property is first set to some value that is not null (but the backing field would still be so).
// As a cheap way to check whether we need to add nullable, we can simply check whether the type name with nullability
// annotations ends with a '?'. If it doesn't and the type is a reference type, we add it. Otherwise, we keep it.
TypeSyntax oldValueTypeSyntax = propertyInfo.IsReferenceType switch
TypeSyntax oldValueTypeSyntax = propertyInfo.IsReferenceTypeOrUnconstraindTypeParameter switch
{
true when !propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?")
=> IdentifierName($"{propertyInfo.TypeNameWithNullabilityAnnotations}?"),
Expand Down
Loading

0 comments on commit de885cb

Please sign in to comment.