fix: Add null checks when mapping nullable sources to non-nullable targets with MayBeNull Attribute#2256
fix: Add null checks when mapping nullable sources to non-nullable targets with MayBeNull Attribute#2256Abhijithtv wants to merge 12 commits intoriok:mainfrom
Conversation
|
Thanks for the contribution! The fix turns out to be a bit more involved than it might initially appear, IMO also my original |
|
Thanks, that makes sense! I checked and it looks like AllowNull isn't currently considered in target nullability checks (I also couldn’t find any tests covering it). Would you prefer this to be handled in a separate PR focusing on AllowNull support? Also, regarding the implementation: would you recommend modeling read and write nullability as separate properties (e.g., sourceMayBeNull vs targetAcceptsNull), or passing a context flag like isRead/isWrite? |
|
As these two strongly belong together and the changes shouldn't get too big, I'd do it in the same PR. |
todo - add test for allow Null
latonz
left a comment
There was a problem hiding this comment.
Thanks for the updates, I added my feedback.
Please also update the 5-0.md breaking changes docs.
| ); | ||
| .HaveDiagnostic( | ||
| DiagnosticDescriptors.MapValueMethodTypeMismatch, | ||
| "Cannot assign method return type string of BuildValue() to B.Value of type string" |
There was a problem hiding this comment.
This error message is misleading (string mismatch vs string).
There was a problem hiding this comment.
The test name no longer matches the assertion — it used to allow the nullable return, it now diagnoses it. Please rename to something like MethodToMaybeNullTargetPropertyShouldDiagnoseNullableReturnType.
There was a problem hiding this comment.
Same as above, name says "should map", body now asserts a diagnostic. Please rename and consider whether this case (both sides annotated [MaybeNull]) is what users actually expect to break. Testing [AllowNull] as the way to opt into nullable writes would help here too.
| @@ -92,6 +92,18 @@ private bool IsAccessible(ISymbol symbol, MemberVisibility visibility) | |||
| } | |||
|
|
|||
| public bool IsNullable(ISymbol symbol) | |||
There was a problem hiding this comment.
Three near-identical helpers (IsNullable, IsReadNullable, IsWriteNullable) is a foot-gun, IsNullable now ignores both [MaybeNull] and [AllowNull], so any future call site that picks it without thinking will reintroduce the bug this PR fixes. Could the IsNullable methods be made private?
There was a problem hiding this comment.
I have removed the IsNullable and replaced all its reverences with IsReadNullable and IsWriteNullable
| public bool IsReadNullable => parameter.Type.IsNullable(); | ||
| public bool IsWriteNullable => parameter.Type.IsNullable(); |
There was a problem hiding this comment.
These ignore [MaybeNull] / [AllowNull] on the parameter symbol, while ConstructorParameterMember correctly delegates to SymbolAccessor.IsReadNullable/IsWriteNullable…
| public ITypeSymbol Type => parameter.Type; | ||
| public INamedTypeSymbol? ContainingType => null; | ||
| public bool IsNullable => parameter.Type.IsNullable(); | ||
| public bool IsReadNullable => |
There was a problem hiding this comment.
For MethodMapping, is it sufficient to rely on the parameter type to determine nullability?
Currently, we do not store the parameter symbol within MethodMapping (I’m not sure whether this is feasible or intended). Could you provide some guidance on how nullability should be handled in this case?
There was a problem hiding this comment.
We can't only rely on the type as that would ignore these attributes.
I think storing the symbol within the method parameter should be okay. I'd add helpers though (IsWriteNullable and IsReadNullable).
|
@latonz Thanks for the review. I realized I forgot to mark the PR as a draft earlier. Your feedback was really helpful. |
| - `MaybeNull` attributes are now respected for fields, properties and constructor parameters in read context. | ||
| - `AllowNull` attributes are now respected for fields, properties and constructor parameters in write context. |
There was a problem hiding this comment.
instead of in read/write context IMO when reading/writing values may be clearer?
| .GetReturnTypeAttributes() | ||
| .Any(a => string.Equals(a.AttributeClass?.Name, nameof(MaybeNullAttribute))); | ||
|
|
||
| var effectiveType = hasNullableAttribute |
There was a problem hiding this comment.
IMO SymbolAccessor.TryHasAttribute<MaybeNullAttribute>(methodSymbol.GetReturnTypeAttributes()) should be used.
| bool hasNullableAttribute = methodSymbol | ||
| .GetReturnTypeAttributes() | ||
| .Any(a => string.Equals(a.AttributeClass?.Name, nameof(MaybeNullAttribute))); | ||
|
|
||
| var effectiveType = hasNullableAttribute |
There was a problem hiding this comment.
this logic is duplicated with the UserMethodMappingExtractor… extract into a new helper, e.g. SymbolAccessor.UpgradeReturnNullable(IMethodSymbol)
| int Ordinal, | ||
| string Name, | ||
| ITypeSymbol Type, | ||
| IParameterSymbol? symbol = null, |
| public ITypeSymbol Type => parameter.Type; | ||
| public INamedTypeSymbol? ContainingType => null; | ||
| public bool IsNullable => parameter.Type.IsNullable(); | ||
| public bool IsReadNullable => |
There was a problem hiding this comment.
We can't only rely on the type as that would ignore these attributes.
I think storing the symbol within the method parameter should be okay. I'd add helpers though (IsWriteNullable and IsReadNullable).
|
|
||
| private static string FormattedReturnType(IMethodSymbol methodSymbol) | ||
| { | ||
| bool hasNullableAttribute = methodSymbol |
There was a problem hiding this comment.
We use var across this codebase unless the type really adds some value or is necessary for the compilation. IMO it doesn't add any value here…
| public bool IsAnyNullable() => Path.Any(p => p.IsNullable); | ||
| public bool IsAnyReadNullable() => Path.Any(x => x.IsReadNullable); | ||
|
|
||
| public bool IsWriteNullable() => Path[^1].IsWriteNullable; |
There was a problem hiding this comment.
Shouldn't this be moved to NonEmptyMemberPath?
Add null checks when mapping nullable sources to non-nullable targets with MayBeNull Attribute
Description
For target members, [MaybeNull] should not be considered when determining nullability.
Otherwise, it may lead to incorrect nullable analysis and result in CS8601 ("Possible null reference assignment") warnings.
Fixes #2192
Checklist