Skip to content

Fix NRT code smells: QueueEntry, LogEntry, conventions#498

Merged
niemyjski merged 4 commits into
mainfrom
fix/nrt-code-smells
Apr 23, 2026
Merged

Fix NRT code smells: QueueEntry, LogEntry, conventions#498
niemyjski merged 4 commits into
mainfrom
fix/nrt-code-smells

Conversation

@niemyjski

@niemyjski niemyjski commented Apr 22, 2026

Copy link
Copy Markdown
Member

Summary

  • QueueEntry.EntryType: Changed from non-nullable Type to Type? on both IQueueEntry and QueueEntry<T>. Poison messages have Value == null, so Value?.GetType() legitimately returns null. All callers already use ?. defensively (e.g., entry.EntryType?.Name). Updated the test wrapper in InMemoryQueueTests.cs to match.
  • LogEntry: Replace = null! initializers with required keyword on CategoryName, Scopes, and Formatter properties (both xunit and xunit.v3). This is an intentional source-breaking change -- callers must provide these values. Both internal construction sites already set all three properties.
  • AGENTS.md: Document acceptable NRT patterns (expression tree !, entity null!, post-filter !, vendored code) so future contributors know which ! usages are intentional vs code smells.

Test plan

  • CI passes -- 0 warnings, 0 errors on dotnet build
  • Existing tests pass with no regressions
  • QueueEntry.EntryType Type? doesn't break callers (verified: all callers use ?. defensively)

…onventions docs

- QueueEntry.EntryType: throw InvalidOperationException instead of ! suppression
  when Value is null (poison message)
- LogEntry: use required keyword instead of null! for CategoryName, Scopes,
  Formatter (both xunit and xunit.v3 variants)
- AGENTS.md: document acceptable NRT patterns (expression tree !, entity null!,
  post-filter !, vendored code)

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

This PR aims to reduce nullable-reference-type (NRT) “code smells” across Foundatio by tightening null semantics in queue/log helper types and documenting accepted NRT patterns for contributors.

Changes:

  • Update QueueEntry<T>.EntryType to stop using null-forgiving and instead throw when Value is null (poison messages).
  • Replace = null! initializers in LogEntry (xunit + xunit.v3) with required members.
  • Add documented NRT conventions to AGENTS.md to clarify when !/null! are acceptable.

Reviewed changes

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

File Description
src/Foundatio/Queues/QueueEntry.cs Changes poison-message behavior for EntryType to throw when Value is null.
src/Foundatio.Xunit/Logging/LogEntry.cs Uses required members instead of null! for key properties.
src/Foundatio.Xunit.v3/Logging/LogEntry.cs Same required member change for the v3 package.
AGENTS.md Documents repo-specific NRT patterns and guidance for ! usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Foundatio/Queues/QueueEntry.cs Outdated
…issing index/type

Foundatio:
- QueueEntry.EntryType: change to Type? instead of throwing -- Copilot correctly
  identified that StartProcessQueueEntryActivity accesses EntryType?.Name BEFORE
  the poison message check, so throwing would break poison message handling
- IQueueEntry.EntryType: update interface to Type? to match
- Test wrapper: update to match new Type? return

Foundatio.Repositories:
- IOptions.Get<T>/SafeGetOption: add [return: NotNullIfNotNull(nameof(defaultValue))]
  so callers passing non-null defaults don't need ! suppressions
- Remove all 6 ! suppressions in IOptions.cs that were only compensating for
  the missing NotNullIfNotNull
- Remove ! from TimeProviderOptions.cs and ElasticCommandOptions.ParentDocumentType
- ChildQueryBuilder: throw InvalidOperationException if index is null (not silent return)
- ParentQueryBuilder: throw InvalidOperationException if index is null when parent queries exist
- SoftDeletesQueryBuilder: throw InvalidOperationException if DocumentType is null
  when SupportsSoftDeletes is enabled

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

This PR tightens nullable reference type (NRT) correctness across queue entries and test logging utilities, and documents agreed-upon NRT patterns to reduce future ! “code smell” usage.

Changes:

  • Make IQueueEntry.EntryType nullable (Type?) and update QueueEntry<T> + a test wrapper accordingly.
  • Switch LogEntry (xunit + xunit.v3) from = null! initializers to required properties.
  • Document NRT conventions in AGENTS.md to clarify when ! and null! are acceptable.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs Updates test wrapper to match nullable EntryType.
src/Foundatio/Queues/QueueEntry.cs Makes EntryType return nullable Type? instead of using !.
src/Foundatio/Queues/IQueueEntry.cs Updates interface contract/docs: EntryType is now nullable.
src/Foundatio.Xunit/Logging/LogEntry.cs Replaces null! defaults with required for key properties.
src/Foundatio.Xunit.v3/Logging/LogEntry.cs Same required change for xunit.v3 variant.
AGENTS.md Adds documented NRT patterns/conventions for contributors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +16
public required string CategoryName { get; set; }
public LogLevel LogLevel { get; set; }
public object[] Scopes { get; set; } = null!;
public required object[] Scopes { get; set; }
public EventId EventId { get; set; }
public object? State { get; set; }
public Exception? Exception { get; set; }
public Func<object?, Exception?, string> Formatter { get; set; } = null!;
public required Func<object?, Exception?, string> Formatter { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. Replaced
equired\ with non-null defaults: \string.Empty\ for CategoryName, []\ for Scopes, and a static default formatter. This preserves NRT safety without breaking downstream consumers who construct \LogEntry\ via object initializer. Fixed in e8b5904.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reverted back to
equired. This is an intentional breaking change -- \LogEntry\ callers should be forced to provide these values. The = null!\ was the real problem.

Comment on lines +10 to +16
public required string CategoryName { get; set; }
public LogLevel LogLevel { get; set; }
public object[] Scopes { get; set; } = null!;
public required object[] Scopes { get; set; }
public EventId EventId { get; set; }
public object? State { get; set; }
public Exception? Exception { get; set; }
public Func<object?, Exception?, string> Formatter { get; set; } = null!;
public required Func<object?, Exception?, string> Formatter { get; set; }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same fix applied here -- replaced
equired\ with non-null defaults (\string.Empty, [], default formatter). Both xunit and xunit.v3 LogEntry are now consistent and non-breaking. Fixed in e8b5904.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same -- reverted to
equired. Intentional breaking change.

Comment on lines +31 to 32
public Type? EntryType => Value?.GetType();
public object GetValue() => Value;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated the PR description to accurately reflect the \Type?\ return behavior instead of the stale throw description. Done.

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

This PR tightens nullable reference type (NRT) correctness across queue entries and test logging utilities, aligning the public APIs with real runtime behavior (notably poison queue entries) and documenting accepted NRT suppression patterns for contributors.

Changes:

  • Make IQueueEntry.EntryType / QueueEntry<T>.EntryType nullable (Type?) to support poison entries with Value == null.
  • Replace null! initializers in LogEntry (xunit + xunit.v3) with safe non-null defaults.
  • Add documented NRT conventions to AGENTS.md to clarify when !/null! is acceptable.

Reviewed changes

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

Show a summary per file
File Description
tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs Updates a test wrapper to match EntryType nullability.
src/Foundatio/Queues/QueueEntry.cs Makes EntryType return null when Value is null instead of using !.
src/Foundatio/Queues/IQueueEntry.cs Updates EntryType to Type? and adjusts the XML docs for poison entries.
src/Foundatio.Xunit/Logging/LogEntry.cs Replaces null! defaults with string.Empty, empty arrays, and a default formatter.
src/Foundatio.Xunit.v3/Logging/LogEntry.cs Mirrors the same safe defaults as the xunit package.
AGENTS.md Documents repo guidance for NRT suppression patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread AGENTS.md
The codebase has NRT enabled. Follow these patterns:

- **Entity/model properties** (`= null!`): Acceptable for properties set by deserialization or ORM frameworks. Prefer `required` keyword where construction is controlled.
- **Expression tree `!`**: Required when expressions like `SortDescending(x => x.DateCreatedUtc!)` box `DateTime?` to `object?` but the delegate expects `object`. The value is never evaluated -- only used for field path extraction. This is correct and unavoidable.
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.

2 participants