Skip to content

fix(binder): bind nullable value-type properties over underlying type#161

Merged
Reefact merged 4 commits into
mainfrom
144-nullable-value-binding
Jul 16, 2026
Merged

fix(binder): bind nullable value-type properties over underlying type#161
Reefact merged 4 commits into
mainfrom
144-nullable-value-binding

Conversation

@Reefact

@Reefact Reefact commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

A nullable value-type DTO property (int?, bool?) could not bind through a method-group converter over its underlying type — the call failed to compile with CS0411, because SimpleProperty(r => r.Prop) inferred TArgument = Nullable<int> and the converter stage then expected Func<Nullable<int>, Outcome<T>>. This adds struct-constrained selector overloads so such properties bind directly against an int-taking converter, and records the API-shape decision as ADR-0008 (Proposed).

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation

Changes

  • Add where TArgument : struct overloads of SimpleProperty and ListOfSimpleProperties. Under the constraint, TArgument? is the constructed Nullable<TArgument> — structurally more specific than the reference overload's bare TArgument — so the value-type overload wins for a nullable-value-type property and surfaces the underlying non-nullable type, with no CS0111 / CS0121. Reference and string properties keep resolving to the original overloads.
  • Add ListOfSimpleValuePropertiesConverter<TRequest, TArgument>: the value-type counterpart of the list converter — it unwraps each present element and records a null element as a missing argument (REQUEST_ARGUMENT_REQUIRED) under its indexed path.
  • Add 13 tests: scalar required / absent / invalid via method group, AsOptionalValue, fallback, bool?, lists (required / null-element / invalid-element / optional-absent), string? and List<string?> regressions, and the non-nullable int guard.
  • Document value-type property binding in doc/RequestBinder.en.md and doc/RequestBinder.fr.md (lockstep).
  • Draft ADR-0008 (Proposed) recording the struct-constrained-overload decision, and index it.

Testing

  • dotnet build FirstClassErrors.sln — succeeded, 0 warnings / 0 errors.
  • dotnet test FirstClassErrors.sln — all green (Cli 63, Core 423, Analyzers 85, GenDoc 136, PropertyTests 21, RequestBinder 75), 0 failed.
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests) — 85 passed (run as part of the full-solution suite above).

The binder suite is 75 tests = 62 existing + 13 new; no regression.

Documentation

  • Public API / error documentation updated — XML docs on the new overloads and the new converter.
  • README / doc/ updated — RequestBinder guide (EN).
  • French translation updated — the relevant translation doc/RequestBinder.fr.md was updated in lockstep with the English guide; doc/README.fr.md itself needed no change.
  • No documentation change required

Architecture decisions

  • No architectural decision in this pull request
  • New decision recorded — ADR drafted as Proposed: ADR-0008
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

ADR-0008 records the public-API-contract decision (the struct-constrained overload shape) surfaced by #144 as architecture + blocking, gated on the v1 API freeze. Drafted as Proposed; the maintainer decides its status.

Related issues

Closes #144

🤖 Generated with Claude Code


Generated by Claude Code

claude and others added 4 commits July 16, 2026 07:51
A nullable value-type DTO property (int?, bool?) inferred TArgument as
Nullable<T>, so a value-object factory over the underlying type
(int -> Outcome<T>) could not bind as a method group (CS0411).

Add a struct-constrained SimpleProperty/ListOfSimpleProperties overload
whose selector carries Nullable<TArgument>: structurally more specific
than the reference overload, it wins for value-type properties and
surfaces the underlying non-nullable type, while reference and string
properties keep resolving to the original overload. Lists of value types
get a dedicated converter that unwraps each present element and records a
null element as a missing argument under its indexed path.

Refs: #144
Record the public-API-contract decision behind the value-type selector
overloads: a nullable value-type DTO property binds through a dedicated
struct-constrained overload whose selector carries Nullable<TArgument>
and whose converter runs over the underlying non-nullable type. Drafted
as Proposed and indexed; the maintainer decides its status.

Refs: #144
@Reefact
Reefact merged commit 46dfd21 into main Jul 16, 2026
11 of 12 checks passed
@Reefact
Reefact deleted the 144-nullable-value-binding branch July 16, 2026 11:12
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.

Binder: an int? DTO property cannot bind via a method-group converter (CS0411)

2 participants