Skip to content

Suppress SHARPYAML002 when a converter handles the type#140

Merged
xoofx merged 1 commit into
xoofx:mainfrom
fdcastel:feature/suppress-sharpyaml002-with-converter
Mar 29, 2026
Merged

Suppress SHARPYAML002 when a converter handles the type#140
xoofx merged 1 commit into
xoofx:mainfrom
fdcastel:feature/suppress-sharpyaml002-with-converter

Conversation

@fdcastel
Copy link
Copy Markdown
Contributor

Motivation

The SHARPYAML002 diagnostic fires for member types that are not registered via [YamlSerializable] and are not built-in scalars. However, if a custom converter already handles the type, the diagnostic is a false positive — the generated POCO code path is never reached at runtime.

This is a practical blocker for types like HttpMethod, IPEndPoint, and IPAddress:

  • Registering HttpMethod via [YamlSerializable(typeof(HttpMethod))] fails with CS0176 because the generator accesses static properties via an instance reference.
  • Registering IPEndPoint triggers a nested SHARPYAML002 for IPAddress, which in turn has its own members — creating an unsolvable cascade.

Applications that provide custom YamlConverter<HttpMethod> and YamlConverter<IPEndPoint> (registered via [YamlSourceGenerationOptions(Converters = [...])]) have no way to suppress the diagnostic today, forcing them to exclude affected types from the source-gen context entirely.

In a real-world application (Deucalion — an ASP.NET Core + SignalR monitoring app), this excludes 6 out of 10 model types from source generation due to transitive dependencies on converter-handled types.

Solution

The SHARPYAML002 validation loop in YamlSerializerContextGenerator.EmitContext() now checks for converters before reporting the diagnostic. A new IsTypeHandledByConverter helper checks three conditions:

  1. Member-level [YamlConverter(typeof(...))] attribute — if the member itself declares a converter, skip the entire member.
  2. Type-level [YamlConverter(typeof(...))] attribute — if the member's type is decorated with [YamlConverter], skip the diagnostic.
  3. Context-level YamlConverter<T> registration — walks the base type chain of each converter in [YamlSourceGenerationOptions(Converters = [...])] to find YamlConverter<T> and matches T against the member type.

These checks apply to:

  • Direct member types (e.g., HttpMethod? Method)
  • Array/list element types (e.g., List<CustomType>)
  • Dictionary value types (e.g., Dictionary<string, CustomType>)

Nullable<T> value types are unwrapped before matching (a YamlConverter<MyStruct> suppresses SHARPYAML002 for MyStruct? members).

Tests

Added 9 new diagnostic tests covering all suppression paths:

Test Scenario
SHARPYAML002_IsSuppressed_WhenContextLevelConverterHandlesMemberType Context-level YamlConverter<T> suppresses for direct member
SHARPYAML002_IsSuppressed_WhenMemberHasYamlConverterAttribute [YamlConverter] on member suppresses
SHARPYAML002_IsSuppressed_WhenTypeHasYamlConverterAttribute [YamlConverter] on type suppresses
SHARPYAML002_IsSuppressed_WhenContextConverterHandlesArrayElementType Converter suppresses for List<T> elements
SHARPYAML002_IsSuppressed_WhenContextConverterHandlesDictionaryValueType Converter suppresses for Dictionary<K,V> values
SHARPYAML002_IsSuppressed_WhenConverterInheritsFromAnotherConverter Derived converter (inheriting from YamlConverter<T>) suppresses
SHARPYAML002_IsSuppressed_WhenContextConverterHandlesNullableValueType YamlConverter<T> suppresses for T? members
SHARPYAML002_StillFires_WhenNoConverterHandlesType Regression: diagnostic still fires without converter
SHARPYAML002_StillFires_WhenConverterHandlesDifferentType Regression: converter for type A doesn't suppress for type B

All 628 existing tests continue to pass.

The source generator SHARPYAML002 diagnostic now checks for converters
before reporting unsupported member types. Previously, types like
HttpMethod or IPEndPoint would trigger SHARPYAML002 even when a custom
YamlConverter<T> was registered via [YamlSourceGenerationOptions] or
[YamlConverter] attributes.

The validation now skips the diagnostic when:
- The member has a [YamlConverter(typeof(...))] attribute
- The member type has a [YamlConverter(typeof(...))] attribute
- A context-level YamlConverter<T> handles the member type

This applies to direct member types, array/list element types, and
dictionary value types. Nullable<T> value types are unwrapped before
matching against converters.

Added 9 tests covering all suppression paths and regression cases.
@fdcastel fdcastel force-pushed the feature/suppress-sharpyaml002-with-converter branch from 3ec240c to 46162a4 Compare March 29, 2026 16:46
@fdcastel fdcastel marked this pull request as draft March 29, 2026 16:53
@fdcastel
Copy link
Copy Markdown
Contributor Author

Thanks for the latest merges, @xoofx.

I will rebase this one onto main and test a bit more before removing it from draft.

@fdcastel fdcastel marked this pull request as ready for review March 29, 2026 17:36
@fdcastel
Copy link
Copy Markdown
Contributor Author

Looking great now! @xoofx, whenever you get a chance…

@xoofx xoofx merged commit 6ef7504 into xoofx:main Mar 29, 2026
1 check passed
@xoofx xoofx added the bug label Mar 29, 2026
@fdcastel
Copy link
Copy Markdown
Contributor Author

BTW: These should be the last changes… at least until my next brilliant idea strikes. 😄

And if it’s not asking too much… how about a teeny-tiny new release? Just a microscopic one? 😉

@fdcastel
Copy link
Copy Markdown
Contributor Author

@xoofx xoofx merged commit 6ef7504 into xoofx:main

That was FAST! 🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants