Skip to content

Annotate ServiceDescriptor.IsKeyedService for nullability flow and sync ref assembly#124493

Merged
danmoseley merged 5 commits intomainfrom
copilot/fix-servicekey-nullability
Feb 26, 2026
Merged

Annotate ServiceDescriptor.IsKeyedService for nullability flow and sync ref assembly#124493
danmoseley merged 5 commits intomainfrom
copilot/fix-servicekey-nullability

Conversation

Copy link
Contributor

Copilot AI commented Feb 17, 2026

ServiceDescriptor.ServiceKey is nullable (object?), so static analysis did not infer non-nullness after if (descriptor.IsKeyedService). This change makes that contract explicit so keyed-service branches no longer require null-forgiving operators.

  • Nullability contract (implementation)

    • Added [MemberNotNullWhen(true, nameof(ServiceKey))] to ServiceDescriptor.IsKeyedService in Microsoft.Extensions.DependencyInjection.Abstractions.
  • Nullability contract (reference assembly)

    • Added the matching [MemberNotNullWhen(true, "ServiceKey")] annotation to ServiceDescriptor.IsKeyedService in Microsoft.Extensions.DependencyInjection.Abstractions/ref to keep ref/src API annotations in sync.
if (descriptor.IsKeyedService)
{
    object serviceKey = descriptor.ServiceKey; // now recognized as non-null by flow analysis
}
Original prompt

This section details on the original issue you should resolve

<issue_title>Static nullability analysis for ServiceDescriptor.ServiceKey does not take into account ServiceDescriptor.IsKeyedService</issue_title>
<issue_description>### Description

ServiceDescriptor.ServiceKey is typed as object? with nullable reference types enabled.
The static analyzer then cannot take into account the result of ServiceDescriptor.IsKeyedService and even if the service is known to be a keyed service, believes ServiceKey may still be null.
Even though IsKeyedService is implemented as

/// <summary>
/// Indicates whether the service is a keyed service.
/// </summary>
public bool IsKeyedService => ServiceKey != null;

Reproduction Steps

#nullable enable

public void Example(ServiceDescriptor descriptor)
{
  if (descriptor.IsKeyedService) {
    object serviceKey = descriptor.ServiceKey; // <-- complains about potential nullness
  }
}

Expected behavior

Would expect no complaints about potential nullness.

Actual behavior

Got complaints about nullness.

Regression?

No response

Known Workarounds

Use the 'null-forgiving' operator (!) to discard potential nullness. E.g.

#nullable enable

public void Example(ServiceDescriptor descriptor)
{
  if (descriptor.IsKeyedService) {
    object serviceKey = descriptor.ServiceKey!; // <-- no longer complains about potential nullness
  }
}

Configuration

.NET version: .NET 10

Other information

The problem lies in a missing [MemberNotNullWhen] annotation.
The following should fix it:

/// <summary>
/// Indicates whether the service is a keyed service.
/// </summary>
[MemberNotNullWhen(returnValue: true, nameof(ServiceKey)]
public bool IsKeyedService => ServiceKey != null;
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix static nullability analysis for ServiceDescriptor.ServiceKey Annotate ServiceDescriptor.IsKeyedService for nullability flow and add regression coverage Feb 17, 2026
Copilot AI requested a review from stephentoub February 17, 2026 02:27
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title Annotate ServiceDescriptor.IsKeyedService for nullability flow and add regression coverage Annotate ServiceDescriptor.IsKeyedService for nullability flow, sync ref assembly, and add regression coverage Feb 17, 2026
Copilot AI requested a review from stephentoub February 17, 2026 02:50
@stephentoub stephentoub marked this pull request as ready for review February 17, 2026 02:54
Copilot AI review requested due to automatic review settings February 17, 2026 02:54
@stephentoub stephentoub enabled auto-merge (squash) February 17, 2026 02:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a nullability flow analysis issue by adding [MemberNotNullWhen(true, nameof(ServiceKey))] to ServiceDescriptor.IsKeyedService. This annotation tells the C# compiler that when IsKeyedService returns true, the ServiceKey property is guaranteed to be non-null, eliminating the need for null-forgiving operators (!) in code that checks this property.

Changes:

  • Added [MemberNotNullWhen(true, nameof(ServiceKey))] attribute to ServiceDescriptor.IsKeyedService in the implementation
  • Synced the annotation to the reference assembly
  • Added regression test to verify the attribute is present and correctly configured

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs Added MemberNotNullWhen attribute to IsKeyedService property to improve nullability flow analysis
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs Synced the MemberNotNullWhen attribute to the reference assembly
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceCollectionKeyedServiceExtensionsTest.cs Added reflection-based test to ensure the attribute is present with correct parameters

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
auto-merge was automatically disabled February 17, 2026 03:15

Head branch was pushed to by a user without write access

Copilot AI changed the title Annotate ServiceDescriptor.IsKeyedService for nullability flow, sync ref assembly, and add regression coverage Annotate ServiceDescriptor.IsKeyedService for nullability flow and sync ref assembly Feb 17, 2026
Copilot AI requested a review from stephentoub February 17, 2026 03:16
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

@danmoseley
Copy link
Member

/tmp/helix/working/B086093A/p/build/apple/AppleBuild.targets(260,5): error : LLVM ERROR: IO failure on output stream: No space left on device

@danmoseley danmoseley merged commit eeedc34 into main Feb 26, 2026
88 of 90 checks passed
@danmoseley danmoseley deleted the copilot/fix-servicekey-nullability branch February 26, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Static nullability analysis for ServiceDescriptor.ServiceKey does not take into account ServiceDescriptor.IsKeyedService

6 participants