[bgen] Propagate nullability information for generic type arguments. Fixes #16860#25541
[bgen] Propagate nullability information for generic type arguments. Fixes #16860#25541rolfbjarne wants to merge 31 commits into
Conversation
…ixes #16860. When a property has a generic type like Action<NSObject?, NSError?>, the C# compiler emits a [NullableAttribute(byte[])] where each byte encodes the nullability for the outer type and each generic argument in depth-first order. Previously, bgen only looked at the first byte (the outer type) and ignored nullability for generic type arguments. This change: - Adds GetNullabilityBytes() to AttributeManager to extract the full byte array - Adds a FormatType overload in TypeManager that processes the byte array to annotate each generic type argument with ? where appropriate - Uses the new method when rendering property type declarations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…onsumption. - Use braces for all if/else/for blocks in new code. - Add complex test samples with many generic args, value types, and alternating nullability patterns. - Fix bug where value types incorrectly consumed nullability bytes. - Only apply nullability for void-returning delegates (Action<>) in the non-wrap path to avoid Func<> covariance issues with trampolines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Pass wrapNullabilityBytes to FormatType calls in the getter body so that cast/construction expressions match the declared property type when it has nullable generic arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ .NET for Apple Platforms PR Reviewer completed successfully! |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review Summary
This PR implements proper nullability propagation for generic type arguments in bgen—fixing a long-standing gap where Action<NSObject?, NSError?> was rendered as Action<NSObject, NSError>. The implementation is well-architected with comprehensive test coverage.
✅ Strengths
- Thorough test coverage: 10 test cases covering nullable args, non-nullable args, value types, mixed patterns, and alternating nullability
- Consistent depth-first traversal: Both bgen and mono-api-info use the same depth-first byte-consumption algorithm
- Well-documented: Excellent inline comments explaining the byte array encoding and the Func<> covariance issue
- Parallel fixes: api-tools updated to match, ensuring API diff reports are correct
Issues Found
- Error handling: Missing null check in
AttributeManager.GetNullabilityBytescould throw NullReferenceException - Performance:
AsyncMethodInfo.cshas O(n2) type tree traversal when computing NSError nullability for complex generic types - Breaking change:
MarkdownFormatter.DiffModificationswaps removal/addition order—confirm this is intentional
💡 Suggestions (4)
- Code organization: Extract duplicate "is-void-returning-delegate" check to a helper method (appears 3 times)
- Error handling: Out-of-bounds nullability array access silently defaults to oblivious rather than failing fast
- Code duplication: Nullability suffix detection logic duplicated between
ApiChange.csandHelpers.cs - Documentation: Add comment explaining why
NullableAttributeis now skipped inmono-api-info.cs
CI Status
✅ Most checks passing. Some builds/tests still in progress.
Verdict
DiffModification swap before merging. The performance concern in AsyncMethodInfo is worth investigating but may be acceptable given that most completion handlers have 2-3 args.
Generated by .NET for Apple Platforms PR Reviewer for issue #25541 · 71.4 AIC · ⌖ 9.49 AIC · ⊞ 5.1K
Comment /review to run again
Clarify that the original code had old/new reversed (additions were rendered as removals and vice versa in markdown output). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The element type for wrapped arrays is a simple non-generic type (e.g. NSObject), so FormatType ignores the bytes anyway. Passing them is misleading and would produce incorrect byte indexing if the element type were ever generic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #2512b80] Build passed (Detect API changes) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #2512b80] Build passed (Build packages) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
✅ [PR Build #2512b80] Build passed (Build macOS tests) ✅Pipeline on Agent |
Summary
When properties have generic delegate types (like
Action<NSObject?, NSError?>), bgen now correctly propagates the?nullability annotations on the generic type arguments in the generated C# code.Previously, bgen only read the first byte of the
[NullableAttribute(byte[])]array (for the outer type's nullability), ignoring the remaining bytes that encode nullability for generic type arguments. This caused properties likeAction<UIViewController?, NSError?>to be rendered asAction<UIViewController, NSError>— losing the nullability annotations.Changes
src/bgen/AttributeManager.cs: AddedGetNullabilityBytes()method that extracts the full byte array from[NullableAttribute].src/bgen/TypeManager.cs: AddedFormatType(Type?, Type?, byte[]?)overload and a recursiveFormatTypeUsedInhelper that consumes nullability bytes in depth-first traversal order, correctly skipping value types (which don't have bytes in the array).src/bgen/Generator.cs: Updated property rendering to pass nullability bytes. The "wrap" path applies nullability for all delegate types. The "non-wrap" path only applies for void-returning delegates (Action<>) to avoidFunc<>covariance issues with trampoline signatures.tests/bgen/BGenTests.cs: AddedGenericTypeNullabilitytest with 10 assertions.tests/bgen/tests/generic-type-nullability.cs: Test input covering nullable args, non-nullable args, value types, many args, mixed patterns, and alternating nullability.Key design decisions
NullableAttributearray. The fix correctly identifies value types and skips them during traversal.Func<>covariance guard: Trampolines use non-nullable generic args. This is fine forAction<in T>(contravariant) but breaks forFunc<out T>(covariant). The non-wrap path only applies nullability for void-returning delegates.Fixes #16860
🤖 Pull request created by Copilot