Skip to content

[apidiff] Add nullability support to mono-api-info and mono-api-html#25603

Merged
rolfbjarne merged 5 commits into
mainfrom
dev/rolf/api-diff-nullability
Jun 3, 2026
Merged

[apidiff] Add nullability support to mono-api-info and mono-api-html#25603
rolfbjarne merged 5 commits into
mainfrom
dev/rolf/api-diff-nullability

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

mono-api-info now reads NullableAttribute and NullableContextAttribute
metadata from assemblies and appends '?' to type names for nullable
reference types in the XML output (parameters, return types, properties,
fields, and events).

mono-api-html now:

  • Strips nullability annotations when matching methods (so nullability-
    only changes don't cause false removed/added entries)
  • Detects nullability-only changes and renders them under a separate
    '(nullability)' subsection header to indicate they are non-breaking
  • Handles the '?' suffix in type name resolution (GetTypeName)

mono-api-info now reads NullableAttribute and NullableContextAttribute
metadata from assemblies and appends '?' to type names for nullable
reference types in the XML output (parameters, return types, properties,
fields, and events).

mono-api-html now:
- Strips nullability annotations when matching methods (so nullability-
  only changes don't cause false removed/added entries)
- Detects nullability-only changes and renders them under a separate
  '(nullability)' subsection header to indicate they are non-breaking
- Handles the '?' suffix in type name resolution (GetTypeName)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds nullable reference type awareness to the apidiff tooling. mono-api-info now reads NullableAttribute / NullableContextAttribute metadata and emits a trailing ? on type names for nullable reference types (parameters, return types, properties, fields, events). mono-api-html strips these annotations for member matching to avoid false add/remove pairs and groups nullability-only differences under a separate (nullability) subsection so they are clearly identified as non-breaking.

Changes:

  • mono-api-info: new NullabilityHelper reads NullableAttribute / NullableContextAttribute and appends ? to nullable reference type names.
  • mono-api-html: Helper.StripNullability / DiffersOnlyByNullability, plus GetTypeName handling of the trailing ?.
  • mono-api-html: MemberComparer.Modify splits modifications into regular and (nullability) buckets; MethodComparer.Find matches return types ignoring nullability.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/api-tools/mono-api-info/mono-api-info.cs Emits ? suffix on nullable reference type names for fields, properties, events, return types and parameters via new NullabilityHelper.
tools/api-tools/mono-api-html/Helpers.cs GetTypeName recognizes trailing ?; new StripNullability / DiffersOnlyByNullability helpers.
tools/api-tools/mono-api-html/MethodComparer.cs Compare return types after stripping nullability so nullability-only changes don't break method matching.
tools/api-tools/mono-api-html/MemberComparer.cs Splits modified members into normal and (nullability) sections.
tools/api-tools/mono-api-html/ApiChange.cs Flags an ApiChange as nullability-only by recursively comparing source/target attributes and children.

Comment thread tools/api-tools/mono-api-html/ApiChange.cs Outdated
Comment on lines +1509 to +1522
static byte? GetNullableContextFromParent (ICustomAttributeProvider? provider)
{
if (provider is MethodDefinition method)
return GetNullableContextFlag (method.DeclaringType);
if (provider is PropertyDefinition prop)
return GetNullableContextFlag (prop.DeclaringType);
if (provider is FieldDefinition field)
return GetNullableContextFlag (field.DeclaringType);
if (provider is EventDefinition evt)
return GetNullableContextFlag (evt.DeclaringType);
if (provider is TypeDefinition type && type.DeclaringType is not null)
return GetNullableContextFlag (type.DeclaringType);
return null;
}
@rolfbjarne

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Instead of highlighting the entire type name as modified, only the
added or removed '?' character is rendered with the change span.

For example:
  NSRunLoopMode[]<span class='added'>?</span> (added nullability)
  NSRunLoopMode[]<span class='removed-inline'>?</span> (removed nullability)
  Dictionary&lt;string<span class='removed-inline'>?</span>, string<span class='added'>?</span>&gt; (mixed)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rolfbjarne

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne rolfbjarne marked this pull request as ready for review June 3, 2026 13:06
@rolfbjarne

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

.NET for Apple Platforms PR Reviewer completed successfully!

@rolfbjarne

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR adds nullability annotation support to the API diff tooling — a valuable improvement for tracking C# nullable reference type changes.

⚠️ Issues Found

Performance (3 warnings)

  • Character-by-character string allocations in AppendNullabilityModified (lines 67, 92, 102). These allocations add up when processing large API surfaces. Append chars directly or batch substrings.

Correctness (1 warning)

  • IsNullabilitySuffix only checks the first character of HTML entities like & and >, which may not correctly identify nullability suffixes in HTML-encoded contexts.

Code organization (2 suggestions)

  • Potential duplication between XElement-level and string-level DiffersOnlyByNullability methods
  • Incomplete comment about generic parameter handling in IsNullableReferenceType

✅ Strengths

  • Clean separation: Nullability-only changes are rendered under "(nullability)" subsections, clearly distinguishing them from breaking changes
  • Backward compatible: Proper stripping of nullability annotations during method matching prevents false positive removed/added entries
  • Comprehensive coverage: Handles fields, properties, events, method return types, and parameters
  • Well-structured: The NullabilityHelper class provides clear single-purpose methods with good separation of concerns

Verdict

The core logic is sound and the feature works as described. The performance concerns are worth addressing to ensure good behavior on large assemblies, and the HTML entity check should be validated. Otherwise, this is a solid enhancement to the API diff infrastructure.

CI is still running — ensure all checks pass before merge.

Generated by .NET for Apple Platforms PR Reviewer for issue #25603 · sonnet45 1.1M
Comment /review to run again

Comment thread tools/api-tools/mono-api-html/ApiChange.cs Outdated
Comment thread tools/api-tools/mono-api-html/ApiChange.cs Outdated
Comment thread tools/api-tools/mono-api-html/ApiChange.cs
Comment thread tools/api-tools/mono-api-html/ApiChange.cs Outdated
Comment thread tools/api-tools/mono-api-info/mono-api-info.cs
Comment thread tools/api-tools/mono-api-html/ApiChange.cs
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

- Fix DiffersOnlyByNullability to always check children even when parent
  attributes differ by nullability (prevents misclassifying non-nullability
  child changes as nullability-only)
- Add module-level NullableContextAttribute fallback for types that rely
  on assembly-wide nullable context
- Fix per-character string allocations: add TextChunk.Append(char) overload
  and use it instead of ToString() per character
- Improve IsNullabilitySuffix comment to document HTML entity assumption
- Fix misleading comment in IsNullableReferenceType

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ec909e3] Build passed (Detect API changes) ✅

Pipeline on Agent
Hash: ec909e312a35dc37796948c5b395f3574186d17b [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ec909e3] Build passed (Build packages) ✅

Pipeline on Agent
Hash: ec909e312a35dc37796948c5b395f3574186d17b [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: ec909e312a35dc37796948c5b395f3574186d17b [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ec909e3] Build passed (Build macOS tests) ✅

Pipeline on Agent
Hash: ec909e312a35dc37796948c5b395f3574186d17b [PR build]

@rolfbjarne rolfbjarne enabled auto-merge (squash) June 3, 2026 14:45
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #ec909e3] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 193 tests passed 🎉

Tests counts

✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 6 tests passed. Html Report (VSDrops) Download
✅ linker (iOS): All 11 tests passed. Html Report (VSDrops) Download
✅ linker (MacCatalyst): All 11 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 11 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 11 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 20 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 23 tests passed. [attempt 2] Html Report (VSDrops) Download
✅ monotouch (macOS): All 23 tests passed. [attempt 2] Html Report (VSDrops) Download
✅ monotouch (tvOS): All 20 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. [attempt 3] Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

macOS tests

✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Ventura (13): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sequoia (15): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Tahoe (26): All 5 tests passed. Html Report (VSDrops) Download

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: ec909e312a35dc37796948c5b395f3574186d17b [PR build]

@rolfbjarne rolfbjarne merged commit ad81f10 into main Jun 3, 2026
55 checks passed
@rolfbjarne rolfbjarne deleted the dev/rolf/api-diff-nullability branch June 3, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants