Skip to content

Support C# required keyword in source generation and reflection#139

Merged
xoofx merged 1 commit into
xoofx:mainfrom
fdcastel:feature/required-keyword-support
Mar 29, 2026
Merged

Support C# required keyword in source generation and reflection#139
xoofx merged 1 commit into
xoofx:mainfrom
fdcastel:feature/required-keyword-support

Conversation

@fdcastel
Copy link
Copy Markdown
Contributor

Summary

Types using the C# required keyword (C# 11+) fail to compile when registered with the source generator because the generated deserialization code emits new T() without setting required members, producing CS9035 errors. The reflection path also doesn't detect required members for validation.

This PR fixes both the source generator and the reflection converter to fully support C# required members.

Problem

public class DnsMonitorConfiguration
{
    public required string Host { get; set; }
    public TimeSpan Timeout { get; set; }
}

// Source generator emits:
var instance = new DnsMonitorConfiguration();  // CS9035: required member 'Host' must be set
instance.Host = __host_value;

Solution

Source Generator (YamlSerializerContextGenerator.cs)

  1. Detect required modifierCreateMemberModel() now checks IPropertySymbol.IsRequired and IFieldSymbol.IsRequired in addition to [YamlRequired] and [JsonRequired] attributes.

  2. Route through initializer path — Types with required keyword members are routed through the constructor/object-initializer code path (same as init-only properties), avoiding the bare new T() that causes CS9035.

  3. Use default! as dead-code fallback — Required-keyword members in the object initializer use default! instead of __defaults.Member, since the required-member validation will throw before that fallback is ever observed.

  4. __defaults construction — When pure init-only members coexist with required keyword members, the defaults instance includes required members in its own initializer with default!.

Generated code (after fix):

var instance = new DnsMonitorConfiguration()
{
    Host = __member0_Host_seen ? __member0_Host : default!,
};
instance.Timeout = __member0_Timeout;

if (!__required0_Host)
{
    var missing = new List<string>();
    if (!__required0_Host) missing.Add("Host");
    throw YamlThrowHelper.ThrowMissingRequiredMembers(...);
}

Reflection (YamlObjectConverter.cs)

  • IsRequired() now checks for System.Runtime.CompilerServices.RequiredMemberAttribute on .NET 8+ targets, detecting the C# required keyword at runtime.

Test Coverage

35 new tests covering:

  • required properties with set and init accessors
  • Mixed modes: required keyword + [YamlRequired] + [JsonRequired]
  • Records with required members
  • Value type required properties
  • Inheritance (required in base + derived)
  • Naming policies (camelCase)
  • Custom options via CreateOptions()
  • Missing required member validation (single and multiple)
  • Both reflection and source-generated modes

All 626 tests pass (591 existing + 35 new).

@fdcastel fdcastel marked this pull request as draft March 29, 2026 14:40
@fdcastel
Copy link
Copy Markdown
Contributor Author

Sorry for the messy commit history -- I’ll clean it up soon.

@fdcastel fdcastel force-pushed the feature/required-keyword-support branch from daefed0 to f326801 Compare March 29, 2026 14:44
The source generator now detects the C# 'required' modifier on properties
and fields (via IPropertySymbol.IsRequired / IFieldSymbol.IsRequired) in
addition to [YamlRequired] and [JsonRequired] attributes.

Source generator changes:
- MemberModel gains IsRequiredKeyword and NeedsObjectInitializer properties
- CreateMemberModel() detects C# required modifier via Roslyn symbol APIs
- Types with required-keyword members are routed through the constructor/
  object-initializer code path (avoiding bare 'new T()' which causes CS9035)
- Required-keyword members use 'default!' as fallback in the object
  initializer (dead code since required-member validation throws first)
- __defaults construction includes required members in its initializer when
  needed for pure init-only member defaults

Reflection changes:
- YamlObjectConverter.IsRequired() now checks for RequiredMemberAttribute
  (emitted by the C# compiler for required members) on net8.0+

Tests: 35 new tests covering required set/init properties, mixed required
modes, records, value types, inheritance, naming policies, and custom options
in both reflection and source-generated modes.
@fdcastel fdcastel force-pushed the feature/required-keyword-support branch from f326801 to e38d28a Compare March 29, 2026 14:47
@fdcastel
Copy link
Copy Markdown
Contributor Author

Commit history rewritten.

Just running the final tests before taking it out of draft.

@fdcastel
Copy link
Copy Markdown
Contributor Author

Just for the record: I’m hosting some temporary, unofficial builds here. Source: here.

These builds include all currently submitted PRs (except the ones that were rejected, like #136).

@xoofx xoofx marked this pull request as ready for review March 29, 2026 16:51
@xoofx xoofx merged commit def0ca1 into xoofx:main Mar 29, 2026
1 check passed
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