Skip to content

xunit/xunit#3423: Suppress CS8618 for members set in InitializeAsync#203

Merged
bradwilson merged 4 commits intoxunit:mainfrom
matantsach:fix/3423-suppress-cs8618-async-lifetime
Mar 14, 2026
Merged

xunit/xunit#3423: Suppress CS8618 for members set in InitializeAsync#203
bradwilson merged 4 commits intoxunit:mainfrom
matantsach:fix/3423-suppress-cs8618-async-lifetime

Conversation

@matantsach
Copy link
Copy Markdown
Contributor

Summary

Adds a DiagnosticSuppressor that suppresses CS8618 ("Non-nullable field/property must contain a non-null value when exiting constructor") when the member is directly assigned in an IAsyncLifetime.InitializeAsync implementation. Fixes xunit/xunit#3423.

Changes

  • Added NonNullableFieldInitializationSuppressor — walks the InitializeAsync method body looking for direct assignments to the flagged member
  • Added CS8618_Suppression descriptor to Descriptors.Suppressors.cs
  • Added VerifyCompilerWarningSuppressor test helpers to CSharpVerifier.Suppressors.cs (sets CompilerDiagnostics.Warnings so CS8618 is included in analysis)
  • Added 6 test cases covering: field/property suppression, non-IAsyncLifetime classes, uninitialized fields, this.-qualified access, and selective suppression with multiple fields

Design

The suppressor:

  1. Checks if the containing type implements IAsyncLifetime
  2. Finds the InitializeAsync implementation via FindImplementationForInterfaceMember
  3. Walks assignment expressions in the method body
  4. Suppresses only if the specific flagged member is directly assigned

Known Limitations

  • Only detects direct assignments in InitializeAsync body — assignments through helper methods called from InitializeAsync are not detected (conservative approach)
  • When a class has an explicit constructor, CS8618 may be reported on the constructor rather than the field/property (Roslyn behavior per AdditionalLocations for CS8618 in DiagnosticSuppressor dotnet/roslyn#58073). The suppressor handles the field/property location case, which covers the typical IAsyncLifetime pattern

Testing

  • 6 new tests, all passing
  • Full test suite: 3203 tests, 0 failures

Add a DiagnosticSuppressor that suppresses CS8618 (non-nullable field/property
not initialized) when the member is directly assigned in an IAsyncLifetime.InitializeAsync
implementation. Handles both fields and properties, including this-qualified access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@matantsach
Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

When a class has an explicit constructor, CS8618 fires on the
constructor rather than the field/property. The suppressor now
resolves the target member by checking AdditionalLocations and
falling back to parsing the member name from the diagnostic message.

Also adds V3 (ValueTask-based IAsyncLifetime) test coverage for all
scenarios, bringing total tests from 6 to 16.

Inspired by edge cases identified by @ssaporito in #3423.
@matantsach
Copy link
Copy Markdown
Contributor Author

Hi, just checking in — is there anything else needed from my side on this PR? Happy to make adjustments if needed.

@matantsach
Copy link
Copy Markdown
Contributor Author

Friendly ping — this PR has been open for ~25 days now. Is there anything I can adjust or improve to help move it forward? Happy to rework anything needed.

@matantsach
Copy link
Copy Markdown
Contributor Author

Marking this as stale on my end — it's been 27 days with no maintainer response. The PR is still mergeable and CI is green, so happy to revisit if this gets picked up. Thanks!

@bradwilson
Copy link
Copy Markdown
Member

Sorry, other items have been higher priority for me at the moment. I do appreciate the work and will get to it. 🙇🏼‍♂️

@matantsach
Copy link
Copy Markdown
Contributor Author

Sorry, other items have been higher priority for me at the moment. I do appreciate the work and will get to it. 🙇🏼‍♂️

of course np. thank you!

@bradwilson
Copy link
Copy Markdown
Member

There are some warnings causing failures that I don't think are caused by your PR, just that you happen to trigger them. I'm looking at them right now.

Example:

DiagnosticResult.CompilerWarning("CS1701").WithArguments("Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51", "xunit.v3.core", "Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51", "Microsoft.Bcl.AsyncInterfaces")

@bradwilson
Copy link
Copy Markdown
Member

After fixing the reference issue, it appears that none of the tests that verify the suppression are passing with latest Roslyn:

Newer Roslyn versions emit AdditionalLocations on CS8618 diagnostics
(dotnet/roslyn#58096), which causes the test framework to fail when
the expected DiagnosticResult doesn't account for them. Add
DiagnosticOptions.IgnoreAdditionalLocations to all suppressed
diagnostic expectations so tests pass on both old and new Roslyn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@matantsach
Copy link
Copy Markdown
Contributor Author

matantsach commented Mar 14, 2026

Thanks for looking into this! I dug into it — the issue is that newer Roslyn (4.14+) started emitting AdditionalLocations on CS8618 diagnostics (from dotnet/roslyn#58096). The test framework then fails because our expected DiagnosticResult only specifies the primary span, but the actual diagnostic now includes an additional location pointing to the field/property declaration.

The suppressor itself works fine — the diagnostics are correctly suppressed. It's just the test assertions that were too strict.

I've pushed a fix that adds DiagnosticOptions.IgnoreAdditionalLocations to all 10 suppressed diagnostic expectations. This way the tests validate the primary location and suppression status without being sensitive to whether Roslyn includes additional locations or not.

pls lmk if this is fine with you

@bradwilson
Copy link
Copy Markdown
Member

pls lmk if this is fine with you

That seems reasonable to me.

@bradwilson bradwilson merged commit 8da2743 into xunit:main Mar 14, 2026
4 checks passed
@bradwilson
Copy link
Copy Markdown
Member

Thanks!

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.

Non-null value warnings for class members set in InitializeAsync are not suppressed

2 participants